using Microsoft.EntityFrameworkCore; using BudgetApp.Data; namespace BudgetApp.Services; public class SalaryService(ApplicationDbContext db) { private readonly ApplicationDbContext _db = db; 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 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) }; }