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>
143 lines
4.4 KiB
C#
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; } = "";
|
|
}
|
|
}
|
|
}
|