using System; using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Threading; using System.Threading.Tasks; using Yavsc.Models.Billing; using Yavsc.Models.Haircut; using Yavsc; namespace Yavsc.Api.Client; /// /// HTTP client for posting commands to the business billing routes. /// Uses absolute URLs so it can coexist with blog-targeting clients on /// the same shared transport. /// public sealed class BillingApiClient { private const string PathPrefix = "billing"; private readonly IYavscApiClient _api; private readonly Uri _baseAddress; public BillingApiClient(IYavscApiClient api, string businessBaseAddress) { _api = api ?? throw new ArgumentNullException(nameof(api)); if (string.IsNullOrWhiteSpace(businessBaseAddress)) throw new ArgumentException("Base address is required.", nameof(businessBaseAddress)); _baseAddress = new Uri(businessBaseAddress, UriKind.Absolute); } public Task CreateAsync(string billingCode, object payload, CancellationToken ct = default) { if (string.IsNullOrWhiteSpace(billingCode)) throw new ArgumentException("Billing code is required.", nameof(billingCode)); return _api.CallAsync( HttpMethod.Post, Absolute($"{PathPrefix}/{Uri.EscapeDataString(billingCode)}"), body: payload, ct: ct); } public Task> GetHairPrestationsAsync(string billingCode, CancellationToken ct = default) { if (string.IsNullOrWhiteSpace(billingCode)) throw new ArgumentException("Billing code is required.", nameof(billingCode)); return _api.CallAsync>( HttpMethod.Get, Absolute($"{PathPrefix}/{Uri.EscapeDataString(billingCode)}/prestations"), ct: ct); } public async Task> GetQuerySummariesAsync(string billingCode, CancellationToken ct = default) { if (string.IsNullOrWhiteSpace(billingCode)) throw new ArgumentException("Billing code is required.", nameof(billingCode)); var items = await _api.CallAsync>( HttpMethod.Get, Absolute($"{PathPrefix}/{Uri.EscapeDataString(billingCode)}"), ct: ct) ?? new List(); foreach (var item in items) { item.BillingCode = billingCode; } return items; } public async Task GetQueryAsync(string billingCode, long queryId, CancellationToken ct = default) { if (string.IsNullOrWhiteSpace(billingCode)) throw new ArgumentException("Billing code is required.", nameof(billingCode)); if (queryId <= 0) throw new ArgumentOutOfRangeException(nameof(queryId)); var code = billingCode.Trim(); var path = Absolute($"{PathPrefix}/{Uri.EscapeDataString(code)}/{queryId}"); if (string.Equals(code, BillingCodes.Rdv, StringComparison.Ordinal)) { var dto = await _api.CallAsync(HttpMethod.Get, path, ct: ct).ConfigureAwait(false); return MapRdv(dto, code); } if (string.Equals(code, BillingCodes.Brush, StringComparison.Ordinal)) { var dto = await _api.CallAsync(HttpMethod.Get, path, ct: ct).ConfigureAwait(false); return MapBrush(dto, code); } if (string.Equals(code, BillingCodes.MBrush, StringComparison.Ordinal)) { var dto = await _api.CallAsync(HttpMethod.Get, path, ct: ct).ConfigureAwait(false); return MapMBrush(dto, code); } throw new NotSupportedException($"Billing code '{code}' is not supported."); } public Task UpdateAsync(string billingCode, long queryId, BillingQueryDetailsDto payload, CancellationToken ct = default) { if (string.IsNullOrWhiteSpace(billingCode)) throw new ArgumentException("Billing code is required.", nameof(billingCode)); if (queryId <= 0) throw new ArgumentOutOfRangeException(nameof(queryId)); if (payload is null) throw new ArgumentNullException(nameof(payload)); var code = billingCode.Trim(); return _api.CallAsync( HttpMethod.Put, Absolute($"{PathPrefix}/{Uri.EscapeDataString(code)}/{queryId}"), body: BuildUpdatePayload(code, queryId, payload), ct: ct); } private string Absolute(string relativePath) => new Uri(_baseAddress, relativePath).ToString(); private static BillingQueryDetailsDto MapRdv(RdvQueryResponse dto, string billingCode) { return new BillingQueryDetailsDto { Id = dto.Id, BillingCode = billingCode, ActivityCode = dto.ActivityCode ?? string.Empty, PerformerId = dto.PerformerId ?? string.Empty, ClientId = dto.ClientId ?? string.Empty, Description = dto.Description ?? string.Empty, Consent = dto.Consent, EventDate = dto.EventDate, Status = dto.Status, Reason = dto.Reason ?? string.Empty, Provisional = dto.Provisional, Location = dto.Location is null ? null : new BillingLocationDto { Address = dto.Location.Address ?? string.Empty, Latitude = dto.Location.Latitude, Longitude = dto.Location.Longitude, } }; } private static BillingQueryDetailsDto MapBrush(HairCutQueryResponse dto, string billingCode) { return new BillingQueryDetailsDto { Id = dto.Id, BillingCode = billingCode, ActivityCode = dto.ActivityCode ?? string.Empty, PerformerId = dto.PerformerId ?? string.Empty, ClientId = dto.ClientId ?? string.Empty, Description = dto.Description ?? string.Empty, Consent = dto.Consent, EventDate = dto.EventDate, Status = dto.Status, AdditionalInfo = dto.AdditionalInfo ?? string.Empty, Provisional = dto.Provisional, PrestationId = dto.PrestationId, Location = dto.Location is null ? null : new BillingLocationDto { Address = dto.Location.Address ?? string.Empty, Latitude = dto.Location.Latitude, Longitude = dto.Location.Longitude, } }; } private static BillingQueryDetailsDto MapMBrush(HairMultiCutQueryResponse dto, string billingCode) { return new BillingQueryDetailsDto { Id = dto.Id, BillingCode = billingCode, ActivityCode = dto.ActivityCode ?? string.Empty, PerformerId = dto.PerformerId ?? string.Empty, ClientId = dto.ClientId ?? string.Empty, Description = dto.Description ?? string.Empty, Consent = dto.Consent, EventDate = dto.EventDate, Status = dto.Status, Provisional = dto.Provisional, PrestationIds = (dto.Prestations ?? new List()) .Select(p => p.PrestationId) .Where(id => id > 0) .ToList(), Location = dto.Location is null ? null : new BillingLocationDto { Address = dto.Location.Address ?? string.Empty, Latitude = dto.Location.Latitude, Longitude = dto.Location.Longitude, } }; } private static object BuildUpdatePayload(string billingCode, long queryId, BillingQueryDetailsDto payload) { if (string.Equals(billingCode, BillingCodes.Rdv, StringComparison.Ordinal)) { if (payload.EventDate is null) throw new ArgumentException("EventDate is required for Rdv.", nameof(payload)); return new { Id = queryId, ActivityCode = payload.ActivityCode, PerformerId = payload.PerformerId, ClientId = payload.ClientId, Consent = payload.Consent, EventDate = payload.EventDate.Value, Location = ToLocationPayload(payload.Location), Reason = payload.Reason, Status = payload.Status, Provisional = payload.Provisional, Description = payload.Description, }; } if (string.Equals(billingCode, BillingCodes.Brush, StringComparison.Ordinal)) { if (payload.PrestationId is null || payload.PrestationId <= 0) throw new ArgumentException("PrestationId is required for Brush.", nameof(payload)); return new { Id = queryId, ActivityCode = payload.ActivityCode, PerformerId = payload.PerformerId, ClientId = payload.ClientId, Consent = payload.Consent, EventDate = payload.EventDate, Location = ToLocationPayload(payload.Location), PrestationId = payload.PrestationId.Value, AdditionalInfo = payload.AdditionalInfo, Status = payload.Status, Provisional = payload.Provisional, Description = payload.Description, }; } if (string.Equals(billingCode, BillingCodes.MBrush, StringComparison.Ordinal)) { if (payload.EventDate is null) throw new ArgumentException("EventDate is required for MBrush.", nameof(payload)); if (payload.PrestationIds is null || payload.PrestationIds.Count == 0) throw new ArgumentException("At least one prestation is required for MBrush.", nameof(payload)); return new { Id = queryId, ActivityCode = payload.ActivityCode, PerformerId = payload.PerformerId, ClientId = payload.ClientId, Consent = payload.Consent, EventDate = payload.EventDate.Value, Location = ToLocationPayload(payload.Location), Prestations = payload.PrestationIds .Where(id => id > 0) .Select(id => new { PrestationId = id }) .ToList(), Status = payload.Status, Provisional = payload.Provisional, Description = payload.Description, }; } throw new NotSupportedException($"Billing code '{billingCode}' is not supported."); } private static object? ToLocationPayload(BillingLocationDto? location) { if (location is null) { return null; } var payload = new Dictionary { ["Address"] = location.Address, }; if (location.Latitude.HasValue) { payload["Latitude"] = location.Latitude.Value; } if (location.Longitude.HasValue) { payload["Longitude"] = location.Longitude.Value; } return payload; } private sealed class BillingLocationResponse { public string? Address { get; set; } public double Latitude { get; set; } public double Longitude { get; set; } } private sealed class RdvQueryResponse { public long Id { get; set; } public string? ActivityCode { get; set; } public string? PerformerId { get; set; } public string? ClientId { get; set; } public string? Description { get; set; } public bool Consent { get; set; } public DateTime EventDate { get; set; } public QueryStatus Status { get; set; } public string? Reason { get; set; } public decimal? Provisional { get; set; } public BillingLocationResponse? Location { get; set; } } private sealed class HairCutQueryResponse { public long Id { get; set; } public string? ActivityCode { get; set; } public string? PerformerId { get; set; } public string? ClientId { get; set; } public string? Description { get; set; } public bool Consent { get; set; } public DateTime? EventDate { get; set; } public QueryStatus Status { get; set; } public decimal? Provisional { get; set; } public long PrestationId { get; set; } public string? AdditionalInfo { get; set; } public BillingLocationResponse? Location { get; set; } } private sealed class HairMultiCutQueryResponse { public long Id { get; set; } public string? ActivityCode { get; set; } public string? PerformerId { get; set; } public string? ClientId { get; set; } public string? Description { get; set; } public bool Consent { get; set; } public DateTime EventDate { get; set; } public QueryStatus Status { get; set; } public decimal? Provisional { get; set; } public BillingLocationResponse? Location { get; set; } public List? Prestations { get; set; } } private sealed class HairPrestationCollectionItemResponse { public long PrestationId { get; set; } } }