Archived
160 lines
6.4 KiB
C#
160 lines
6.4 KiB
C#
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);
|
|
}
|
|
}
|