Initial commit: Purrse personal finance app
Self-hosted, plugin-extensible personal finance manager built with ASP.NET Core 9.0, Vue 3 + Vuetify 3, and PostgreSQL 17. Backend (8 .NET projects): - Core: 19 domain models, 6 enums, 14 DTOs, 12 service interfaces - Data: EF Core DbContext, 16 entity configurations, category seeder - API: 14 controllers, 15 services, JWT auth, SignalR, middleware - Plugins: Abstractions + OFX/CSV/QIF file parsers - Tests: 28 xUnit tests (fingerprinting, duplicate detection, parsers) Frontend (Vue 3 + Vuetify 3 + TypeScript): - 13 views, Pinia stores, Axios API services with JWT interceptors - Dashboard, accounts, transactions, categories, imports, and more Deployment: - Docker Compose (PostgreSQL 17 + .NET API + nginx frontend) - Auto-migration on startup Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
using Purrse.Core.Enums;
|
||||
|
||||
namespace Purrse.Core.Models;
|
||||
|
||||
public class Account
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public Guid UserId { get; set; }
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public AccountType Type { get; set; }
|
||||
public string? Institution { get; set; }
|
||||
public string? AccountNumber { get; set; }
|
||||
public decimal Balance { get; set; }
|
||||
public decimal? CreditLimit { get; set; }
|
||||
public decimal? InterestRate { get; set; }
|
||||
public bool IsActive { get; set; } = true;
|
||||
public bool IsClosed { get; set; }
|
||||
public string? Notes { get; set; }
|
||||
public int SortOrder { get; set; }
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
||||
|
||||
public User User { get; set; } = null!;
|
||||
public LoanDetail? LoanDetail { get; set; }
|
||||
public ICollection<Transaction> Transactions { get; set; } = new List<Transaction>();
|
||||
public ICollection<InvestmentHolding> InvestmentHoldings { get; set; } = new List<InvestmentHolding>();
|
||||
public ICollection<Reconciliation> Reconciliations { get; set; } = new List<Reconciliation>();
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
namespace Purrse.Core.Models;
|
||||
|
||||
public class AmortizationEntry
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public Guid LoanDetailId { get; set; }
|
||||
public int PaymentNumber { get; set; }
|
||||
public DateTime PaymentDate { get; set; }
|
||||
public decimal PaymentAmount { get; set; }
|
||||
public decimal PrincipalAmount { get; set; }
|
||||
public decimal InterestAmount { get; set; }
|
||||
public decimal? EscrowAmount { get; set; }
|
||||
public decimal RemainingBalance { get; set; }
|
||||
|
||||
public LoanDetail LoanDetail { get; set; } = null!;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace Purrse.Core.Models;
|
||||
|
||||
public class Budget
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public Guid UserId { get; set; }
|
||||
public int Year { get; set; }
|
||||
public int Month { get; set; }
|
||||
public string? Notes { get; set; }
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
|
||||
public User User { get; set; } = null!;
|
||||
public ICollection<BudgetItem> Items { get; set; } = new List<BudgetItem>();
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace Purrse.Core.Models;
|
||||
|
||||
public class BudgetItem
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public Guid BudgetId { get; set; }
|
||||
public Guid CategoryId { get; set; }
|
||||
public decimal BudgetedAmount { get; set; }
|
||||
|
||||
public Budget Budget { get; set; } = null!;
|
||||
public Category Category { get; set; } = null!;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using Purrse.Core.Enums;
|
||||
|
||||
namespace Purrse.Core.Models;
|
||||
|
||||
public class Category
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public Guid UserId { get; set; }
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public CategoryType Type { get; set; }
|
||||
public Guid? ParentId { get; set; }
|
||||
public int SortOrder { get; set; }
|
||||
public bool IsSystem { get; set; }
|
||||
public bool IsActive { get; set; } = true;
|
||||
|
||||
public User User { get; set; } = null!;
|
||||
public Category? Parent { get; set; }
|
||||
public ICollection<Category> Children { get; set; } = new List<Category>();
|
||||
public ICollection<Transaction> Transactions { get; set; } = new List<Transaction>();
|
||||
public ICollection<BudgetItem> BudgetItems { get; set; } = new List<BudgetItem>();
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using Purrse.Core.Enums;
|
||||
|
||||
namespace Purrse.Core.Models;
|
||||
|
||||
public class ImportBatch
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public Guid UserId { get; set; }
|
||||
public Guid AccountId { get; set; }
|
||||
public string FileName { get; set; } = string.Empty;
|
||||
public string FileType { get; set; } = string.Empty;
|
||||
public int TotalCount { get; set; }
|
||||
public int ImportedCount { get; set; }
|
||||
public int DuplicateCount { get; set; }
|
||||
public int SkippedCount { get; set; }
|
||||
public ImportStatus Status { get; set; } = ImportStatus.Pending;
|
||||
public DateTime ImportedAt { get; set; } = DateTime.UtcNow;
|
||||
|
||||
public User User { get; set; } = null!;
|
||||
public Account Account { get; set; } = null!;
|
||||
public ICollection<Transaction> Transactions { get; set; } = new List<Transaction>();
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace Purrse.Core.Models;
|
||||
|
||||
public class InvestmentHolding
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public Guid AccountId { get; set; }
|
||||
public Guid SecurityId { get; set; }
|
||||
public decimal Shares { get; set; }
|
||||
public decimal CostBasis { get; set; }
|
||||
public DateTime AsOfDate { get; set; }
|
||||
|
||||
public Account Account { get; set; } = null!;
|
||||
public Security Security { get; set; } = null!;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
namespace Purrse.Core.Models;
|
||||
|
||||
public class LoanDetail
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public Guid AccountId { get; set; }
|
||||
public decimal OriginalBalance { get; set; }
|
||||
public decimal InterestRate { get; set; }
|
||||
public int TermMonths { get; set; }
|
||||
public decimal MonthlyPayment { get; set; }
|
||||
public DateTime OriginationDate { get; set; }
|
||||
public DateTime MaturityDate { get; set; }
|
||||
public decimal? EscrowAmount { get; set; }
|
||||
public decimal? ExtraPayment { get; set; }
|
||||
|
||||
public Account Account { get; set; } = null!;
|
||||
public ICollection<AmortizationEntry> AmortizationEntries { get; set; } = new List<AmortizationEntry>();
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace Purrse.Core.Models;
|
||||
|
||||
public class Payee
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public Guid UserId { get; set; }
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public Guid? DefaultCategoryId { get; set; }
|
||||
public bool IsActive { get; set; } = true;
|
||||
|
||||
public User User { get; set; } = null!;
|
||||
public Category? DefaultCategory { get; set; }
|
||||
public ICollection<PayeeAlias> Aliases { get; set; } = new List<PayeeAlias>();
|
||||
public ICollection<Transaction> Transactions { get; set; } = new List<Transaction>();
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace Purrse.Core.Models;
|
||||
|
||||
public class PayeeAlias
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public Guid PayeeId { get; set; }
|
||||
public string Alias { get; set; } = string.Empty;
|
||||
|
||||
public Payee Payee { get; set; } = null!;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace Purrse.Core.Models;
|
||||
|
||||
public class PluginConfiguration
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public Guid PluginRegistrationId { get; set; }
|
||||
public string Key { get; set; } = string.Empty;
|
||||
public string Value { get; set; } = string.Empty;
|
||||
|
||||
public PluginRegistration PluginRegistration { get; set; } = null!;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace Purrse.Core.Models;
|
||||
|
||||
public class PluginRegistration
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public string PluginId { get; set; } = string.Empty;
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string Version { get; set; } = string.Empty;
|
||||
public string EntryAssembly { get; set; } = string.Empty;
|
||||
public string EntryType { get; set; } = string.Empty;
|
||||
public bool IsEnabled { get; set; } = true;
|
||||
public DateTime InstalledAt { get; set; } = DateTime.UtcNow;
|
||||
|
||||
public ICollection<PluginConfiguration> Configurations { get; set; } = new List<PluginConfiguration>();
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace Purrse.Core.Models;
|
||||
|
||||
public class Reconciliation
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public Guid AccountId { get; set; }
|
||||
public DateTime StatementDate { get; set; }
|
||||
public decimal StatementBalance { get; set; }
|
||||
public bool IsCompleted { get; set; }
|
||||
public DateTime? CompletedAt { get; set; }
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
|
||||
public Account Account { get; set; } = null!;
|
||||
public ICollection<Transaction> Transactions { get; set; } = new List<Transaction>();
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using Purrse.Core.Enums;
|
||||
|
||||
namespace Purrse.Core.Models;
|
||||
|
||||
public class ScheduledTransaction
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public Guid UserId { get; set; }
|
||||
public Guid AccountId { get; set; }
|
||||
public decimal Amount { get; set; }
|
||||
public string? PayeeName { get; set; }
|
||||
public Guid? PayeeId { get; set; }
|
||||
public Guid? CategoryId { get; set; }
|
||||
public string? Memo { get; set; }
|
||||
public TransactionType Type { get; set; }
|
||||
public Frequency Frequency { get; set; }
|
||||
public DateTime NextDueDate { get; set; }
|
||||
public DateTime? EndDate { get; set; }
|
||||
public bool AutoPost { get; set; }
|
||||
public int ReminderDaysBefore { get; set; } = 3;
|
||||
public bool IsActive { get; set; } = true;
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
|
||||
public User User { get; set; } = null!;
|
||||
public Account Account { get; set; } = null!;
|
||||
public Payee? Payee { get; set; }
|
||||
public Category? Category { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace Purrse.Core.Models;
|
||||
|
||||
public class Security
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public string Symbol { get; set; } = string.Empty;
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string? SecurityType { get; set; }
|
||||
|
||||
public ICollection<InvestmentHolding> Holdings { get; set; } = new List<InvestmentHolding>();
|
||||
public ICollection<SecurityPrice> Prices { get; set; } = new List<SecurityPrice>();
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace Purrse.Core.Models;
|
||||
|
||||
public class SecurityPrice
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public Guid SecurityId { get; set; }
|
||||
public DateTime Date { get; set; }
|
||||
public decimal Price { get; set; }
|
||||
|
||||
public Security Security { get; set; } = null!;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using Purrse.Core.Enums;
|
||||
|
||||
namespace Purrse.Core.Models;
|
||||
|
||||
public class Transaction
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public Guid AccountId { get; set; }
|
||||
public DateTime Date { get; set; }
|
||||
public decimal Amount { get; set; }
|
||||
public Guid? PayeeId { get; set; }
|
||||
public string? PayeeName { get; set; }
|
||||
public Guid? CategoryId { get; set; }
|
||||
public string? Memo { get; set; }
|
||||
public string? ReferenceNumber { get; set; }
|
||||
public string? CheckNumber { get; set; }
|
||||
public TransactionType Type { get; set; }
|
||||
public TransactionStatus Status { get; set; } = TransactionStatus.Uncleared;
|
||||
public string? FitId { get; set; }
|
||||
public string? Fingerprint { get; set; }
|
||||
public Guid? TransferTransactionId { get; set; }
|
||||
public Guid? ImportBatchId { get; set; }
|
||||
public Guid? ReconciliationId { get; set; }
|
||||
public bool IsVoid { get; set; }
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
||||
|
||||
public Account Account { get; set; } = null!;
|
||||
public Payee? Payee { get; set; }
|
||||
public Category? Category { get; set; }
|
||||
public Transaction? TransferTransaction { get; set; }
|
||||
public ImportBatch? ImportBatch { get; set; }
|
||||
public Reconciliation? Reconciliation { get; set; }
|
||||
public ICollection<TransactionSplit> Splits { get; set; } = new List<TransactionSplit>();
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace Purrse.Core.Models;
|
||||
|
||||
public class TransactionSplit
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public Guid TransactionId { get; set; }
|
||||
public Guid? CategoryId { get; set; }
|
||||
public decimal Amount { get; set; }
|
||||
public string? Memo { get; set; }
|
||||
|
||||
public Transaction Transaction { get; set; } = null!;
|
||||
public Category? Category { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace Purrse.Core.Models;
|
||||
|
||||
public class User
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public string Username { get; set; } = string.Empty;
|
||||
public string Email { get; set; } = string.Empty;
|
||||
public string PasswordHash { get; set; } = string.Empty;
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
public DateTime? LastLoginAt { get; set; }
|
||||
public string? RefreshToken { get; set; }
|
||||
public DateTime? RefreshTokenExpiresAt { get; set; }
|
||||
|
||||
public ICollection<Account> Accounts { get; set; } = new List<Account>();
|
||||
}
|
||||
Reference in New Issue
Block a user