Files
MoneyMap/MoneyMap/Pages/ViewReceipt.cshtml.cs
AJ Isaacs 865195ad16 Feature: Save AI parsing notes with receipt
Store user-provided parsing notes in the database so they persist
across parsing attempts. Notes are displayed in Receipt Information
and pre-populated in the textarea for future parses.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-18 20:49:31 -05:00

128 lines
4.3 KiB
C#

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using MoneyMap.Data;
using MoneyMap.Models;
using MoneyMap.Services;
namespace MoneyMap.Pages
{
public class ViewReceiptModel : PageModel
{
private readonly MoneyMapContext _db;
private readonly IReceiptManager _receiptManager;
private readonly IEnumerable<IReceiptParser> _parsers;
private readonly IConfiguration _config;
public ViewReceiptModel(
MoneyMapContext db,
IReceiptManager receiptManager,
IEnumerable<IReceiptParser> parsers,
IConfiguration config)
{
_db = db;
_receiptManager = receiptManager;
_parsers = parsers;
_config = config;
}
public Receipt? Receipt { get; set; }
public List<ReceiptLineItem> LineItems { get; set; } = new();
public List<ReceiptParseLog> ParseLogs { get; set; } = new();
public List<ParserOption> AvailableParsers { get; set; } = new();
public string ReceiptUrl { get; set; } = "";
public string SelectedModel => _config["AI:ReceiptParsingModel"] ?? "gpt-4o-mini";
[TempData]
public string? SuccessMessage { get; set; }
[TempData]
public string? ErrorMessage { get; set; }
[BindProperty]
public string? ParsingNotes { get; set; }
public async Task<IActionResult> OnGetAsync(long id)
{
Receipt = await _db.Receipts
.Include(r => r.Transaction)
.Include(r => r.LineItems)
.Include(r => r.ParseLogs.OrderByDescending(pl => pl.StartedAtUtc))
.FirstOrDefaultAsync(r => r.Id == id);
if (Receipt == null)
return NotFound();
LineItems = Receipt.LineItems?.OrderBy(li => li.LineNumber).ToList() ?? new();
ParseLogs = Receipt.ParseLogs?.OrderByDescending(pl => pl.StartedAtUtc).ToList() ?? new();
// Load saved parsing notes
ParsingNotes = Receipt.ParsingNotes;
// Get receipt URL for display - use handler parameter
ReceiptUrl = $"/ViewReceipt/{id}?handler=file";
// Get available parsers
AvailableParsers = _parsers.Select(p => new ParserOption
{
Name = p.GetType().Name.Replace("ReceiptParser", ""),
FullName = p.GetType().Name
}).ToList();
return Page();
}
public async Task<IActionResult> OnGetFileAsync(long id)
{
var receipt = await _receiptManager.GetReceiptAsync(id);
if (receipt == null)
return NotFound();
var filePath = _receiptManager.GetReceiptPhysicalPath(receipt);
if (!System.IO.File.Exists(filePath))
return NotFound("Receipt file not found on disk.");
var fileBytes = await System.IO.File.ReadAllBytesAsync(filePath);
// For PDFs, set content disposition to inline so they display in browser
if (receipt.ContentType == "application/pdf")
{
Response.Headers.Append("Content-Disposition", $"inline; filename=\"{receipt.FileName}\"");
return File(fileBytes, "application/pdf");
}
return File(fileBytes, receipt.ContentType);
}
public async Task<IActionResult> OnPostParseAsync(long id, string parser)
{
var selectedParser = _parsers.FirstOrDefault(p => p.GetType().Name == parser);
if (selectedParser == null)
{
ErrorMessage = "Parser not found.";
return RedirectToPage(new { id });
}
// Use the configured model from settings, pass user notes
var result = await selectedParser.ParseReceiptAsync(id, SelectedModel, ParsingNotes);
if (result.IsSuccess)
{
SuccessMessage = result.Message;
}
else
{
ErrorMessage = result.Message;
}
return RedirectToPage(new { id });
}
public class ParserOption
{
public string Name { get; set; } = "";
public string FullName { get; set; } = "";
}
}
}