Spaces:
Sleeping
Sleeping
Yuyuqt
feat: add user management, book management, categories management, membership management, and borrowing management
87c9973 | using Database.Models; | |
| using Microsoft.EntityFrameworkCore; | |
| namespace LibraryManagement.Backend.Features.Borrowings | |
| { | |
| public interface IBorrowingService | |
| { | |
| Task<BorrowingDto> BorrowBookAsync(int userId, int bookId); | |
| Task<BorrowingDto> ReturnBookAsync(int borrowingId); | |
| Task<IEnumerable<BorrowingDto>> GetUserBorrowingsAsync(int userId); | |
| Task<IEnumerable<BorrowingDto>> GetAllBorrowingsAsync(); | |
| } | |
| public class BorrowingService : IBorrowingService | |
| { | |
| private readonly LibraryManagementContext _context; | |
| private const decimal FinePerDay = 500; | |
| public BorrowingService(LibraryManagementContext context) | |
| { | |
| _context = context; | |
| } | |
| public async Task<BorrowingDto> BorrowBookAsync(int userId, int bookId) | |
| { | |
| // 1. Check for Active Membership | |
| var subscription = await _context.UserSubscriptions | |
| .Include(s => s.Membership) | |
| .Where(s => s.UserId == userId && s.IsActive && s.ExpiryDate > DateTime.UtcNow) | |
| .FirstOrDefaultAsync(); | |
| if (subscription == null) | |
| { | |
| throw new Exception("Active membership required to borrow books."); | |
| } | |
| // 2. Check for Overdue Books or Unpaid Fines | |
| var hasBlockers = await _context.Borrowings | |
| .AnyAsync(b => b.UserId == userId && | |
| ((b.Status == "Borrowed" && b.DueDate < DateTime.UtcNow) || (b.FineAmount > 0 && !b.IsFinePaid))); | |
| if (hasBlockers) | |
| { | |
| throw new Exception("Borrowing blocked: You have overdue books or unpaid fines."); | |
| } | |
| // 3. Check MaxBooks limit | |
| var activeBorrowCount = await _context.Borrowings | |
| .CountAsync(b => b.UserId == userId && b.Status == "Borrowed"); | |
| if (activeBorrowCount >= subscription.Membership.MaxBooks) | |
| { | |
| throw new Exception($"Borrowing limit reached: You can only borrow {subscription.Membership.MaxBooks} books at a time."); | |
| } | |
| // 4. Check Book Availability | |
| var book = await _context.Books.FindAsync(bookId); | |
| if (book == null || !book.IsActive || book.AvailableCopies <= 0) | |
| { | |
| throw new Exception("Book is currently unavailable."); | |
| } | |
| // 5. Create Borrowing Record | |
| var borrowing = new Borrowing | |
| { | |
| UserId = userId, | |
| BookId = bookId, | |
| BorrowDate = DateTime.UtcNow, | |
| DueDate = DateTime.UtcNow.AddDays(subscription.Membership.BorrowingDays), | |
| Status = "Borrowed", | |
| FineAmount = 0, | |
| IsFinePaid = false | |
| }; | |
| // 6. Update Book Stock | |
| book.AvailableCopies--; | |
| if (book.AvailableCopies == 0) | |
| { | |
| book.Status = "Out Of Stock"; | |
| } | |
| _context.Borrowings.Add(borrowing); | |
| await _context.SaveChangesAsync(); | |
| // Load relations for mapping | |
| await _context.Entry(borrowing).Reference(b => b.Book).LoadAsync(); | |
| await _context.Entry(borrowing).Reference(b => b.User).LoadAsync(); | |
| return MapToDto(borrowing); | |
| } | |
| public async Task<BorrowingDto> ReturnBookAsync(int borrowingId) | |
| { | |
| var borrowing = await _context.Borrowings | |
| .Include(b => b.Book) | |
| .Include(b => b.User) | |
| .FirstOrDefaultAsync(b => b.Id == borrowingId); | |
| if (borrowing == null) throw new Exception("Borrowing record not found."); | |
| if (borrowing.Status == "Returned") throw new Exception("Book has already been returned."); | |
| var now = DateTime.UtcNow; | |
| borrowing.ReturnDate = now; | |
| borrowing.Status = "Returned"; | |
| // Calculate Fine | |
| if (now > borrowing.DueDate) | |
| { | |
| var overdueDays = (now.Date - borrowing.DueDate.Date).Days; | |
| if (overdueDays > 0) | |
| { | |
| borrowing.FineAmount = overdueDays * FinePerDay; | |
| } | |
| } | |
| // Update Book Stock | |
| var book = borrowing.Book; | |
| book.AvailableCopies++; | |
| book.Status = "Available"; | |
| await _context.SaveChangesAsync(); | |
| return MapToDto(borrowing); | |
| } | |
| public async Task<IEnumerable<BorrowingDto>> GetUserBorrowingsAsync(int userId) | |
| { | |
| var borrowings = await _context.Borrowings | |
| .Include(b => b.Book) | |
| .Include(b => b.User) | |
| .Where(b => b.UserId == userId) | |
| .OrderByDescending(b => b.BorrowDate) | |
| .ToListAsync(); | |
| return borrowings.Select(MapToDto); | |
| } | |
| public async Task<IEnumerable<BorrowingDto>> GetAllBorrowingsAsync() | |
| { | |
| var borrowings = await _context.Borrowings | |
| .Include(b => b.Book) | |
| .Include(b => b.User) | |
| .OrderByDescending(b => b.BorrowDate) | |
| .ToListAsync(); | |
| return borrowings.Select(MapToDto); | |
| } | |
| private static BorrowingDto MapToDto(Borrowing b) | |
| { | |
| return new BorrowingDto | |
| { | |
| Id = b.Id, | |
| UserId = b.UserId, | |
| UserEmail = b.User?.Email ?? "Unknown", | |
| BookId = b.BookId, | |
| BookTitle = b.Book?.Title ?? "Unknown", | |
| BorrowDate = b.BorrowDate, | |
| DueDate = b.DueDate, | |
| ReturnDate = b.ReturnDate, | |
| Status = b.Status, | |
| FineAmount = b.FineAmount, | |
| IsFinePaid = b.IsFinePaid | |
| }; | |
| } | |
| } | |
| } | |