117 lines
3.9 KiB
C#
117 lines
3.9 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 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.Monthly => from.AddMonths(1),
|
|
RecurrenceType.Yearly => from.AddYears(1),
|
|
_ => from.AddMonths(1)
|
|
};
|
|
}
|