Reopen estimate: carry signature strokes on the estimate payload

Reopening an already-validated estimate showed an empty pad: the
strokes were stored server-side but never sent back. Embed them in the
estimate payload and load them straight into the pad, in the same wire
format PostIt captured — no separate fetch.

- Server: Signature inverse navs get STJ [JsonIgnore] (cycle-safe
  under System.Text.Json, matching ApplicationUser); the four estimate
  read endpoints Include(e => e.Signatures) so strokes ride the payload
- Client: EstimateDto.Signatures with EstimateSignatureDto (Strokes,
  Type=Pro/0|Client/1, CoordinateMax, CapturedAtUtc)
- PostIt: SignaturePadControl.Load(int[]) replaces the buffer and
  raises RedrawRequested; EstimateValidationPageViewModel.LoadExisting
  Signature() picks the perspective's side off the estimate and loads
  it on Attach
- Tests: server asserts GET estimate/{id} carries the strokes; PostIt
  VM tests cover load + side-by-perspective

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
Paul Schneider 2026-09-21 18:03:08 +01:00
commit 064be13388
7 changed files with 177 additions and 7 deletions

View file

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
using PostIt.Controls;
using PostIt.ViewModels;
@ -132,6 +133,51 @@ public class EstimateValidationPageViewModelTests
Assert.Contains("client", cli.RoleLabel, StringComparison.OrdinalIgnoreCase);
}
[Fact]
public void Load_existing_signature_renders_loaded_strokes_in_pad()
{
var strokes = new[] { 1, 5000, 5000 };
var front = new FrontOfficeApiClient(new RecordingApi(), "https://business.example/api/v1/");
var estimate = SampleEstimate();
estimate.Signatures = new List<EstimateSignatureDto>
{
new() { Type = 0, Strokes = strokes, CoordinateMax = 10_000 },
};
var vm = new EstimateValidationPageViewModel(estimate, EstimateListPerspective.Provider, front);
var pad = new SignaturePadControl();
vm.Attach(pad);
var snap = pad.Snapshot();
Assert.False(snap.IsEmpty);
Assert.Equal(strokes, snap.Strokes);
Assert.True(vm.HasSignature);
}
[Fact]
public void Load_existing_signature_picks_side_by_perspective()
{
var proStrokes = new[] { 1, 1000, 1000 };
var cliStrokes = new[] { 1, 9000, 9000 };
var front = new FrontOfficeApiClient(new RecordingApi(), "https://business.example/api/v1/");
var estimate = SampleEstimate();
estimate.Signatures = new List<EstimateSignatureDto>
{
new() { Type = 0, Strokes = proStrokes },
new() { Type = 1, Strokes = cliStrokes },
};
var proPad = new SignaturePadControl();
var proVm = new EstimateValidationPageViewModel(estimate, EstimateListPerspective.Provider, front);
proVm.Attach(proPad);
Assert.Equal(proStrokes, proPad.Snapshot().Strokes);
var cliPad = new SignaturePadControl();
var cliVm = new EstimateValidationPageViewModel(estimate, EstimateListPerspective.Client, front);
cliVm.Attach(cliPad);
Assert.Equal(cliStrokes, cliPad.Snapshot().Strokes);
}
private sealed class RecordingApi : IYavscApiClient
{
public HttpClient Http { get; } = new();