first commit
This commit is contained in:
@@ -0,0 +1,161 @@
|
||||
using Fitness_core_BE.DTOs.Ledger;
|
||||
using Fitness_core_BE.Exceptions;
|
||||
using Fitness_core_BE.Helpers;
|
||||
using Fitness_core_BE.Infra.UoW;
|
||||
using Fitness_core_BE.Interfaces;
|
||||
using Fitness_core_BE.Interfaces.Services;
|
||||
using Fitness_core_BE.Models;
|
||||
|
||||
namespace Fitness_core_BE.Services
|
||||
{
|
||||
public class ProfitLossService : IProfitLossService
|
||||
{
|
||||
private readonly IProfitLossRepository _repository;
|
||||
private readonly UserRoleHelper _userRoleHelper;
|
||||
private readonly IUnitOfWork _uow;
|
||||
|
||||
public ProfitLossService(
|
||||
IProfitLossRepository repository,
|
||||
UserRoleHelper userRoleHelper,
|
||||
IUnitOfWork uow)
|
||||
{
|
||||
_repository = repository;
|
||||
_userRoleHelper = userRoleHelper;
|
||||
_uow = uow;
|
||||
}
|
||||
|
||||
public async Task<ProfitLossDto> GetProfitLossAsync(DateTime from, DateTime to, HttpContext httpContext)
|
||||
{
|
||||
var clientId = await GetClientIdAsync(httpContext);
|
||||
var (fromUtc, toUtc) = NormalizeRange(from, to);
|
||||
|
||||
var entries = await _repository.GetDerivedEntriesAsync(
|
||||
clientId, fromUtc, toUtc);
|
||||
|
||||
// Manual entries.
|
||||
var manualEntries = await GetManualEntriesRawAsync(clientId, fromUtc, toUtc);
|
||||
entries.AddRange(manualEntries.Select(ToDto));
|
||||
|
||||
var totalIncome = entries.Where(e => e.EntryType == "Income").Sum(e => e.Amount);
|
||||
var totalExpense = entries.Where(e => e.EntryType == "Expense").Sum(e => e.Amount);
|
||||
|
||||
return new ProfitLossDto
|
||||
{
|
||||
From = fromUtc,
|
||||
To = toUtc,
|
||||
TotalIncome = totalIncome,
|
||||
TotalExpense = totalExpense,
|
||||
NetProfit = totalIncome - totalExpense,
|
||||
IncomeByCategory = entries.Where(e => e.EntryType == "Income")
|
||||
.GroupBy(e => e.Category)
|
||||
.Select(g => new CategoryTotalDto { Category = g.Key, Amount = g.Sum(e => e.Amount) })
|
||||
.OrderByDescending(c => c.Amount)
|
||||
.ToList(),
|
||||
ExpenseByCategory = entries.Where(e => e.EntryType == "Expense")
|
||||
.GroupBy(e => e.Category)
|
||||
.Select(g => new CategoryTotalDto { Category = g.Key, Amount = g.Sum(e => e.Amount) })
|
||||
.OrderByDescending(c => c.Amount)
|
||||
.ToList(),
|
||||
Entries = entries.OrderByDescending(e => e.EntryDate).ToList()
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<List<LedgerEntryDto>> GetManualEntriesAsync(DateTime from, DateTime to, HttpContext httpContext)
|
||||
{
|
||||
var clientId = await GetClientIdAsync(httpContext);
|
||||
var (fromUtc, toUtc) = NormalizeRange(from, to);
|
||||
var entries = await GetManualEntriesRawAsync(clientId, fromUtc, toUtc);
|
||||
return entries.Select(ToDto).OrderByDescending(e => e.EntryDate).ToList();
|
||||
}
|
||||
|
||||
public async Task<LedgerEntryDto> AddManualEntryAsync(AddLedgerEntryRequest request, HttpContext httpContext)
|
||||
{
|
||||
var clientId = await GetClientIdAsync(httpContext);
|
||||
|
||||
if (request.EntryType != "Income" && request.EntryType != "Expense")
|
||||
throw new ArgumentException("entryType must be 'Income' or 'Expense'.");
|
||||
if (string.IsNullOrWhiteSpace(request.Category))
|
||||
throw new ArgumentException("category is required.");
|
||||
if (request.Amount <= 0)
|
||||
throw new ArgumentException("amount must be greater than zero.");
|
||||
|
||||
await _uow.BeginAsync();
|
||||
try
|
||||
{
|
||||
var entry = new LedgerEntry
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
FKClientId = clientId,
|
||||
EntryType = request.EntryType,
|
||||
Category = request.Category,
|
||||
Description = request.Description,
|
||||
Amount = request.Amount,
|
||||
EntryDate = request.EntryDate ?? DateTime.UtcNow,
|
||||
CreatedAt = DateTime.UtcNow
|
||||
};
|
||||
await _repository.AddManualEntryAsync(entry);
|
||||
await _uow.CommitAsync();
|
||||
|
||||
return ToDto(entry);
|
||||
}
|
||||
catch
|
||||
{
|
||||
await _uow.RollbackAsync();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task DeleteManualEntryAsync(Guid id, HttpContext httpContext)
|
||||
{
|
||||
var clientId = await GetClientIdAsync(httpContext);
|
||||
|
||||
var entry = await _repository.GetManualEntryByIdAsync(id)
|
||||
?? throw new NotFoundException("Ledger entry not found.");
|
||||
|
||||
if (entry.FKClientId != clientId)
|
||||
throw new ForbiddenException("This ledger entry does not belong to your gym.");
|
||||
|
||||
await _uow.BeginAsync();
|
||||
try
|
||||
{
|
||||
_repository.RemoveManualEntry(entry);
|
||||
await _uow.CommitAsync();
|
||||
}
|
||||
catch
|
||||
{
|
||||
await _uow.RollbackAsync();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<List<LedgerEntry>> GetManualEntriesRawAsync(Guid clientId, DateTime fromUtc, DateTime toUtc)
|
||||
{
|
||||
return await _repository.GetManualEntriesAsync(clientId, fromUtc, toUtc);
|
||||
}
|
||||
|
||||
private async Task<Guid> GetClientIdAsync(HttpContext httpContext)
|
||||
{
|
||||
var userId = DataExtractor.GetUserIdFromContext(httpContext);
|
||||
return await _userRoleHelper.GetClientId(userId);
|
||||
}
|
||||
|
||||
private static (DateTime From, DateTime To) NormalizeRange(DateTime from, DateTime to)
|
||||
{
|
||||
var fromUtc = DateTime.SpecifyKind(from.Date, DateTimeKind.Utc);
|
||||
// Treat `to` as inclusive of the whole day.
|
||||
var toUtc = DateTime.SpecifyKind(to.Date.AddDays(1), DateTimeKind.Utc);
|
||||
return (fromUtc, toUtc);
|
||||
}
|
||||
|
||||
private static LedgerEntryDto ToDto(LedgerEntry entry) => new()
|
||||
{
|
||||
Id = entry.Id,
|
||||
EntryType = entry.EntryType,
|
||||
Category = entry.Category,
|
||||
Description = entry.Description,
|
||||
Amount = entry.Amount,
|
||||
EntryDate = entry.EntryDate,
|
||||
IsManual = true
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user