using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using Newtonsoft.Json; namespace Yavsc.Models.Billing; /// /// One captured signature, attached to a single /// . Multiple versions are allowed per /// (EstimateId, Type, SignerId) tuple — the controller reads the /// most recent when asked. The wire-format payload is the /// same int[] shape PostIt produces (see /// PostIt.Models.SignaturePadData): a length-prefixed /// sequence of strokes, each stroke being /// [k, x0, y0, x1, y1, ...] with x, y ∈ [0, /// CoordinateMax]. /// /// /// The column is the source of truth: the /// signature is captured and stored entirely in-app (PostIt) and /// round-tripped as JSON, with no on-disk copy. A dedicated table /// (instead of a JSON column on ) keeps the /// row narrow for list views and preserves /// superseded versions for audit. /// /// public class Signature { [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] public long Id { get; set; } public long EstimateId { get; set; } [ForeignKey(nameof(EstimateId)), JsonIgnore] public virtual Estimate Estimate { get; set; } /// /// The ApplicationUser.Id of the signer. Always /// matches Estimate.OwnerId when /// is , and /// Estimate.ClientId when /// is . /// The authz layer enforces this invariant; we don't /// duplicate the constraint in the schema to keep the model /// honest if a future business rule relaxes it (e.g. proxy /// signing). /// [Required] public string SignerId { get; set; } [ForeignKey(nameof(SignerId)), JsonIgnore] public virtual ApplicationUser Signer { get; set; } public SignatureType Type { get; set; } /// /// The normalised coordinate upper bound used at capture /// time. Today always /// PostIt.Models.SignaturePadData.CoordinateMax /// (10_000). Stored so a future change to the wire format /// can be replayed against old signatures without data /// loss. /// public int CoordinateMax { get; set; } /// /// Wire-format payload. PostgreSQL stores an int[] /// natively via Npgsql; the column is round-tripped through /// JsonConvert only if the migration binds it as /// text for backwards compatibility (see the EF /// configuration in ApplicationDbContext). /// [Required] public int[] Strokes { get; set; } = Array.Empty(); public DateTime CapturedAtUtc { get; set; } }