Files
MoneyMap/MoneyMap/Pages/Receipts.cshtml.cs
AJ 8eb07c43e0 Add Receipts page for managing unmapped receipts
Added functionality to upload and manage receipts without initially
associating them to a transaction, with the ability to map them later.

Changes:
- Modified Receipt model to make TransactionId nullable
- Updated ReceiptManager service with new methods:
  - UploadUnmappedReceiptAsync: Upload receipts without a transaction
  - MapReceiptToTransactionAsync: Map an existing receipt to a transaction
- Created Receipts page (Receipts.cshtml) with:
  - Upload form for new receipts
  - List view of all receipts (mapped and unmapped)
  - Status badges (Mapped/Unmapped)
  - Map to Transaction modal dialog
  - Delete receipt functionality
- Added "Receipts" link to navigation menu
- Fixed Transactions page receipt count query for nullable TransactionId
- Created migration: MakeReceiptTransactionIdNullable

This enables workflows where receipts are uploaded first and matched
to transactions later, useful for batch receipt processing.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-12 11:28:33 -04:00

143 lines
4.4 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 ReceiptsModel : PageModel
{
private readonly MoneyMapContext _db;
private readonly IReceiptManager _receiptManager;
public ReceiptsModel(MoneyMapContext db, IReceiptManager receiptManager)
{
_db = db;
_receiptManager = receiptManager;
}
public List<ReceiptRow> Receipts { get; set; } = new();
[BindProperty]
public IFormFile? UploadFile { get; set; }
[TempData]
public string? Message { get; set; }
[TempData]
public bool IsSuccess { get; set; }
public async Task OnGetAsync()
{
await LoadReceiptsAsync();
}
public async Task<IActionResult> OnPostUploadAsync()
{
if (UploadFile == null)
{
Message = "Please select a file to upload.";
IsSuccess = false;
await LoadReceiptsAsync();
return Page();
}
var result = await _receiptManager.UploadUnmappedReceiptAsync(UploadFile);
if (result.IsSuccess)
{
Message = "Receipt uploaded successfully!";
IsSuccess = true;
return RedirectToPage();
}
else
{
Message = result.ErrorMessage ?? "Failed to upload receipt.";
IsSuccess = false;
await LoadReceiptsAsync();
return Page();
}
}
public async Task<IActionResult> OnPostDeleteAsync(long receiptId)
{
var success = await _receiptManager.DeleteReceiptAsync(receiptId);
if (success)
{
Message = "Receipt deleted successfully.";
IsSuccess = true;
}
else
{
Message = "Failed to delete receipt.";
IsSuccess = false;
}
return RedirectToPage();
}
public async Task<IActionResult> OnPostMapToTransactionAsync(long receiptId, long transactionId)
{
var success = await _receiptManager.MapReceiptToTransactionAsync(receiptId, transactionId);
if (success)
{
Message = "Receipt mapped to transaction successfully.";
IsSuccess = true;
}
else
{
Message = "Failed to map receipt to transaction.";
IsSuccess = false;
}
return RedirectToPage();
}
private async Task LoadReceiptsAsync()
{
var receipts = await _db.Receipts
.Include(r => r.Transaction)
.OrderByDescending(r => r.UploadedAtUtc)
.ToListAsync();
Receipts = receipts.Select(r => new ReceiptRow
{
Id = r.Id,
FileName = r.FileName,
ContentType = r.ContentType,
FileSizeBytes = r.FileSizeBytes,
UploadedAtUtc = r.UploadedAtUtc,
TransactionId = r.TransactionId,
TransactionName = r.Transaction?.Name,
TransactionDate = r.Transaction?.Date,
TransactionAmount = r.Transaction?.Amount,
Merchant = r.Merchant,
ReceiptDate = r.ReceiptDate,
Total = r.Total,
StoragePath = r.StoragePath
}).ToList();
}
public class ReceiptRow
{
public long Id { get; set; }
public string FileName { get; set; } = "";
public string ContentType { get; set; } = "";
public long FileSizeBytes { get; set; }
public DateTime UploadedAtUtc { get; set; }
public long? TransactionId { get; set; }
public string? TransactionName { get; set; }
public DateTime? TransactionDate { get; set; }
public decimal? TransactionAmount { get; set; }
public string? Merchant { get; set; }
public DateTime? ReceiptDate { get; set; }
public decimal? Total { get; set; }
public string StoragePath { get; set; } = "";
}
}
}