Project to GitHub

This commit is contained in:
2026-06-24 08:33:23 +02:00
commit ecd89a68f8
121 changed files with 6481 additions and 0 deletions
@@ -0,0 +1,159 @@
using Bsevita.Library.Api.Data.Generated;
using Bsevita.Library.Models.Common;
using Bsevita.Library.Models.Loans;
using Bsevita.Library.Models.Reports;
using Microsoft.EntityFrameworkCore;
namespace Bsevita.Library.Api.Services;
public sealed class ReportService(LibraryDbContext dbContext, TimeProvider timeProvider)
{
public async Task<ReportsDto> GetReportsAsync(CancellationToken cancellationToken)
{
var activeLoans = await GetActiveLoansAsync(0, LibraryRules.DefaultPageSize, cancellationToken);
var overdueLoans = await GetOverdueAsync(0, LibraryRules.DefaultPageSize, cancellationToken);
return new ReportsDto(
activeLoans,
overdueLoans,
await GetActiveStudentsAsync(0, LibraryRules.DefaultPageSize, cancellationToken),
await GetStatisticsAsync(cancellationToken));
}
public async Task<IReadOnlyList<LoanDto>> GetActiveLoansAsync(
int skip,
int take,
CancellationToken cancellationToken)
{
var now = timeProvider.GetUtcNow();
var query = dbContext.Loans.AsNoTracking()
.Where(loan => loan.ReturnedAt == null)
.OrderBy(loan => loan.DueAt)
.Skip(Math.Max(0, skip))
.Take(Math.Clamp(take, 1, LibraryRules.MaxPageSize));
var loans = await query
.Select(loan => new
{
loan.LoanId,
loan.StudentId,
StudentName = loan.Student.FirstName + " " + loan.Student.LastName,
loan.Student.CardNumber,
loan.BookId,
BookTitle = loan.Book.Title,
loan.Book.BookNumber,
loan.LoanedAt,
loan.DueAt,
loan.ReturnedAt
})
.ToListAsync(cancellationToken);
return loans.Select(loan => ToLoanDto(
loan.LoanId,
loan.StudentId,
loan.StudentName,
loan.CardNumber,
loan.BookId,
loan.BookTitle,
loan.BookNumber,
loan.LoanedAt,
loan.DueAt,
loan.ReturnedAt,
now)).ToList();
}
public Task<IReadOnlyList<LoanDto>> GetActiveLoansAsync(CancellationToken cancellationToken) =>
GetActiveLoansAsync(0, LibraryRules.DefaultPageSize, cancellationToken);
public async Task<IReadOnlyList<LoanDto>> GetOverdueAsync(
int skip,
int take,
CancellationToken cancellationToken)
{
var now = timeProvider.GetUtcNow();
var query = dbContext.Loans.AsNoTracking()
.Where(loan => loan.ReturnedAt == null && loan.DueAt < now)
.OrderBy(loan => loan.DueAt)
.Skip(Math.Max(0, skip))
.Take(Math.Clamp(take, 1, LibraryRules.MaxPageSize));
var loans = await query
.Select(loan => new
{
loan.LoanId,
loan.StudentId,
StudentName = loan.Student.FirstName + " " + loan.Student.LastName,
loan.Student.CardNumber,
loan.BookId,
BookTitle = loan.Book.Title,
loan.Book.BookNumber,
loan.LoanedAt,
loan.DueAt,
loan.ReturnedAt
})
.ToListAsync(cancellationToken);
return loans.Select(loan => ToLoanDto(
loan.LoanId,
loan.StudentId,
loan.StudentName,
loan.CardNumber,
loan.BookId,
loan.BookTitle,
loan.BookNumber,
loan.LoanedAt,
loan.DueAt,
loan.ReturnedAt,
now)).ToList();
}
public Task<IReadOnlyList<LoanDto>> GetOverdueAsync(CancellationToken cancellationToken) =>
GetOverdueAsync(0, LibraryRules.DefaultPageSize, cancellationToken);
public async Task<IReadOnlyList<ActiveStudentDto>> GetActiveStudentsAsync(
int skip,
int take,
CancellationToken cancellationToken) =>
await dbContext.Students.AsNoTracking()
.Where(student => student.IsActive && student.Loans.Any(loan => loan.ReturnedAt == null))
.OrderBy(student => student.LastName)
.ThenBy(student => student.FirstName)
.Skip(Math.Max(0, skip))
.Take(Math.Clamp(take, 1, LibraryRules.MaxPageSize))
.Select(student => new ActiveStudentDto(
student.StudentId,
student.FirstName + " " + student.LastName,
student.CardNumber,
student.ClassName,
student.Loans.Count(loan => loan.ReturnedAt == null)))
.ToListAsync(cancellationToken);
public Task<IReadOnlyList<ActiveStudentDto>> GetActiveStudentsAsync(CancellationToken cancellationToken) =>
GetActiveStudentsAsync(0, LibraryRules.DefaultPageSize, cancellationToken);
public async Task<LibraryStatisticsDto> GetStatisticsAsync(CancellationToken cancellationToken)
{
var now = timeProvider.GetUtcNow();
var totalBooks = await dbContext.Books.CountAsync(book => book.IsActive, cancellationToken);
var activeLoans = await dbContext.Loans.CountAsync(loan => loan.ReturnedAt == null, cancellationToken);
var availableBooks = await dbContext.Books.CountAsync(
book => book.IsActive && !book.Loans.Any(loan => loan.ReturnedAt == null),
cancellationToken);
var totalStudents = await dbContext.Students.CountAsync(student => student.IsActive, cancellationToken);
var overdueLoans = await dbContext.Loans.CountAsync(loan => loan.ReturnedAt == null && loan.DueAt < now, cancellationToken);
return new LibraryStatisticsDto(totalBooks, availableBooks, totalStudents, activeLoans, overdueLoans);
}
private static LoanDto ToLoanDto(
Guid Id,
Guid StudentId,
string StudentName,
string CardNumber,
Guid BookId,
string BookTitle,
string BookNumber,
DateTimeOffset LoanedAt,
DateTimeOffset DueAt,
DateTimeOffset? ReturnedAt,
DateTimeOffset now)
{
var isOverdue = ReturnedAt is null && DueAt < now;
var daysOverdue = isOverdue ? Math.Max(1, (int)Math.Floor((now - DueAt).TotalDays)) : 0;
return new LoanDto(Id, StudentId, StudentName, CardNumber, BookId, BookTitle, BookNumber, LoanedAt, DueAt, ReturnedAt, isOverdue, daysOverdue);
}
}