using BudgetApp.Data.Entities; using BudgetApp.Data.Interfaces; using Microsoft.EntityFrameworkCore; namespace BudgetApp.Data.Repositories; public class SalaryRepository(ApplicationDbContext db) : ISalaryRepository { public async Task> 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(); 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 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.Monthly => from.AddMonths(1), RecurrenceType.Yearly => from.AddYears(1), _ => from.AddMonths(1) }; }