Files
Budgeteer/BudgetApp.Data/Repositories/SalaryRepository.cs
T
2026-04-02 19:28:18 +01:00

139 lines
4.8 KiB
C#

using BudgetApp.Data.Entities;
using BudgetApp.Data.Interfaces;
using Microsoft.EntityFrameworkCore;
namespace BudgetApp.Data.Repositories;
public class SalaryRepository(ApplicationDbContext db) : ISalaryRepository
{
public async Task<List<Salary>> GetSalariesAsync(string userId)
{
return await db.Salaries
.Include(s => s.Account)
.Include(s => s.Category)
.Where(s => s.UserId == userId)
.OrderBy(s => s.Name)
.ToListAsync();
}
public async Task CreateSalaryAsync(Salary salary, DateTime windowEnd)
{
var salaryCategoryId = await db.Categories
.Where(c => c.UserId == salary.UserId && c.Name == "Salary")
.Select(c => (int?)c.Id)
.FirstOrDefaultAsync();
salary.CategoryId = salaryCategoryId;
db.Salaries.Add(salary);
await db.SaveChangesAsync();
await MaterializeSalaryTransactionsAsync(salary.UserId, windowEnd);
}
public async Task UpdateSalaryAsync(Salary salary, DateTime windowEnd, string userId)
{
var existing = await db.Salaries
.FirstOrDefaultAsync(s => s.Id == salary.Id && s.UserId == userId);
if (existing is null) return;
existing.Name = salary.Name;
existing.Amount = salary.Amount;
existing.AccountId = salary.AccountId;
existing.RecurrenceType = salary.RecurrenceType;
existing.StartDate = salary.StartDate;
await db.SaveChangesAsync();
// Delete future unpaid transactions and re-materialise with the updated settings
await db.Transactions
.Where(t => t.SalaryId == salary.Id && t.Date.Date > DateTime.Today && !t.IsPaid)
.ExecuteDeleteAsync();
await MaterializeSalaryTransactionsAsync(userId, windowEnd);
}
public async Task DeleteSalaryAsync(int id, string userId)
{
var exists = await db.Salaries.AnyAsync(s => s.Id == id && s.UserId == userId);
if (!exists) return;
await using var transaction = await db.Database.BeginTransactionAsync();
// Delete future unpaid transactions generated by this salary
await db.Transactions
.Where(t => t.SalaryId == id && t.Date.Date > DateTime.Today && !t.IsPaid)
.ExecuteDeleteAsync();
// Unlink past / paid transactions so they stay in history without the salary reference
await db.Transactions
.Where(t => t.SalaryId == id)
.ExecuteUpdateAsync(s => s.SetProperty(t => t.SalaryId, (int?)null));
await db.Salaries
.Where(s => s.Id == id && s.UserId == userId)
.ExecuteDeleteAsync();
await transaction.CommitAsync();
}
public async Task MaterializeSalaryTransactionsAsync(string userId, DateTime windowEnd)
{
var salaries = await db.Salaries
.Where(s => s.UserId == userId)
.ToListAsync();
var newTransactions = new List<Transaction>();
foreach (var salary in salaries)
{
var latestDate = await db.Transactions
.Where(t => t.SalaryId == salary.Id)
.OrderByDescending(t => t.Date)
.Select(t => (DateTime?)t.Date)
.FirstOrDefaultAsync();
var nextDate = latestDate.HasValue
? GetNextDate(latestDate.Value, salary)
: salary.StartDate.Date;
while (nextDate <= windowEnd)
{
newTransactions.Add(new Transaction
{
Description = salary.Name,
Amount = salary.Amount,
Date = nextDate,
Type = TransactionType.Income,
AccountId = salary.AccountId,
CategoryId = salary.CategoryId,
RecurrenceType = RecurrenceType.None,
SalaryId = salary.Id
});
nextDate = GetNextDate(nextDate, salary);
}
}
if (newTransactions.Count > 0)
{
db.Transactions.AddRange(newTransactions);
await db.SaveChangesAsync();
}
}
public async Task<DateTime?> GetNextPaydateAsync(int salaryId)
{
return await db.Transactions
.Where(t => t.SalaryId == salaryId && t.Date.Date >= DateTime.Today)
.OrderBy(t => t.Date)
.Select(t => (DateTime?)t.Date)
.FirstOrDefaultAsync();
}
private static DateTime GetNextDate(DateTime from, Salary salary) => salary.RecurrenceType switch
{
RecurrenceType.Weekly => from.AddDays(7),
RecurrenceType.FourWeekly => from.AddDays(28),
RecurrenceType.Monthly => from.AddMonths(1),
RecurrenceType.Yearly => from.AddYears(1),
_ => from.AddMonths(1)
};
}