Estimate UI
This commit is contained in:
parent
9550f9fe2d
commit
027c0e1a03
19 changed files with 1235 additions and 14 deletions
52
src/Yavsc.Api.Client/Dtos/EstimateDtos.cs
Normal file
52
src/Yavsc.Api.Client/Dtos/EstimateDtos.cs
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Yavsc.Api.Client;
|
||||
|
||||
/// <summary>
|
||||
/// A single billable line of an estimate, mirroring the JSON shape of
|
||||
/// the server-side <c>Yavsc.Models.Billing.CommandLine</c> entity.
|
||||
/// </summary>
|
||||
public sealed class EstimateLineDto
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string Description { get; set; } = string.Empty;
|
||||
public int Count { get; set; } = 1;
|
||||
public decimal UnitaryCost { get; set; }
|
||||
public long EstimateId { get; set; }
|
||||
public string Currency { get; set; } = "EUR";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Estimate payload exchanged with the <c>api/v1/estimate</c> routes
|
||||
/// (<c>EstimateApiController</c>). <see cref="AttachedGraphics"/> and
|
||||
/// <see cref="AttachedFiles"/> are always initialised: the server-side
|
||||
/// entity reads them from non-nullable string properties and a null
|
||||
/// list would break its serialisation.
|
||||
/// </summary>
|
||||
public sealed class EstimateDto
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public long? CommandId { get; set; }
|
||||
public string Title { get; set; } = string.Empty;
|
||||
public string Description { get; set; } = string.Empty;
|
||||
public List<EstimateLineDto> Bill { get; set; } = new();
|
||||
public List<string> AttachedGraphics { get; set; } = new();
|
||||
public List<string> AttachedFiles { get; set; } = new();
|
||||
public string? OwnerId { get; set; }
|
||||
public string ClientId { get; set; } = string.Empty;
|
||||
public string CommandType { get; set; } = string.Empty;
|
||||
public DateTime ProviderValidationDate { get; set; }
|
||||
public DateTime ClientValidationDate { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Response of a successful estimate creation
|
||||
/// (<c>Ok(new { estimate.Id, estimate.Bill })</c>).
|
||||
/// </summary>
|
||||
public sealed class EstimateCreatedDto
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public List<EstimateLineDto> Bill { get; set; } = new();
|
||||
}
|
||||
110
src/Yavsc.Api.Client/EstimateApiClient.cs
Normal file
110
src/Yavsc.Api.Client/EstimateApiClient.cs
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Yavsc.Api.Client;
|
||||
|
||||
/// <summary>
|
||||
/// HTTP client for the estimate routes (<c>api/v1/estimate</c>,
|
||||
/// served by <c>EstimateApiController</c>). Follows the same
|
||||
/// DTO↔path mapper shape as <see cref="BillingApiClient"/>: all
|
||||
/// transport concerns (base URL, JSON, Bearer auth, silent refresh
|
||||
/// on 401) are delegated to <see cref="IYavscApiClient"/>.
|
||||
/// </summary>
|
||||
public sealed class EstimateApiClient
|
||||
{
|
||||
private const string PathPrefix = "estimate";
|
||||
|
||||
private readonly IYavscApiClient _api;
|
||||
private readonly Func<string> _businessBaseAddress;
|
||||
|
||||
public EstimateApiClient(IYavscApiClient api, string businessBaseAddress)
|
||||
: this(api, () => businessBaseAddress)
|
||||
{
|
||||
}
|
||||
|
||||
public EstimateApiClient(IYavscApiClient api, Func<string> businessBaseAddress)
|
||||
{
|
||||
_api = api ?? throw new ArgumentNullException(nameof(api));
|
||||
_businessBaseAddress = businessBaseAddress ?? throw new ArgumentNullException(nameof(businessBaseAddress));
|
||||
|
||||
// Validate initial value early to fail fast on invalid setup.
|
||||
_ = ResolveBusinessBaseAddress();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Lists the estimates of the given owner; when <paramref name="ownerId"/>
|
||||
/// is null, the server falls back to the current user.
|
||||
/// </summary>
|
||||
public Task<List<EstimateDto>> GetEstimatesAsync(string? ownerId = null, CancellationToken ct = default)
|
||||
{
|
||||
var path = string.IsNullOrWhiteSpace(ownerId)
|
||||
? PathPrefix
|
||||
: $"{PathPrefix}?ownerId={Uri.EscapeDataString(ownerId)}";
|
||||
|
||||
return _api.CallAsync<List<EstimateDto>>(HttpMethod.Get, Absolute(path), ct: ct);
|
||||
}
|
||||
|
||||
public Task<EstimateDto> GetEstimateAsync(long id, CancellationToken ct = default)
|
||||
{
|
||||
if (id <= 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(id));
|
||||
|
||||
return _api.CallAsync<EstimateDto>(HttpMethod.Get, Absolute($"{PathPrefix}/{id}"), ct: ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an estimate. When <see cref="EstimateDto.CommandId"/> is set,
|
||||
/// the server also stamps the linked command as validated.
|
||||
/// </summary>
|
||||
public async Task<EstimateCreatedDto> CreateAsync(EstimateDto estimate, CancellationToken ct = default)
|
||||
{
|
||||
if (estimate is null)
|
||||
throw new ArgumentNullException(nameof(estimate));
|
||||
|
||||
var created = await _api.CallAsync<EstimateCreatedDto>(
|
||||
HttpMethod.Post,
|
||||
Absolute(PathPrefix),
|
||||
body: estimate,
|
||||
ct: ct).ConfigureAwait(false);
|
||||
|
||||
return created ?? new EstimateCreatedDto();
|
||||
}
|
||||
|
||||
public Task UpdateAsync(long id, EstimateDto estimate, CancellationToken ct = default)
|
||||
{
|
||||
if (id <= 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(id));
|
||||
if (estimate is null)
|
||||
throw new ArgumentNullException(nameof(estimate));
|
||||
|
||||
estimate.Id = id;
|
||||
|
||||
return _api.CallAsync(
|
||||
HttpMethod.Put,
|
||||
Absolute($"{PathPrefix}/{id}"),
|
||||
body: estimate,
|
||||
ct: ct);
|
||||
}
|
||||
|
||||
public Task DeleteAsync(long id, CancellationToken ct = default)
|
||||
{
|
||||
if (id <= 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(id));
|
||||
|
||||
return _api.CallAsync(HttpMethod.Delete, Absolute($"{PathPrefix}/{id}"), ct: ct);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue