Split QueryStatus.Accepted into ProAccepted/ClientAccepted

Distinguish the two parties' acceptances: ProAccepted (provider) and
ClientAccepted (client) replace the single Accepted value for new
acceptances. Accepted=2 is retained as a legacy value so existing DB rows
keep their integer mapping.

- FrontOfficeApiController.AcceptQuery sets status by ActorRole
- BillingController provider-ongoing filters match the new values
- FrontOfficeController (MVC) dashboard counts use ProAccepted||Accepted
- PostIt: IsOngoingStatus, GetStatusRank (client-accepted ranks first for
  the pro), and status-badge switches (P/C glyphs) handle the new values
- Tests assert ProAccepted (provider) vs ClientAccepted (client)

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
Paul Schneider 2026-09-21 17:33:32 +01:00
commit 725fd0c728
8 changed files with 62 additions and 19 deletions

View file

@ -171,5 +171,9 @@ public partial class BillingQueriesPageViewModel : ViewModelBase, IActionStatusV
} }
private static bool IsOngoingStatus(QueryStatus status) private static bool IsOngoingStatus(QueryStatus status)
=> status is QueryStatus.Inserted or QueryStatus.Accepted or QueryStatus.InProgress; => status is QueryStatus.Inserted
or QueryStatus.Accepted
or QueryStatus.ProAccepted
or QueryStatus.ClientAccepted
or QueryStatus.InProgress;
} }

View file

@ -166,6 +166,8 @@ public partial class BillingQueryDetailsPageViewModel : ViewModelBase, IActionSt
=> status switch => status switch
{ {
QueryStatus.Accepted => "#E6F7EC", QueryStatus.Accepted => "#E6F7EC",
QueryStatus.ProAccepted => "#E6F7EC",
QueryStatus.ClientAccepted => "#E1F5FE",
QueryStatus.InProgress => "#FFF4D6", QueryStatus.InProgress => "#FFF4D6",
QueryStatus.Rejected => "#FDECEA", QueryStatus.Rejected => "#FDECEA",
QueryStatus.Failed => "#ECEFF1", QueryStatus.Failed => "#ECEFF1",
@ -177,6 +179,8 @@ public partial class BillingQueryDetailsPageViewModel : ViewModelBase, IActionSt
=> status switch => status switch
{ {
QueryStatus.Accepted => "#2E7D32", QueryStatus.Accepted => "#2E7D32",
QueryStatus.ProAccepted => "#2E7D32",
QueryStatus.ClientAccepted => "#1565C0",
QueryStatus.InProgress => "#B26A00", QueryStatus.InProgress => "#B26A00",
QueryStatus.Rejected => "#C62828", QueryStatus.Rejected => "#C62828",
QueryStatus.Failed => "#607D8B", QueryStatus.Failed => "#607D8B",
@ -188,6 +192,8 @@ public partial class BillingQueryDetailsPageViewModel : ViewModelBase, IActionSt
=> status switch => status switch
{ {
QueryStatus.Accepted => "#1B5E20", QueryStatus.Accepted => "#1B5E20",
QueryStatus.ProAccepted => "#1B5E20",
QueryStatus.ClientAccepted => "#0D47A1",
QueryStatus.InProgress => "#7A4A00", QueryStatus.InProgress => "#7A4A00",
QueryStatus.Rejected => "#8E0000", QueryStatus.Rejected => "#8E0000",
QueryStatus.Failed => "#37474F", QueryStatus.Failed => "#37474F",
@ -199,6 +205,8 @@ public partial class BillingQueryDetailsPageViewModel : ViewModelBase, IActionSt
=> status switch => status switch
{ {
QueryStatus.Accepted => "OK", QueryStatus.Accepted => "OK",
QueryStatus.ProAccepted => "P",
QueryStatus.ClientAccepted => "C",
QueryStatus.InProgress => "~", QueryStatus.InProgress => "~",
QueryStatus.Rejected => "!", QueryStatus.Rejected => "!",
QueryStatus.Failed => "X", QueryStatus.Failed => "X",

View file

@ -344,11 +344,13 @@ public partial class ProviderOngoingRequestsPageViewModel : ViewModelBase, IActi
=> status switch => status switch
{ {
QueryStatus.InProgress => 0, QueryStatus.InProgress => 0,
QueryStatus.Accepted => 1, QueryStatus.ClientAccepted => 1,
QueryStatus.Inserted => 2, QueryStatus.ProAccepted => 2,
QueryStatus.Success => 3, QueryStatus.Accepted => 2,
QueryStatus.Rejected => 4, QueryStatus.Inserted => 3,
QueryStatus.Failed => 5, QueryStatus.Success => 4,
QueryStatus.Rejected => 5,
QueryStatus.Failed => 6,
_ => 99, _ => 99,
}; };

View file

@ -5,15 +5,28 @@ namespace Yavsc
/// should be associated to any /// should be associated to any
/// client user query to a provider user or /// client user query to a provider user or
/// other external entity. /// other external entity.
///
/// <para>
/// Acceptance is split by party: <see cref="ProAccepted"/> when the
/// provider accepts the client's request (and produces/submits the
/// estimate), <see cref="ClientAccepted"/> when the client accepts
/// the provider's estimate. <see cref="Accepted"/> is the legacy
/// single-value kept for rows written before the split; new writes
/// use the party-specific values. Its integer value (2) is stable
/// so existing rows still deserialize.
/// </para>
/// </summary> /// </summary>
public enum QueryStatus: int public enum QueryStatus: int
{ {
Inserted, Inserted = 0,
Rejected, Rejected = 1,
Accepted, Accepted = 2,
InProgress, InProgress = 3,
// final states // final states
Failed, Failed = 4,
Success Success = 5,
// party-specific acceptance (see summary)
ProAccepted = 6,
ClientAccepted = 7,
} }
} }

View file

@ -127,7 +127,8 @@ public sealed class FrontOfficeApiControllerTests : IClassFixture<ApiWebServerFi
var assertDb = assertScope.ServiceProvider.GetRequiredService<ApplicationDbContext>(); var assertDb = assertScope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
var updated = assertDb.RdvQueries.Single(q => q.Id == queryId); var updated = assertDb.RdvQueries.Single(q => q.Id == queryId);
Assert.Equal(QueryStatus.Accepted, updated.Status); // Default caller is "alice" (the provider) → ProAccepted.
Assert.Equal(QueryStatus.ProAccepted, updated.Status);
} }
[Fact] [Fact]
@ -146,7 +147,7 @@ public sealed class FrontOfficeApiControllerTests : IClassFixture<ApiWebServerFi
using var assertScope = _fixture.Services.CreateScope(); using var assertScope = _fixture.Services.CreateScope();
var assertDb = assertScope.ServiceProvider.GetRequiredService<ApplicationDbContext>(); var assertDb = assertScope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
Assert.Equal(QueryStatus.Accepted, assertDb.RdvQueries.Single(q => q.Id == queryId).Status); Assert.Equal(QueryStatus.ProAccepted, assertDb.RdvQueries.Single(q => q.Id == queryId).Status);
var estimate = assertDb.Estimates.Single(e => e.Id == estimateId); var estimate = assertDb.Estimates.Single(e => e.Id == estimateId);
Assert.True(estimate.ProviderValidationDate != default); Assert.True(estimate.ProviderValidationDate != default);
@ -173,7 +174,7 @@ public sealed class FrontOfficeApiControllerTests : IClassFixture<ApiWebServerFi
using var assertScope = _fixture.Services.CreateScope(); using var assertScope = _fixture.Services.CreateScope();
var assertDb = assertScope.ServiceProvider.GetRequiredService<ApplicationDbContext>(); var assertDb = assertScope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
Assert.Equal(QueryStatus.Accepted, assertDb.RdvQueries.Single(q => q.Id == queryId).Status); Assert.Equal(QueryStatus.ClientAccepted, assertDb.RdvQueries.Single(q => q.Id == queryId).Status);
var estimate = assertDb.Estimates.Single(e => e.Id == estimateId); var estimate = assertDb.Estimates.Single(e => e.Id == estimateId);
Assert.True(estimate.ClientValidationDate != default); Assert.True(estimate.ClientValidationDate != default);

View file

@ -157,6 +157,8 @@ namespace Yavsc.ApiControllers
.Where(q => allowedActivityCodes.Contains(q.ActivityCode)) .Where(q => allowedActivityCodes.Contains(q.ActivityCode))
.Where(q => q.Status == QueryStatus.Inserted .Where(q => q.Status == QueryStatus.Inserted
|| q.Status == QueryStatus.Accepted || q.Status == QueryStatus.Accepted
|| q.Status == QueryStatus.ProAccepted
|| q.Status == QueryStatus.ClientAccepted
|| q.Status == QueryStatus.InProgress) || q.Status == QueryStatus.InProgress)
.Cast<NominativeServiceCommand>() .Cast<NominativeServiceCommand>()
.ToList() .ToList()
@ -169,6 +171,8 @@ namespace Yavsc.ApiControllers
.Where(q => allowedActivityCodes.Contains(q.ActivityCode)) .Where(q => allowedActivityCodes.Contains(q.ActivityCode))
.Where(q => q.Status == QueryStatus.Inserted .Where(q => q.Status == QueryStatus.Inserted
|| q.Status == QueryStatus.Accepted || q.Status == QueryStatus.Accepted
|| q.Status == QueryStatus.ProAccepted
|| q.Status == QueryStatus.ClientAccepted
|| q.Status == QueryStatus.InProgress) || q.Status == QueryStatus.InProgress)
.Cast<NominativeServiceCommand>() .Cast<NominativeServiceCommand>()
.ToList() .ToList()
@ -181,6 +185,8 @@ namespace Yavsc.ApiControllers
.Where(q => allowedActivityCodes.Contains(q.ActivityCode)) .Where(q => allowedActivityCodes.Contains(q.ActivityCode))
.Where(q => q.Status == QueryStatus.Inserted .Where(q => q.Status == QueryStatus.Inserted
|| q.Status == QueryStatus.Accepted || q.Status == QueryStatus.Accepted
|| q.Status == QueryStatus.ProAccepted
|| q.Status == QueryStatus.ClientAccepted
|| q.Status == QueryStatus.InProgress) || q.Status == QueryStatus.InProgress)
.Cast<NominativeServiceCommand>() .Cast<NominativeServiceCommand>()
.ToList() .ToList()

View file

@ -100,7 +100,16 @@ namespace Yavsc.ApiControllers
estimate.ClientValidationDate = DateTime.UtcNow; estimate.ClientValidationDate = DateTime.UtcNow;
} }
query.Status = QueryStatus.Accepted; // The party-specific status records who accepted: the
// provider (ProAccepted) or the client (ClientAccepted).
// An admin accepting (neither party) falls back to the
// legacy generic Accepted.
query.Status = role switch
{
ActorRole.Provider => QueryStatus.ProAccepted,
ActorRole.Client => QueryStatus.ClientAccepted,
_ => QueryStatus.Accepted,
};
try try
{ {

View file

@ -46,9 +46,9 @@ namespace Yavsc.Controllers
{ {
EstimateToProduceCount = _context.RdvQueries.Where(c => c.PerformerId == uid && c.EventDate > now && c.Status == QueryStatus.Inserted EstimateToProduceCount = _context.RdvQueries.Where(c => c.PerformerId == uid && c.EventDate > now && c.Status == QueryStatus.Inserted
&& c.ValidationDate == null && !_context.Estimates.Any(e => e.CommandId == c.Id)).Count(), && c.ValidationDate == null && !_context.Estimates.Any(e => e.CommandId == c.Id)).Count(),
EstimateToHonorAsProCount = _context.RdvQueries.Where(c => c.PerformerId == uid && c.EventDate > now && c.Status == QueryStatus.Accepted EstimateToHonorAsProCount = _context.RdvQueries.Where(c => c.PerformerId == uid && c.EventDate > now && (c.Status == QueryStatus.ProAccepted || c.Status == QueryStatus.Accepted)
&& c.ValidationDate == null && _context.Estimates.Any(e => e.CommandId == c.Id )).Count(), && c.ValidationDate == null && _context.Estimates.Any(e => e.CommandId == c.Id )).Count(),
EstimateToSignAsCliCount = _context.Estimates.Where(e => e.ClientId == uid && e.Query.Status == QueryStatus.Accepted).Count(), EstimateToSignAsCliCount = _context.Estimates.Where(e => e.ClientId == uid && (e.Query.Status == QueryStatus.ProAccepted || e.Query.Status == QueryStatus.Accepted)).Count(),
BillToSignAsCliCount = 0, BillToSignAsCliCount = 0,
NewPayementsCount = 0 NewPayementsCount = 0