Compare commits
3 Commits
57040bc656
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 60675b4581 | |||
| 715893fb1f | |||
| 8eb8cd4fbf |
@@ -0,0 +1,30 @@
|
||||
using LibraryApp.Domain.Entities;
|
||||
|
||||
namespace LibraryApp.Domain;
|
||||
|
||||
public class BookCountReport
|
||||
{
|
||||
public ShelfId ShelfId { get; }
|
||||
public DateTime CountedAt { get; }
|
||||
public IReadOnlyList<BookInstance> MissingInstances { get; }
|
||||
public int TotalExpected { get; }
|
||||
public int TotalFound { get; }
|
||||
|
||||
public BookCountReport(
|
||||
ShelfId shelfId,
|
||||
DateTime countedAt,
|
||||
IReadOnlyList<BookInstance> allInstancesOnShelf,
|
||||
IReadOnlyList<BookInstanceBarcode> scannedBarcodes)
|
||||
{
|
||||
ShelfId = shelfId;
|
||||
CountedAt = countedAt;
|
||||
TotalExpected = allInstancesOnShelf.Count;
|
||||
|
||||
var scannedSet = scannedBarcodes.Select(b => b.Barcode).ToHashSet();
|
||||
MissingInstances = allInstancesOnShelf
|
||||
.Where(i => !scannedSet.Contains(i.Barcode.Barcode))
|
||||
.ToList();
|
||||
|
||||
TotalFound = TotalExpected - MissingInstances.Count;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
namespace LibraryApp.Domain;
|
||||
|
||||
public class Class1
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace LibraryApp.Domain;
|
||||
|
||||
public record DbId
|
||||
{
|
||||
public string Id { get; init; }
|
||||
public bool BlankId => string.IsNullOrWhiteSpace(Id);
|
||||
public DbId(string id)
|
||||
{
|
||||
if(id is null)
|
||||
throw new ArgumentNullException(nameof(id));
|
||||
Id = id;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
namespace LibraryApp.Domain.Entities;
|
||||
|
||||
public class Book
|
||||
{
|
||||
public BookId Id { get; private set; }
|
||||
public BookTitle Title { get; private set; }
|
||||
public BookAuthor Author { get; private set; }
|
||||
|
||||
public Book(BookTitle title, BookAuthor author)
|
||||
: this(new BookId(string.Empty), title, author)
|
||||
{ }
|
||||
|
||||
public Book(BookId id, BookTitle title, BookAuthor author)
|
||||
{
|
||||
Id = id;
|
||||
Title = title;
|
||||
Author = author;
|
||||
}
|
||||
|
||||
public Book UpdateTitle(string newTitle)
|
||||
{
|
||||
Title = new (newTitle);
|
||||
return this;
|
||||
}
|
||||
public Book UpdateAuthor(string newAuthor)
|
||||
{
|
||||
Author = new (newAuthor);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public record BookId : DbId
|
||||
{
|
||||
public BookId(string id) : base(id) {}
|
||||
}
|
||||
|
||||
public record BookTitle
|
||||
{
|
||||
public string Title { get; init; }
|
||||
|
||||
public BookTitle(string title)
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(title))
|
||||
{
|
||||
throw new ArgumentException("Title cannot be empty.");
|
||||
}
|
||||
Title = title;
|
||||
}
|
||||
}
|
||||
|
||||
public record BookAuthor
|
||||
{
|
||||
public string Author { get; init; }
|
||||
|
||||
public BookAuthor(string author)
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(author))
|
||||
{
|
||||
throw new ArgumentException("Author cannot be empty.");
|
||||
}
|
||||
Author = author;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
namespace LibraryApp.Domain.Entities;
|
||||
|
||||
public enum BookInstanceStatus
|
||||
{
|
||||
OnShelf,
|
||||
Lent,
|
||||
PutAside,
|
||||
Lost
|
||||
}
|
||||
|
||||
public class BookInstance
|
||||
{
|
||||
public BookInstanceId Id { get; private set; }
|
||||
public BookId BookId { get; private set; }
|
||||
public ShelfId ShelfId { get; private set; }
|
||||
public BookInstanceBarcode Barcode { get; private set; }
|
||||
public BookInstanceStatus Status { get; private set; }
|
||||
|
||||
public BookInstance(BookId bookId, ShelfId shelfId, BookInstanceBarcode barcode)
|
||||
: this(new BookInstanceId(string.Empty), bookId, shelfId, barcode, BookInstanceStatus.OnShelf)
|
||||
{ }
|
||||
|
||||
public BookInstance(BookInstanceId id, BookId bookId, ShelfId shelfId, BookInstanceBarcode barcode, BookInstanceStatus status)
|
||||
{
|
||||
Id = id;
|
||||
BookId = bookId;
|
||||
ShelfId = shelfId;
|
||||
Barcode = barcode;
|
||||
Status = status;
|
||||
}
|
||||
|
||||
public BookInstance MarkAsLent()
|
||||
{
|
||||
if (Status != BookInstanceStatus.OnShelf)
|
||||
throw new InvalidOperationException($"Cannot lend a book that is not on the shelf. Current status: {Status}.");
|
||||
Status = BookInstanceStatus.Lent;
|
||||
return this;
|
||||
}
|
||||
|
||||
public BookInstance ReturnToShelf()
|
||||
{
|
||||
if (Status != BookInstanceStatus.Lent)
|
||||
throw new InvalidOperationException($"Cannot return a book that is not lent out. Current status: {Status}.");
|
||||
Status = BookInstanceStatus.OnShelf;
|
||||
return this;
|
||||
}
|
||||
|
||||
public BookInstance MarkAsPutAside()
|
||||
{
|
||||
if (Status != BookInstanceStatus.OnShelf)
|
||||
throw new InvalidOperationException($"Cannot put aside a book that is not on the shelf. Current status: {Status}.");
|
||||
Status = BookInstanceStatus.PutAside;
|
||||
return this;
|
||||
}
|
||||
|
||||
public BookInstance PlaceOnShelf(ShelfId shelfId)
|
||||
{
|
||||
if (Status != BookInstanceStatus.PutAside)
|
||||
throw new InvalidOperationException($"Cannot place on shelf a book that is not put aside. Current status: {Status}.");
|
||||
ShelfId = shelfId;
|
||||
Status = BookInstanceStatus.OnShelf;
|
||||
return this;
|
||||
}
|
||||
|
||||
public BookInstance MarkAsLost()
|
||||
{
|
||||
if (Status != BookInstanceStatus.OnShelf)
|
||||
throw new InvalidOperationException($"Cannot mark as lost a book that is not on the shelf. Current status: {Status}.");
|
||||
Status = BookInstanceStatus.Lost;
|
||||
return this;
|
||||
}
|
||||
|
||||
public BookInstance UnmarkAsLost()
|
||||
{
|
||||
if (Status != BookInstanceStatus.Lost)
|
||||
throw new InvalidOperationException($"Cannot unmark as lost a book that is not marked as lost. Current status: {Status}.");
|
||||
Status = BookInstanceStatus.OnShelf;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public record BookInstanceId : DbId
|
||||
{
|
||||
public BookInstanceId(string id) : base(id) {}
|
||||
}
|
||||
|
||||
public record BookInstanceBarcode
|
||||
{
|
||||
public string Barcode { get; init; }
|
||||
|
||||
public BookInstanceBarcode(string barcode)
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(barcode))
|
||||
{
|
||||
throw new ArgumentException("Barcode cannot be empty.");
|
||||
}
|
||||
Barcode = barcode;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
namespace LibraryApp.Domain.Entities;
|
||||
|
||||
public class LendRecord
|
||||
{
|
||||
public LendRecordId Id { get; private set; }
|
||||
public BookInstanceId BookInstanceId { get; private set; }
|
||||
public MemberId MemberId { get; private set; }
|
||||
public LendCode Code { get; private set; }
|
||||
public DateTime LendDate { get; private set; }
|
||||
public DateTime? ReturnDate { get; private set; }
|
||||
|
||||
public LendRecord(BookInstanceId bookInstanceId, MemberId memberId, LendCode code)
|
||||
: this(new LendRecordId(string.Empty), bookInstanceId, memberId, code, DateTime.UtcNow, null)
|
||||
{ }
|
||||
|
||||
public LendRecord(LendRecordId id, BookInstanceId bookInstanceId, MemberId memberId, LendCode code, DateTime lendDate, DateTime? returnDate)
|
||||
{
|
||||
Id = id;
|
||||
BookInstanceId = bookInstanceId;
|
||||
MemberId = memberId;
|
||||
Code = code;
|
||||
LendDate = lendDate;
|
||||
ReturnDate = returnDate;
|
||||
}
|
||||
|
||||
public LendRecord MarkAsReturned()
|
||||
{
|
||||
if(ReturnDate != null)
|
||||
throw new InvalidOperationException("This lend record is already marked as returned.");
|
||||
ReturnDate = DateTime.UtcNow;
|
||||
return this;
|
||||
}
|
||||
|
||||
public LendRecord UpdateLendDate(DateTime newLendDate)
|
||||
{
|
||||
if(ReturnDate != null)
|
||||
throw new InvalidOperationException("Cannot update lend date for a record that is already marked as returned.");
|
||||
LendDate = newLendDate;
|
||||
return this;
|
||||
}
|
||||
|
||||
public LendRecord UnmarkAsReturned()
|
||||
{
|
||||
if(ReturnDate == null)
|
||||
throw new InvalidOperationException("This lend record is not marked as returned.");
|
||||
ReturnDate = null;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public record LendRecordId : DbId
|
||||
{
|
||||
public LendRecordId(string id) : base(id) {}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A short, human-friendly code written on the physical lend slip alongside the member's name.
|
||||
/// Must be unique per lend record and no longer than 8 characters.
|
||||
/// </summary>
|
||||
public record LendCode
|
||||
{
|
||||
public string Code { get; init; }
|
||||
|
||||
public LendCode(string code)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(code))
|
||||
throw new ArgumentException("Lend code cannot be empty.");
|
||||
if (code.Length > 8)
|
||||
throw new ArgumentException("Lend code cannot exceed 8 characters.");
|
||||
Code = code.ToUpperInvariant();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
namespace LibraryApp.Domain.Entities;
|
||||
|
||||
public class Member
|
||||
{
|
||||
public MemberId Id { get; private set; }
|
||||
public MemberName Name { get; private set; }
|
||||
public List<MemberEmail> Emails { get; private set; } = new();
|
||||
public List<MemberPhone> Phones { get; private set; } = new();
|
||||
public int PrimaryEmailIndex { get; private set; } = 0;
|
||||
public int PrimaryPhoneIndex { get; private set; } = 0;
|
||||
|
||||
public Member(MemberName name, MemberEmail email, MemberPhone phone)
|
||||
: this(new MemberId(string.Empty), name, email, phone)
|
||||
{ }
|
||||
public Member(MemberId id, MemberName name, MemberEmail email, MemberPhone phone)
|
||||
{
|
||||
|
||||
Id = id;
|
||||
Name = name;
|
||||
Emails.Add(email);
|
||||
Phones.Add(phone);
|
||||
}
|
||||
|
||||
public Member UpdateName(string newName)
|
||||
{
|
||||
Name = new (newName);
|
||||
return this;
|
||||
}
|
||||
public Member AddEmail(string email)
|
||||
{
|
||||
Emails.Add(new MemberEmail(email));
|
||||
return this;
|
||||
}
|
||||
public Member AddPhone(string phone)
|
||||
{
|
||||
Phones.Add(new MemberPhone(phone));
|
||||
return this;
|
||||
}
|
||||
public Member SetPrimaryEmail(MemberEmail email)
|
||||
{
|
||||
var index = Emails.FindIndex(e => e.Email == email.Email);
|
||||
if(index == -1)
|
||||
throw new ArgumentException("Email not found in member's email list.", nameof(email));
|
||||
PrimaryEmailIndex = index;
|
||||
return this;
|
||||
}
|
||||
public Member SetPrimaryPhone(MemberPhone phone)
|
||||
{
|
||||
var index = Phones.FindIndex(p => p.Phone == phone.Phone);
|
||||
if(index == -1)
|
||||
throw new ArgumentException("Phone not found in member's phone list.", nameof(phone));
|
||||
PrimaryPhoneIndex = index;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public record MemberId : DbId
|
||||
{
|
||||
public MemberId(string id) : base(id) {}
|
||||
}
|
||||
|
||||
public record MemberEmail
|
||||
{
|
||||
public string Email { get; init; }
|
||||
|
||||
public MemberEmail(string email)
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(email))
|
||||
throw new ArgumentException("Email cannot be empty.");
|
||||
|
||||
if(!email.Contains("@"))
|
||||
throw new ArgumentException("Email must contain '@'.");
|
||||
|
||||
if(!email.Contains("."))
|
||||
throw new ArgumentException("Email must contain '.'.");
|
||||
|
||||
if(email.StartsWith("@") || email.EndsWith("@"))
|
||||
throw new ArgumentException("Email cannot start or end with '@'.");
|
||||
|
||||
var atCount = email.Count(c => c == '@');
|
||||
if(atCount > 1)
|
||||
throw new ArgumentException("Email cannot contain more than one '@'.");
|
||||
|
||||
var dotCount = email.Count(c => c == '.');
|
||||
if(dotCount > 1)
|
||||
throw new ArgumentException("Email cannot contain more than one '.'.");
|
||||
|
||||
Email = email;
|
||||
}
|
||||
}
|
||||
|
||||
public record MemberName
|
||||
{
|
||||
public string Name { get; init; }
|
||||
|
||||
public MemberName(string name)
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
throw new ArgumentException("Member name cannot be empty.");
|
||||
}
|
||||
Name = name;
|
||||
}
|
||||
}
|
||||
|
||||
public record MemberPhone
|
||||
{
|
||||
private const string VALID_PHONE_PATTERN = @"^\+?[1-9]\d{1,14}$";
|
||||
public string Phone { get; init; }
|
||||
|
||||
public MemberPhone(string phone)
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(phone))
|
||||
throw new ArgumentException("Phone number cannot be empty.");
|
||||
if(!System.Text.RegularExpressions.Regex.IsMatch(phone, VALID_PHONE_PATTERN))
|
||||
throw new ArgumentException("Phone number is not in a valid format.");
|
||||
Phone = phone;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
namespace LibraryApp.Domain.Entities;
|
||||
|
||||
public class Shelf
|
||||
{
|
||||
public ShelfId Id { get; private set; }
|
||||
public ShelfName Name { get; private set; }
|
||||
public DateTime? LastCountedAt { get; private set; }
|
||||
|
||||
public Shelf(ShelfName name)
|
||||
: this(new ShelfId(string.Empty), name, null)
|
||||
{ }
|
||||
|
||||
public Shelf(ShelfId id, ShelfName name, DateTime? lastCountedAt)
|
||||
{
|
||||
Id = id;
|
||||
Name = name;
|
||||
LastCountedAt = lastCountedAt;
|
||||
}
|
||||
|
||||
public Shelf RecordCount()
|
||||
{
|
||||
LastCountedAt = DateTime.UtcNow;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public record ShelfId : DbId
|
||||
{
|
||||
public ShelfId(string id) : base(id) {}
|
||||
}
|
||||
|
||||
public record ShelfName
|
||||
{
|
||||
public string Name { get; init; }
|
||||
|
||||
public ShelfName(string name)
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
throw new ArgumentException("Shelf name cannot be empty.");
|
||||
}
|
||||
Name = name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace LibraryApp.Domain;
|
||||
|
||||
public record Error
|
||||
{
|
||||
public string Message { get; init; }
|
||||
|
||||
public Error(string message)
|
||||
{
|
||||
Message = message;
|
||||
}
|
||||
|
||||
public static implicit operator Error(string message) => new Error(message);
|
||||
public static implicit operator string(Error error) => error.Message;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using LibraryApp.Domain.Entities;
|
||||
|
||||
namespace LibraryApp.Domain.Repositories;
|
||||
|
||||
public interface IBookInstanceRepository
|
||||
{
|
||||
public Task<Result<BookInstance>> GetByIdAsync(BookInstanceId id);
|
||||
public Task<Result<BookInstance>> GetByBarcodeAsync(BookInstanceBarcode barcode);
|
||||
public Task<Result<List<BookInstance>>> GetAllByShelfIdAsync(ShelfId shelfId);
|
||||
public Task<Result<List<BookInstance>>> GetAllByStatusAsync(BookInstanceStatus status);
|
||||
public Task<Result<BookInstance>> UpdateAsync(BookInstance bookInstance);
|
||||
public Task<Result<List<BookInstance>>> GetAllAsync(int pageNumber, int pageSize);
|
||||
public Task<Result<BookInstance>> AddAsync(BookInstance bookInstance);
|
||||
public Task<Result> DeleteAsync(BookInstanceId id);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using LibraryApp.Domain.Entities;
|
||||
|
||||
namespace LibraryApp.Domain.Repositories;
|
||||
|
||||
public interface IBookRepository
|
||||
{
|
||||
public Task<Result<Book>> GetAsync(BookId id);
|
||||
public Task<Result<Book>> UpdateAsync(Book book);
|
||||
public Task<Result<List<Book>>> GetAllAsync(int pageNumber, int pageSize);
|
||||
public Task<Result<Book>> AddAsync(Book book);
|
||||
public Task<Result> DeleteAsync(BookId id);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using LibraryApp.Domain.Entities;
|
||||
|
||||
namespace LibraryApp.Domain.Repositories;
|
||||
|
||||
public interface ILendRecordRepository
|
||||
{
|
||||
public Task<Result<LendRecord>> GetAsync(LendRecordId id);
|
||||
public Task<Result<List<LendRecord>>> GetAllByMemberIdAsync(MemberId memberId);
|
||||
public Task<Result<LendRecord?>> GetActiveByBookInstanceIdAsync(BookInstanceId bookInstanceId);
|
||||
public Task<bool> ExistsWithCodeAsync(LendCode code);
|
||||
public Task<Result<LendRecord>> UpdateAsync(LendRecord lendRecord);
|
||||
public Task<Result<List<LendRecord>>> GetAllAsync(int pageNumber, int pageSize);
|
||||
public Task<Result<LendRecord>> AddAsync(LendRecord lendRecord);
|
||||
public Task<Result> DeleteAsync(LendRecordId id);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using LibraryApp.Domain.Entities;
|
||||
|
||||
namespace LibraryApp.Domain.Repositories;
|
||||
|
||||
public interface IMemberRepository
|
||||
{
|
||||
public Task<Result<Member>> GetAsync(MemberId id);
|
||||
public Task<Result<Member>> UpdateAsync(Member member);
|
||||
public Task<Result<List<Member>>> GetAllAsync(int pageNumber, int pageSize);
|
||||
public Task<Result<Member>> AddAsync(Member member);
|
||||
public Task<Result> DeleteAsync(MemberId id);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using LibraryApp.Domain.Entities;
|
||||
|
||||
namespace LibraryApp.Domain.Repositories;
|
||||
|
||||
public interface IShelfRepository
|
||||
{
|
||||
public Task<Result<Shelf>> GetAsync(ShelfId id);
|
||||
public Task<Result<Shelf>> GetByBookInstanceBarcodeAsync(BookInstanceBarcode barcode);
|
||||
public Task<Result<Shelf>> GetNextForCountAsync();
|
||||
public Task<Result<Shelf>> UpdateAsync(Shelf shelf);
|
||||
public Task<Result<List<Shelf>>> GetAllAsync(int pageNumber, int pageSize);
|
||||
public Task<Result<Shelf>> AddAsync(Shelf shelf);
|
||||
public Task<Result> DeleteAsync(ShelfId id);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
namespace LibraryApp.Domain;
|
||||
|
||||
public record Result
|
||||
{
|
||||
public required bool IsSuccess { get; init; }
|
||||
public Error? ErrorMessage { get; init; }
|
||||
|
||||
private Result() { }
|
||||
|
||||
public static Result Success() => new Result { IsSuccess = true };
|
||||
public static Result Failure(string errorMessage) => new Result { IsSuccess = false, ErrorMessage = errorMessage };
|
||||
}
|
||||
public record Result<T>
|
||||
{
|
||||
public required bool IsSuccess { get; init; }
|
||||
public T? Value { get; init; }
|
||||
public Error? ErrorMessage { get; init; }
|
||||
|
||||
private Result() { }
|
||||
|
||||
public static Result<T> Success(T value) => new Result<T> { IsSuccess = true, Value = value };
|
||||
public static Result<T> Failure(string errorMessage) => new Result<T> { IsSuccess = false, ErrorMessage = errorMessage };
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using LibraryApp.Domain;
|
||||
using LibraryApp.Domain.Entities;
|
||||
|
||||
namespace LibraryApp.Domain.Services;
|
||||
|
||||
public class BookCountService
|
||||
{
|
||||
private readonly IBookCountReportPrinter _printer;
|
||||
|
||||
public BookCountService(IBookCountReportPrinter printer)
|
||||
{
|
||||
_printer = printer;
|
||||
}
|
||||
|
||||
public async Task<Result<BookCountReport>> PerformCountAsync(
|
||||
Shelf shelf,
|
||||
IReadOnlyList<BookInstanceBarcode> scannedBarcodes,
|
||||
IReadOnlyList<BookInstance> allInstancesOnShelf)
|
||||
{
|
||||
shelf.RecordCount();
|
||||
|
||||
var report = new BookCountReport(shelf.Id, DateTime.UtcNow, allInstancesOnShelf, scannedBarcodes);
|
||||
|
||||
foreach (var instance in report.MissingInstances)
|
||||
instance.MarkAsLost();
|
||||
|
||||
await _printer.PrintAsync(report);
|
||||
|
||||
return Result<BookCountReport>.Success(report);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace LibraryApp.Domain.Services;
|
||||
|
||||
public interface IBookCountReportPrinter
|
||||
{
|
||||
Task PrintAsync(BookCountReport report);
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
using LibraryApp.Domain.Entities;
|
||||
using LibraryApp.Domain.Repositories;
|
||||
|
||||
namespace LibraryApp.Domain.Services;
|
||||
|
||||
public class LendingService
|
||||
{
|
||||
private readonly ILendRecordRepository _lendRecords;
|
||||
|
||||
public LendingService(ILendRecordRepository lendRecords)
|
||||
{
|
||||
_lendRecords = lendRecords;
|
||||
}
|
||||
|
||||
public async Task<Result<LendRecord>> LendBookAsync(BookInstance bookInstance, Member member)
|
||||
{
|
||||
if (bookInstance.Status != BookInstanceStatus.OnShelf)
|
||||
return Result<LendRecord>.Failure($"Book is not available for lending. Current status: {bookInstance.Status}.");
|
||||
|
||||
var code = await GenerateUniqueLendCodeAsync();
|
||||
bookInstance.MarkAsLent();
|
||||
var record = new LendRecord(bookInstance.Id, member.Id, code);
|
||||
return Result<LendRecord>.Success(record);
|
||||
}
|
||||
|
||||
public Task<Result> ReturnBookAsync(BookInstance bookInstance, LendRecord lendRecord)
|
||||
{
|
||||
if (bookInstance.Status != BookInstanceStatus.Lent)
|
||||
return Task.FromResult(Result.Failure($"Book is not currently lent out. Current status: {bookInstance.Status}."));
|
||||
|
||||
bookInstance.ReturnToShelf();
|
||||
lendRecord.MarkAsReturned();
|
||||
return Task.FromResult(Result.Success());
|
||||
}
|
||||
|
||||
private async Task<LendCode> GenerateUniqueLendCodeAsync()
|
||||
{
|
||||
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||
var random = Random.Shared;
|
||||
|
||||
while (true)
|
||||
{
|
||||
var code = new string(Enumerable.Range(0, 6).Select(_ => chars[random.Next(chars.Length)]).ToArray());
|
||||
var lendCode = new LendCode(code);
|
||||
if (!await _lendRecords.ExistsWithCodeAsync(lendCode))
|
||||
return lendCode;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using LibraryApp.Domain;
|
||||
|
||||
namespace LibraryApp.Services;
|
||||
|
||||
public record Result
|
||||
{
|
||||
public required bool IsSuccess { get; init; }
|
||||
public Error? ErrorMessage { get; init; }
|
||||
|
||||
private Result() { }
|
||||
|
||||
public static Result Success() => new Result { IsSuccess = true };
|
||||
public static Result Failure(string errorMessage) => new Result { IsSuccess = false, ErrorMessage = errorMessage };
|
||||
}
|
||||
Reference in New Issue
Block a user