Update
This commit is contained in:
@@ -85,4 +85,28 @@ public class OfferingEntryController : ControllerBase
|
||||
await _hub.Clients.Group(result.SessionDate).SendAsync("LineAdded", result);
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
// ── Paper-proof PDF for the date's session (merged client-side) ──────────
|
||||
// Date-keyed so the anonymous page (which has no session id) can attach the
|
||||
// count sheet / envelope photos. Mirrors OfferingSessionsController's proof
|
||||
// validation; the desktop session page reviews/deletes the result.
|
||||
|
||||
[HttpGet("proof")]
|
||||
public async Task<IActionResult> GetProof([FromQuery] DateOnly date)
|
||||
{
|
||||
var result = await _sessions.OpenProofForDateAsync(date);
|
||||
if (result is null) return NoContent(); // no session/proof yet — client merges nothing
|
||||
return File(result.Value.stream, result.Value.contentType);
|
||||
}
|
||||
|
||||
[HttpPost("proof")]
|
||||
[RequestSizeLimit(52_428_800)] // 50 MB — a merged multi-image PDF is larger than one receipt
|
||||
public async Task<IActionResult> UploadProof([FromForm] DateOnly date, IFormFile file)
|
||||
{
|
||||
if (file is null || file.Length == 0) return BadRequest(new { message = "No file." });
|
||||
if (file.ContentType != "application/pdf") return BadRequest(new { message = "Proof must be a PDF." });
|
||||
await using var stream = file.OpenReadStream();
|
||||
await _sessions.SaveProofForDateAsync(date, stream, file.FileName);
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,5 +10,6 @@ public class OfferingEntrySummaryDto
|
||||
public string? Status { get; set; } // null when no session yet
|
||||
public decimal SystemTotal { get; set; }
|
||||
public int LineCount { get; set; }
|
||||
public bool HasProof { get; set; } // a merged paper-proof PDF is attached to this session
|
||||
public List<OfferingGivingLineDto> Lines { get; set; } = [];
|
||||
}
|
||||
|
||||
@@ -21,4 +21,8 @@ public interface IOfferingSessionService
|
||||
Task SaveProofAsync(int id, Stream content, string fileName);
|
||||
Task<(Stream stream, string contentType)?> OpenProofAsync(int id);
|
||||
Task DeleteProofAsync(int id);
|
||||
|
||||
// Date-keyed variants for the anonymous mobile page (knows the date, not the id).
|
||||
Task SaveProofForDateAsync(DateOnly date, Stream content, string fileName);
|
||||
Task<(Stream stream, string contentType)?> OpenProofForDateAsync(DateOnly date);
|
||||
}
|
||||
|
||||
@@ -183,6 +183,7 @@ public class OfferingSessionService : IOfferingSessionService
|
||||
Status = session.Status,
|
||||
SystemTotal = session.SystemTotal,
|
||||
LineCount = lines.Count,
|
||||
HasProof = session.ProofPdfPath != null,
|
||||
Lines = await BuildLineDtosAsync(lines),
|
||||
};
|
||||
}
|
||||
@@ -339,6 +340,39 @@ public class OfferingSessionService : IOfferingSessionService
|
||||
await _db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
// ── Date-keyed proof (anonymous mobile page knows the date, not the id) ──
|
||||
|
||||
// Attach a proof PDF to the date's session, creating a Draft session if none
|
||||
// exists yet (the volunteer may snap the count sheet before any line is keyed).
|
||||
public async Task SaveProofForDateAsync(DateOnly date, Stream content, string fileName)
|
||||
{
|
||||
var session = await _db.OfferingSessions.FirstOrDefaultAsync(s => s.SessionDate == date);
|
||||
if (session is null)
|
||||
{
|
||||
session = new OfferingSession { SessionDate = date, Status = "Draft" };
|
||||
_db.OfferingSessions.Add(session);
|
||||
try
|
||||
{
|
||||
await _db.SaveChangesAsync();
|
||||
}
|
||||
catch (DbUpdateException)
|
||||
{
|
||||
// Lost the create race — another phone inserted today's session first.
|
||||
_db.ChangeTracker.Clear();
|
||||
session = await _db.OfferingSessions.FirstAsync(s => s.SessionDate == date);
|
||||
}
|
||||
}
|
||||
await SaveProofAsync(session.Id, content, fileName);
|
||||
}
|
||||
|
||||
public async Task<(Stream stream, string contentType)?> OpenProofForDateAsync(DateOnly date)
|
||||
{
|
||||
var session = await _db.OfferingSessions.AsNoTracking()
|
||||
.FirstOrDefaultAsync(s => s.SessionDate == date);
|
||||
if (session is null) return null;
|
||||
return await OpenProofAsync(session.Id);
|
||||
}
|
||||
|
||||
private static Giving MapLine(OfferingGivingLineRequest line, DateOnly sessionDate) => new()
|
||||
{
|
||||
MemberId = line.IsAnonymous ? null : line.MemberId,
|
||||
|
||||
Reference in New Issue
Block a user