- Add hidden fields for immutable transaction properties to preserve values during form submission - Make category field optional by removing validation for empty values - Simplify category input handling by removing duplicate hidden field - Clean up JavaScript by using proper element IDs instead of querySelector 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
281 lines
9.8 KiB
C#
281 lines
9.8 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 EditTransactionModel : PageModel
|
|
{
|
|
private readonly MoneyMapContext _db;
|
|
private readonly IReceiptManager _receiptManager;
|
|
private readonly IReceiptParser _receiptParser;
|
|
private readonly IReferenceDataService _referenceDataService;
|
|
private readonly IMerchantService _merchantService;
|
|
|
|
public EditTransactionModel(MoneyMapContext db, IReceiptManager receiptManager, IReceiptParser receiptParser, IReferenceDataService referenceDataService, IMerchantService merchantService)
|
|
{
|
|
_db = db;
|
|
_receiptManager = receiptManager;
|
|
_receiptParser = receiptParser;
|
|
_referenceDataService = referenceDataService;
|
|
_merchantService = merchantService;
|
|
}
|
|
|
|
[BindProperty]
|
|
public TransactionEditModel Transaction { get; set; } = new();
|
|
|
|
[BindProperty]
|
|
public IFormFile? ReceiptFile { get; set; }
|
|
|
|
[BindProperty]
|
|
public bool UseCustomCategory { get; set; }
|
|
|
|
[BindProperty]
|
|
public bool UseCustomMerchant { get; set; }
|
|
|
|
public List<string> AvailableCategories { get; set; } = new();
|
|
public List<Merchant> AvailableMerchants { get; set; } = new();
|
|
public List<ReceiptWithItems> Receipts { get; set; } = new();
|
|
|
|
[TempData]
|
|
public string? SuccessMessage { get; set; }
|
|
|
|
[TempData]
|
|
public string? ErrorMessage { get; set; }
|
|
|
|
public async Task<IActionResult> OnGetAsync(long id)
|
|
{
|
|
var transaction = await _db.Transactions
|
|
.Include(t => t.Card)
|
|
.ThenInclude(c => c!.Account)
|
|
.Include(t => t.Account)
|
|
.Include(t => t.TransferToAccount)
|
|
.Include(t => t.Merchant)
|
|
.Include(t => t.Receipts)
|
|
.ThenInclude(r => r.LineItems)
|
|
.FirstOrDefaultAsync(t => t.Id == id);
|
|
|
|
if (transaction == null)
|
|
return NotFound();
|
|
|
|
Transaction = new TransactionEditModel
|
|
{
|
|
Id = transaction.Id,
|
|
Date = transaction.Date,
|
|
Name = transaction.Name,
|
|
Memo = transaction.Memo,
|
|
Amount = transaction.Amount,
|
|
Category = transaction.Category ?? "",
|
|
MerchantId = transaction.MerchantId,
|
|
Notes = transaction.Notes ?? "",
|
|
CardLabel = transaction.PaymentMethodLabel,
|
|
AccountLabel = transaction.Card?.Account?.DisplayLabel ?? transaction.Account?.DisplayLabel ?? "None"
|
|
};
|
|
|
|
Receipts = transaction.Receipts?.Select(r => new ReceiptWithItems
|
|
{
|
|
Receipt = r,
|
|
LineItems = r.LineItems?.OrderBy(li => li.LineNumber).ToList() ?? new List<ReceiptLineItem>()
|
|
}).ToList() ?? new List<ReceiptWithItems>();
|
|
|
|
await LoadAvailableCategoriesAsync();
|
|
await LoadAvailableMerchantsAsync();
|
|
|
|
// Check if current category exists in list
|
|
UseCustomCategory = !string.IsNullOrWhiteSpace(Transaction.Category)
|
|
&& !AvailableCategories.Contains(Transaction.Category);
|
|
|
|
// Check if current merchant exists in list
|
|
UseCustomMerchant = Transaction.MerchantId.HasValue
|
|
&& !AvailableMerchants.Any(m => m.Id == Transaction.MerchantId.Value);
|
|
|
|
return Page();
|
|
}
|
|
|
|
public async Task<IActionResult> OnPostAsync()
|
|
{
|
|
// Remove Category from model state validation if it's empty (category is optional)
|
|
if (string.IsNullOrWhiteSpace(Transaction.Category))
|
|
{
|
|
ModelState.Remove("Transaction.Category");
|
|
}
|
|
|
|
// Remove Notes from model state validation if it's empty
|
|
if (string.IsNullOrWhiteSpace(Transaction.Notes))
|
|
{
|
|
ModelState.Remove("Transaction.Notes");
|
|
}
|
|
|
|
// Remove MerchantName from validation (it's only used when creating new merchant)
|
|
ModelState.Remove("Transaction.MerchantName");
|
|
|
|
if (!ModelState.IsValid)
|
|
{
|
|
await LoadDataAsync();
|
|
return Page();
|
|
}
|
|
|
|
var transaction = await _db.Transactions.FindAsync(Transaction.Id);
|
|
if (transaction == null)
|
|
return NotFound();
|
|
|
|
// Update category and notes
|
|
transaction.Category = string.IsNullOrWhiteSpace(Transaction.Category)
|
|
? ""
|
|
: Transaction.Category.Trim();
|
|
|
|
transaction.Notes = string.IsNullOrWhiteSpace(Transaction.Notes)
|
|
? ""
|
|
: Transaction.Notes.Trim();
|
|
|
|
// Update merchant
|
|
if (!string.IsNullOrWhiteSpace(Transaction.MerchantName))
|
|
{
|
|
// Create or get merchant if custom name was entered
|
|
var merchantId = await _merchantService.GetOrCreateIdAsync(Transaction.MerchantName);
|
|
transaction.MerchantId = merchantId;
|
|
}
|
|
else if (Transaction.MerchantId.HasValue && Transaction.MerchantId.Value > 0)
|
|
{
|
|
// Existing merchant was selected
|
|
transaction.MerchantId = Transaction.MerchantId.Value;
|
|
}
|
|
else
|
|
{
|
|
// No merchant selected
|
|
transaction.MerchantId = null;
|
|
}
|
|
|
|
await _db.SaveChangesAsync();
|
|
|
|
SuccessMessage = "Transaction updated successfully!";
|
|
return RedirectToPage(new { id = Transaction.Id });
|
|
}
|
|
|
|
public async Task<IActionResult> OnPostUploadReceiptAsync()
|
|
{
|
|
if (ReceiptFile == null)
|
|
{
|
|
ErrorMessage = "Please select a file to upload.";
|
|
await LoadDataAsync();
|
|
return Page();
|
|
}
|
|
|
|
var result = await _receiptManager.UploadReceiptAsync(Transaction.Id, ReceiptFile);
|
|
|
|
if (result.IsSuccess)
|
|
{
|
|
SuccessMessage = "Receipt uploaded successfully!";
|
|
}
|
|
else
|
|
{
|
|
ErrorMessage = result.ErrorMessage;
|
|
}
|
|
|
|
return RedirectToPage(new { id = Transaction.Id });
|
|
}
|
|
|
|
public async Task<IActionResult> OnPostUnmapReceiptAsync(long receiptId)
|
|
{
|
|
var receipt = await _db.Receipts.FindAsync(receiptId);
|
|
if (receipt != null)
|
|
{
|
|
receipt.TransactionId = null;
|
|
await _db.SaveChangesAsync();
|
|
SuccessMessage = "Receipt unmapped successfully! You can now find it on the Receipts page.";
|
|
}
|
|
else
|
|
{
|
|
ErrorMessage = "Receipt not found.";
|
|
}
|
|
|
|
return RedirectToPage(new { id = Transaction.Id });
|
|
}
|
|
|
|
public async Task<IActionResult> OnPostDeleteReceiptAsync(long receiptId)
|
|
{
|
|
var success = await _receiptManager.DeleteReceiptAsync(receiptId);
|
|
|
|
if (success)
|
|
{
|
|
SuccessMessage = "Receipt deleted permanently!";
|
|
}
|
|
else
|
|
{
|
|
ErrorMessage = "Failed to delete receipt.";
|
|
}
|
|
|
|
return RedirectToPage(new { id = Transaction.Id });
|
|
}
|
|
|
|
public async Task<IActionResult> OnPostParseReceiptAsync(long receiptId)
|
|
{
|
|
var result = await _receiptParser.ParseReceiptAsync(receiptId);
|
|
|
|
if (result.IsSuccess)
|
|
{
|
|
SuccessMessage = result.Message;
|
|
}
|
|
else
|
|
{
|
|
ErrorMessage = result.Message;
|
|
}
|
|
|
|
return RedirectToPage(new { id = Transaction.Id });
|
|
}
|
|
|
|
private async Task LoadDataAsync()
|
|
{
|
|
await LoadAvailableCategoriesAsync();
|
|
await LoadAvailableMerchantsAsync();
|
|
|
|
var transaction = await _db.Transactions
|
|
.Include(t => t.Receipts)
|
|
.ThenInclude(r => r.LineItems)
|
|
.FirstOrDefaultAsync(t => t.Id == Transaction.Id);
|
|
|
|
if (transaction != null)
|
|
{
|
|
Receipts = transaction.Receipts?.Select(r => new ReceiptWithItems
|
|
{
|
|
Receipt = r,
|
|
LineItems = r.LineItems?.OrderBy(li => li.LineNumber).ToList() ?? new List<ReceiptLineItem>()
|
|
}).ToList() ?? new List<ReceiptWithItems>();
|
|
}
|
|
}
|
|
|
|
private async Task LoadAvailableCategoriesAsync()
|
|
{
|
|
AvailableCategories = await _referenceDataService.GetAvailableCategoriesAsync();
|
|
}
|
|
|
|
private async Task LoadAvailableMerchantsAsync()
|
|
{
|
|
AvailableMerchants = await _referenceDataService.GetAvailableMerchantsAsync();
|
|
}
|
|
|
|
public class TransactionEditModel
|
|
{
|
|
public long Id { get; set; }
|
|
public DateTime Date { get; set; }
|
|
public string Name { get; set; } = "";
|
|
public string Memo { get; set; } = "";
|
|
public decimal Amount { get; set; }
|
|
public string Category { get; set; } = "";
|
|
public int? MerchantId { get; set; }
|
|
public string? MerchantName { get; set; } = "";
|
|
public string? Notes { get; set; } = "";
|
|
public string CardLabel { get; set; } = "";
|
|
public string AccountLabel { get; set; } = "";
|
|
}
|
|
|
|
public class ReceiptWithItems
|
|
{
|
|
public Receipt Receipt { get; set; } = null!;
|
|
public List<ReceiptLineItem> LineItems { get; set; } = new();
|
|
}
|
|
}
|
|
} |