Remove manual Transfers page and nav link (bank-only data preference)
This commit is contained in:
@@ -1,128 +0,0 @@
|
|||||||
@page
|
|
||||||
@model MoneyMap.Pages.CreateTransferModel
|
|
||||||
@{
|
|
||||||
ViewData["Title"] = "Create Transfer";
|
|
||||||
}
|
|
||||||
|
|
||||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
|
||||||
<h2>Create Transfer</h2>
|
|
||||||
<a asp-page="/Transactions" class="btn btn-outline-secondary">Back to Transactions</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@if (!string.IsNullOrEmpty(Model.SuccessMessage))
|
|
||||||
{
|
|
||||||
<div class="alert alert-success alert-dismissible fade show" role="alert">
|
|
||||||
@Model.SuccessMessage
|
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
@if (!string.IsNullOrEmpty(Model.ErrorMessage))
|
|
||||||
{
|
|
||||||
<div class="alert alert-danger alert-dismissible fade show" role="alert">
|
|
||||||
@Model.ErrorMessage
|
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<div class="row justify-content-center">
|
|
||||||
<div class="col-lg-8">
|
|
||||||
<div class="card shadow-sm">
|
|
||||||
<div class="card-header">
|
|
||||||
<strong>Transfer Details</strong>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<div class="alert alert-info mb-4">
|
|
||||||
<strong>Note:</strong> This will create two matching transactions - a debit from the source account and a credit to the destination account.
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<form method="post">
|
|
||||||
<div class="row mb-3">
|
|
||||||
<div class="col-md-6">
|
|
||||||
<label asp-for="FromAccountId" class="form-label fw-bold">From Account</label>
|
|
||||||
<select asp-for="FromAccountId" class="form-select" required>
|
|
||||||
<option value="">-- Select source account --</option>
|
|
||||||
@foreach (var account in Model.Accounts)
|
|
||||||
{
|
|
||||||
<option value="@account.Id">@account.DisplayLabel</option>
|
|
||||||
}
|
|
||||||
</select>
|
|
||||||
<span asp-validation-for="FromAccountId" class="text-danger"></span>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6">
|
|
||||||
<label asp-for="ToAccountId" class="form-label fw-bold">To Account</label>
|
|
||||||
<select asp-for="ToAccountId" class="form-select" required>
|
|
||||||
<option value="">-- Select destination account --</option>
|
|
||||||
@foreach (var account in Model.Accounts)
|
|
||||||
{
|
|
||||||
<option value="@account.Id">@account.DisplayLabel</option>
|
|
||||||
}
|
|
||||||
</select>
|
|
||||||
<span asp-validation-for="ToAccountId" class="text-danger"></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row mb-3">
|
|
||||||
<div class="col-md-6">
|
|
||||||
<label asp-for="Date" class="form-label fw-bold">Date</label>
|
|
||||||
<input asp-for="Date" type="date" class="form-control" required />
|
|
||||||
<span asp-validation-for="Date" class="text-danger"></span>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6">
|
|
||||||
<label asp-for="Amount" class="form-label fw-bold">Amount</label>
|
|
||||||
<input asp-for="Amount" type="number" step="0.01" min="0.01" class="form-control" placeholder="0.00" required />
|
|
||||||
<span asp-validation-for="Amount" class="text-danger"></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-3">
|
|
||||||
<label asp-for="Memo" class="form-label fw-bold">Memo (optional)</label>
|
|
||||||
<input asp-for="Memo" type="text" class="form-control" placeholder="e.g., 'Monthly savings transfer'" />
|
|
||||||
<div class="form-text">Optional note to help you identify this transfer</div>
|
|
||||||
<span asp-validation-for="Memo" class="text-danger"></span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-3">
|
|
||||||
<label asp-for="Category" class="form-label fw-bold">Category (optional)</label>
|
|
||||||
<input asp-for="Category" type="text" class="form-control" placeholder="Transfer" list="categoryList" />
|
|
||||||
<div class="form-text">Defaults to "Transfer" if left blank</div>
|
|
||||||
<span asp-validation-for="Category" class="text-danger"></span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Category autocomplete -->
|
|
||||||
<datalist id="categoryList">
|
|
||||||
<option value="Transfer">Transfer</option>
|
|
||||||
@foreach (var cat in Model.AvailableCategories)
|
|
||||||
{
|
|
||||||
<option value="@cat">@cat</option>
|
|
||||||
}
|
|
||||||
</datalist>
|
|
||||||
|
|
||||||
<div class="d-flex gap-2">
|
|
||||||
<button type="submit" class="btn btn-success btn-lg">Create Transfer</button>
|
|
||||||
<a asp-page="/Transactions" class="btn btn-secondary btn-lg">Cancel</a>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card shadow-sm mt-3">
|
|
||||||
<div class="card-header">
|
|
||||||
<strong>How Transfers Work</strong>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<ul class="mb-0">
|
|
||||||
<li>Creates two linked transactions to maintain balance accuracy</li>
|
|
||||||
<li>Source account gets a <strong class="text-danger">debit</strong> (money out)</li>
|
|
||||||
<li>Destination account gets a <strong class="text-success">credit</strong> (money in)</li>
|
|
||||||
<li>Both transactions are linked and can be identified as transfers</li>
|
|
||||||
<li>Transfers can be filtered out when viewing spending reports</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@section Scripts {
|
|
||||||
<partial name="_ValidationScriptsPartial" />
|
|
||||||
}
|
|
||||||
@@ -1,147 +0,0 @@
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using MoneyMap.Data;
|
|
||||||
using MoneyMap.Models;
|
|
||||||
using System.ComponentModel.DataAnnotations;
|
|
||||||
|
|
||||||
namespace MoneyMap.Pages
|
|
||||||
{
|
|
||||||
public class CreateTransferModel : PageModel
|
|
||||||
{
|
|
||||||
private readonly MoneyMapContext _db;
|
|
||||||
|
|
||||||
public CreateTransferModel(MoneyMapContext db)
|
|
||||||
{
|
|
||||||
_db = db;
|
|
||||||
}
|
|
||||||
|
|
||||||
[BindProperty]
|
|
||||||
[Required(ErrorMessage = "Please select a source account")]
|
|
||||||
public int FromAccountId { get; set; }
|
|
||||||
|
|
||||||
[BindProperty]
|
|
||||||
[Required(ErrorMessage = "Please select a destination account")]
|
|
||||||
public int ToAccountId { get; set; }
|
|
||||||
|
|
||||||
[BindProperty]
|
|
||||||
[Required]
|
|
||||||
public DateTime Date { get; set; } = DateTime.Today;
|
|
||||||
|
|
||||||
[BindProperty]
|
|
||||||
[Required]
|
|
||||||
[Range(0.01, double.MaxValue, ErrorMessage = "Amount must be greater than 0")]
|
|
||||||
public decimal Amount { get; set; }
|
|
||||||
|
|
||||||
[BindProperty]
|
|
||||||
[MaxLength(500)]
|
|
||||||
public string Memo { get; set; } = string.Empty;
|
|
||||||
|
|
||||||
[BindProperty]
|
|
||||||
[MaxLength(100)]
|
|
||||||
public string Category { get; set; } = string.Empty;
|
|
||||||
|
|
||||||
public List<Account> Accounts { get; set; } = new();
|
|
||||||
public List<string> AvailableCategories { get; set; } = new();
|
|
||||||
|
|
||||||
[TempData]
|
|
||||||
public string? SuccessMessage { get; set; }
|
|
||||||
|
|
||||||
[TempData]
|
|
||||||
public string? ErrorMessage { get; set; }
|
|
||||||
|
|
||||||
public async Task<IActionResult> OnGetAsync()
|
|
||||||
{
|
|
||||||
await LoadDataAsync();
|
|
||||||
return Page();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IActionResult> OnPostAsync()
|
|
||||||
{
|
|
||||||
// Custom validation
|
|
||||||
if (FromAccountId == ToAccountId)
|
|
||||||
{
|
|
||||||
ModelState.AddModelError(string.Empty, "Source and destination accounts must be different");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ModelState.IsValid)
|
|
||||||
{
|
|
||||||
await LoadDataAsync();
|
|
||||||
return Page();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify both accounts exist
|
|
||||||
var fromAccount = await _db.Accounts.FindAsync(FromAccountId);
|
|
||||||
var toAccount = await _db.Accounts.FindAsync(ToAccountId);
|
|
||||||
|
|
||||||
if (fromAccount == null || toAccount == null)
|
|
||||||
{
|
|
||||||
ErrorMessage = "One or both accounts not found";
|
|
||||||
await LoadDataAsync();
|
|
||||||
return Page();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use "Transfer" as default category if not specified
|
|
||||||
var transferCategory = string.IsNullOrWhiteSpace(Category) ? "Transfer" : Category.Trim();
|
|
||||||
var transferMemo = string.IsNullOrWhiteSpace(Memo)
|
|
||||||
? $"Transfer to {toAccount.DisplayLabel}"
|
|
||||||
: Memo.Trim();
|
|
||||||
|
|
||||||
// Create the debit transaction (source account - money out)
|
|
||||||
var debitTransaction = new Transaction
|
|
||||||
{
|
|
||||||
Date = Date,
|
|
||||||
Name = $"Transfer to {toAccount.DisplayLabel}",
|
|
||||||
Memo = transferMemo,
|
|
||||||
Amount = -Amount, // Negative for debit
|
|
||||||
Category = transferCategory,
|
|
||||||
AccountId = FromAccountId,
|
|
||||||
TransferToAccountId = ToAccountId,
|
|
||||||
CardId = null // Transfers don't use cards
|
|
||||||
};
|
|
||||||
|
|
||||||
// Create the credit transaction (destination account - money in)
|
|
||||||
var creditTransaction = new Transaction
|
|
||||||
{
|
|
||||||
Date = Date,
|
|
||||||
Name = $"Transfer from {fromAccount.DisplayLabel}",
|
|
||||||
Memo = transferMemo,
|
|
||||||
Amount = Amount, // Positive for credit
|
|
||||||
Category = transferCategory,
|
|
||||||
AccountId = ToAccountId,
|
|
||||||
TransferToAccountId = FromAccountId, // Links back to source
|
|
||||||
CardId = null
|
|
||||||
};
|
|
||||||
|
|
||||||
_db.Transactions.Add(debitTransaction);
|
|
||||||
_db.Transactions.Add(creditTransaction);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await _db.SaveChangesAsync();
|
|
||||||
SuccessMessage = $"Transfer of {Amount:C} from {fromAccount.DisplayLabel} to {toAccount.DisplayLabel} created successfully!";
|
|
||||||
return RedirectToPage("/Transactions");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
ErrorMessage = $"Failed to create transfer: {ex.Message}";
|
|
||||||
await LoadDataAsync();
|
|
||||||
return Page();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task LoadDataAsync()
|
|
||||||
{
|
|
||||||
// Load accounts and order in memory by computed property
|
|
||||||
var accounts = await _db.Accounts.ToListAsync();
|
|
||||||
Accounts = accounts.OrderBy(a => a.DisplayLabel).ToList();
|
|
||||||
|
|
||||||
AvailableCategories = await _db.Transactions
|
|
||||||
.Select(t => t.Category ?? "")
|
|
||||||
.Where(c => !string.IsNullOrWhiteSpace(c))
|
|
||||||
.Distinct()
|
|
||||||
.OrderBy(c => c)
|
|
||||||
.ToListAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -41,7 +41,7 @@
|
|||||||
<a class="nav-link text-dark" asp-page="/Recategorize">Recategorize</a>
|
<a class="nav-link text-dark" asp-page="/Recategorize">Recategorize</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link text-dark" asp-page="/CreateTransfer">Transfer</a>
|
<!-- Transfer page removed by request -->
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user