diff --git a/LabFiles/03-develop-code-features/AccelerateDevGHCopilot/src/Library.Console/CommonActions.cs b/LabFiles/03-develop-code-features/AccelerateDevGHCopilot/src/Library.Console/CommonActions.cs index 681f95c..a002da6 100644 --- a/LabFiles/03-develop-code-features/AccelerateDevGHCopilot/src/Library.Console/CommonActions.cs +++ b/LabFiles/03-develop-code-features/AccelerateDevGHCopilot/src/Library.Console/CommonActions.cs @@ -9,5 +9,6 @@ public enum CommonActions SearchPatrons = 4, RenewPatronMembership = 8, ReturnLoanedBook = 16, - ExtendLoanedBook = 32 + ExtendLoanedBook = 32, + SearchBooks = 64 } diff --git a/LabFiles/03-develop-code-features/AccelerateDevGHCopilot/src/Library.Console/ConsoleApp.cs b/LabFiles/03-develop-code-features/AccelerateDevGHCopilot/src/Library.Console/ConsoleApp.cs index 9fc9750..e9c476e 100644 --- a/LabFiles/03-develop-code-features/AccelerateDevGHCopilot/src/Library.Console/ConsoleApp.cs +++ b/LabFiles/03-develop-code-features/AccelerateDevGHCopilot/src/Library.Console/ConsoleApp.cs @@ -2,6 +2,7 @@ using Library.ApplicationCore.Entities; using Library.ApplicationCore.Enums; using Library.Console; +using Library.Infrastructure.Data; public class ConsoleApp { @@ -16,13 +17,15 @@ public class ConsoleApp ILoanRepository _loanRepository; ILoanService _loanService; IPatronService _patronService; + JsonData _jsonData; - public ConsoleApp(ILoanService loanService, IPatronService patronService, IPatronRepository patronRepository, ILoanRepository loanRepository) + public ConsoleApp(ILoanService loanService, IPatronService patronService, IPatronRepository patronRepository, ILoanRepository loanRepository, JsonData jsonData) { _patronRepository = patronRepository; _loanRepository = loanRepository; _loanService = loanService; _patronService = patronService; + _jsonData = jsonData; } public async Task Run() @@ -136,6 +139,7 @@ static CommonActions ReadInputOptions(CommonActions options, out int optionNumbe { "q" when options.HasFlag(CommonActions.Quit) => CommonActions.Quit, "s" when options.HasFlag(CommonActions.SearchPatrons) => CommonActions.SearchPatrons, + "b" when options.HasFlag(CommonActions.SearchBooks) => CommonActions.SearchBooks, "m" when options.HasFlag(CommonActions.RenewPatronMembership) => CommonActions.RenewPatronMembership, "e" when options.HasFlag(CommonActions.ExtendLoanedBook) => CommonActions.ExtendLoanedBook, "r" when options.HasFlag(CommonActions.ReturnLoanedBook) => CommonActions.ReturnLoanedBook, @@ -170,6 +174,10 @@ static void WriteInputOptions(CommonActions options) { Console.WriteLine(" - \"s\" for new search"); } + if (options.HasFlag(CommonActions.SearchBooks)) + { + Console.WriteLine(" - \"b\" to check if a book is available for loan"); + } if (options.HasFlag(CommonActions.Quit)) { Console.WriteLine(" - \"q\" to quit"); @@ -193,7 +201,7 @@ async Task PatronDetails() loanNumber++; } - CommonActions options = CommonActions.SearchPatrons | CommonActions.Quit | CommonActions.Select | CommonActions.RenewPatronMembership; + CommonActions options = CommonActions.SearchPatrons | CommonActions.SearchBooks | CommonActions.Quit | CommonActions.Select | CommonActions.RenewPatronMembership; CommonActions action = ReadInputOptions(options, out int selectedLoanNumber); if (action == CommonActions.Select) { @@ -217,6 +225,10 @@ async Task PatronDetails() { return ConsoleState.PatronSearch; } + else if (action == CommonActions.SearchBooks) + { + return await SearchBooks(); + } else if (action == CommonActions.RenewPatronMembership) { var status = await _patronService.RenewMembership(selectedPatronDetails.Id); @@ -229,6 +241,43 @@ async Task PatronDetails() throw new InvalidOperationException("An input option is not handled."); } + async Task SearchBooks() + { + string? bookTitle = null; + while (string.IsNullOrWhiteSpace(bookTitle)) + { + Console.Write("Enter a book title to search for: "); + bookTitle = Console.ReadLine(); + } + + await _jsonData.EnsureDataLoaded(); + + Book? book = _jsonData.Books!.FirstOrDefault(b => string.Equals(b.Title, bookTitle, StringComparison.OrdinalIgnoreCase)); + if (book == null) + { + Console.WriteLine($"No book found with title \"{bookTitle}\"."); + return ConsoleState.PatronDetails; + } + + List bookItems = _jsonData.BookItems!.Where(bookItem => bookItem.BookId == book.Id).ToList(); + List activeLoans = _jsonData.Loans! + .Where(loan => bookItems.Any(bookItem => bookItem.Id == loan.BookItemId) && loan.ReturnDate == null) + .OrderByDescending(loan => loan.LoanDate) + .ToList(); + + if (bookItems.Any(bookItem => activeLoans.All(loan => loan.BookItemId != bookItem.Id))) + { + Console.WriteLine($"{book.Title} is available for loan"); + } + else + { + Loan loan = activeLoans.First(); + Console.WriteLine($"{book.Title} is on loan to another patron. The return due date is {loan.DueDate}."); + } + + return ConsoleState.PatronDetails; + } + async Task LoanDetails() { Console.WriteLine($"Book title: {selectedLoanDetails.BookItem!.Book!.Title}"); diff --git a/untitled:plan-consultaDisponibilidadeLivros.prompt.md b/untitled:plan-consultaDisponibilidadeLivros.prompt.md new file mode 100644 index 0000000..8eb4510 --- /dev/null +++ b/untitled:plan-consultaDisponibilidadeLivros.prompt.md @@ -0,0 +1,37 @@ +## Plan: Consulta de disponibilidade de livros + +Completar a implementação parcial da consulta de disponibilidade no projeto `AccelerateDevGHCopilot`, mantendo o fluxo atual de console e o contrato do laboratório. A versão alvo já tem partes de `SearchBooks` em `ConsoleApp`, mas está inconsistente com `CommonActions` e com a injeção de `JsonData`; o plano corrige essa integração e valida o comportamento pelos dados JSON existentes. + +**Passos** +1. Atualizar `CommonActions` com `SearchBooks = 64`, preservando os valores atuais das flags. +2. Corrigir `ConsoleApp` para receber `JsonData` no construtor e armazená-lo em `_jsonData`; manter o registro único `services.AddSingleton()` já presente em `Program.cs`. +3. Completar a integração em `PatronDetails`: incluir `SearchBooks` nas opções, aceitar `b` em `ReadInputOptions`, exibir a opção em `WriteInputOptions`, chamar `SearchBooks` e retornar a `PatronDetails` após a consulta. +4. Revisar `SearchBooks`: solicitar novamente enquanto o título estiver nulo ou em branco; chamar `EnsureDataLoaded`; localizar o livro sem diferenciar maiúsculas/minúsculas; informar título inexistente; localizar todos os exemplares pelo `BookId`; considerar o título disponível se algum exemplar não tiver empréstimo ativo (`ReturnDate == null`); caso todos estejam emprestados, exibir uma data de devolução de empréstimo ativo com a formatação esperada pelo roteiro. +5. Manter o escopo fora de empréstimos e reservas: não criar `Reservations.json`, repositórios, interfaces ou serviços novos. +6. Validar com `dotnet build`, `dotnet test` e execução manual do console. Como não há testes de console no projeto alvo, cobrir manualmente entrada vazia, título inexistente, livro disponível, livro emprestado, empréstimo devolvido e retorno ao menu. + +**Relevant files** +- `/workspaces/mslearn-github-copilot-dev/LabFiles/03-develop-code-features/AccelerateDevGHCopilot/src/Library.Console/CommonActions.cs` — enum `CommonActions`; adicionar `SearchBooks`. +- `/workspaces/mslearn-github-copilot-dev/LabFiles/03-develop-code-features/AccelerateDevGHCopilot/src/Library.Console/ConsoleApp.cs` — corrigir a injeção e completar `ReadInputOptions`, `WriteInputOptions`, `PatronDetails` e `SearchBooks`. +- `/workspaces/mslearn-github-copilot-dev/LabFiles/03-develop-code-features/AccelerateDevGHCopilot/src/Library.Console/Program.cs` — somente confirmar o registro existente de `JsonData`. +- `/workspaces/mslearn-github-copilot-dev/LabFiles/03-develop-code-features/AccelerateDevGHCopilot/src/Library.Infrastructure/Data/JsonData.cs` — reutilizar `EnsureDataLoaded` e as listas carregadas; nenhuma alteração prevista. +- `/workspaces/mslearn-github-copilot-dev/LabFiles/03-develop-code-features/AccelerateDevGHCopilot/src/Library.ApplicationCore/Entities/Book.cs`, `BookItem.cs` e `Loan.cs` — relações `BookItem.BookId`, `Loan.BookItemId` e `Loan.ReturnDate` usadas na consulta. +- `/workspaces/mslearn-github-copilot-dev/Instructions/Labs/LAB_AK_03_develop_code_features.md` — requisitos funcionais e mensagens esperadas. +- `/workspaces/mslearn-github-copilot-dev/LabFiles/03-develop-code-features/AccelerateDevGHCopilot/tests/UnitTests` — não há testes de console; os testes existentes de serviços devem continuar passando. + +**Verification** +1. Executar `dotnet build` em `/workspaces/mslearn-github-copilot-dev/LabFiles/03-develop-code-features/AccelerateDevGHCopilot` e confirmar ausência de erros, especialmente a resolução de `SearchBooks` e `_jsonData`. +2. Executar `dotnet test` no mesmo diretório e confirmar que os testes existentes continuam passando. +3. Executar `dotnet run --project src/Library.Console/Library.Console.csproj`, selecionar um patrono, usar `b` e consultar um título conhecido; verificar a mensagem de disponibilidade ou de empréstimo com data. +4. Repetir a execução com entrada vazia, título inexistente e título com diferenças de caixa; verificar a repetição da solicitação, a mensagem de inexistência e a busca case-insensitive. +5. Validar um empréstimo devolvido e, se os dados contiverem múltiplos exemplares, confirmar que um exemplar livre torna o título disponível; confirmar também o retorno a `PatronDetails` e o funcionamento de `q`. + +**Decisions** +- A disponibilidade é por título e por todos os exemplares associados ao `BookId`, não somente pelo primeiro exemplar. +- Empréstimo ativo é definido por `ReturnDate == null`; a data de vencimento, inclusive vencida, não altera essa regra do requisito. +- Se houver múltiplos empréstimos ativos inconsistentes para o mesmo exemplar, selecionar deterministicamente o mais recente por `LoanDate` para a data exibida, sem alterar o modelo. +- O escopo limita-se à disponibilidade; empréstimos, reservas e persistência nova ficam para as etapas posteriores. +- Reutilizar `JsonData`; não criar novo repositório/serviço nem duplicar seu registro no DI. + +**Further Considerations** +1. A implementação posterior de empréstimos pode alterar `Loans.json`; a consulta deve continuar usando o mesmo `JsonData` singleton e recarregar dados quando a camada de empréstimos fizer isso.