From f5f5d835e67adb37aa68b4099c975774c91d95f2 Mon Sep 17 00:00:00 2001 From: ABUna1 Date: Fri, 24 Jul 2026 09:19:01 +0900 Subject: [PATCH 1/3] Add book availability feature --- .../src/Library.Console/ConsoleApp.cs | 112 +++-- .../src/Library.Console/Json/Loans.json | 403 +----------------- 2 files changed, 79 insertions(+), 436 deletions(-) diff --git a/LabFiles/02-analyze-document-code/AccelerateDevGHCopilot/src/Library.Console/ConsoleApp.cs b/LabFiles/02-analyze-document-code/AccelerateDevGHCopilot/src/Library.Console/ConsoleApp.cs index 9fc9750..9c65407 100644 --- a/LabFiles/02-analyze-document-code/AccelerateDevGHCopilot/src/Library.Console/ConsoleApp.cs +++ b/LabFiles/02-analyze-document-code/AccelerateDevGHCopilot/src/Library.Console/ConsoleApp.cs @@ -1,16 +1,11 @@ -using Library.ApplicationCore; -using Library.ApplicationCore.Entities; -using Library.ApplicationCore.Enums; -using Library.Console; - -public class ConsoleApp + { ConsoleState _currentState = ConsoleState.PatronSearch; List matchingPatrons = new List(); Patron? selectedPatronDetails = null; - Loan selectedLoanDetails = null!; + Loan? selectedLoanDetails = null; IPatronRepository _patronRepository; ILoanRepository _loanRepository; @@ -27,23 +22,17 @@ public ConsoleApp(ILoanService loanService, IPatronService patronService, IPatro public async Task Run() { - while (true) + while (_currentState != ConsoleState.Quit) { - switch (_currentState) + _currentState = _currentState switch { - case ConsoleState.PatronSearch: - _currentState = await PatronSearch(); - break; - case ConsoleState.PatronSearchResults: - _currentState = await PatronSearchResults(); - break; - case ConsoleState.PatronDetails: - _currentState = await PatronDetails(); - break; - case ConsoleState.LoanDetails: - _currentState = await LoanDetails(); - break; - } + ConsoleState.PatronSearch => await PatronSearch(), + ConsoleState.PatronSearchResults => await PatronSearchResults(), + ConsoleState.PatronDetails => await PatronDetails(), + ConsoleState.LoanDetails => await LoanDetails(), + ConsoleState.Quit => ConsoleState.Quit, + _ => throw new InvalidOperationException($"Unexpected state: {_currentState}") + }; } } @@ -53,7 +42,6 @@ async Task PatronSearch() matchingPatrons = await _patronRepository.SearchPatrons(searchInput); - // Guard-style clauses for edge cases if (matchingPatrons.Count > 20) { Console.WriteLine("More than 20 patrons satisfy the search, please provide more specific input..."); @@ -96,12 +84,20 @@ async Task PatronSearchResults() { CommonActions options = CommonActions.Select | CommonActions.SearchPatrons | CommonActions.Quit; CommonActions action = ReadInputOptions(options, out int selectedPatronNumber); + if (action == CommonActions.Select) { if (selectedPatronNumber >= 1 && selectedPatronNumber <= matchingPatrons.Count) { var selectedPatron = matchingPatrons.ElementAt(selectedPatronNumber - 1); - selectedPatronDetails = await _patronRepository.GetPatron(selectedPatron.Id)!; + selectedPatronDetails = await _patronRepository.GetPatron(selectedPatron.Id); + + if (selectedPatronDetails is null) + { + Console.WriteLine("The selected patron could not be loaded. Please try again."); + return ConsoleState.PatronSearchResults; + } + return ConsoleState.PatronDetails; } else @@ -148,6 +144,7 @@ _ when int.TryParse(userInput, out optionNumber) => CommonActions.Select, Console.WriteLine("Invalid input. Please try again."); } } while (action == CommonActions.Repeat); + return action; } @@ -182,6 +179,12 @@ static void WriteInputOptions(CommonActions options) async Task PatronDetails() { + if (selectedPatronDetails is null) + { + Console.WriteLine("No patron details are available."); + return ConsoleState.PatronSearch; + } + Console.WriteLine($"Name: {selectedPatronDetails.Name}"); Console.WriteLine($"Membership Expiration: {selectedPatronDetails.MembershipEnd}"); Console.WriteLine(); @@ -195,12 +198,20 @@ async Task PatronDetails() CommonActions options = CommonActions.SearchPatrons | CommonActions.Quit | CommonActions.Select | CommonActions.RenewPatronMembership; CommonActions action = ReadInputOptions(options, out int selectedLoanNumber); + if (action == CommonActions.Select) { if (selectedLoanNumber >= 1 && selectedLoanNumber <= selectedPatronDetails.Loans.Count()) { var selectedLoan = selectedPatronDetails.Loans.ElementAt(selectedLoanNumber - 1); - selectedLoanDetails = selectedPatronDetails.Loans.Where(l => l.Id == selectedLoan.Id).Single(); + selectedLoanDetails = selectedPatronDetails.Loans.SingleOrDefault(l => l.Id == selectedLoan.Id); + + if (selectedLoanDetails is null) + { + Console.WriteLine("The selected loan could not be found."); + return ConsoleState.PatronDetails; + } + return ConsoleState.LoanDetails; } else @@ -221,8 +232,14 @@ async Task PatronDetails() { var status = await _patronService.RenewMembership(selectedPatronDetails.Id); Console.WriteLine(EnumHelper.GetDescription(status)); - // reloading after renewing membership - selectedPatronDetails = (await _patronRepository.GetPatron(selectedPatronDetails.Id))!; + + selectedPatronDetails = await _patronRepository.GetPatron(selectedPatronDetails.Id); + if (selectedPatronDetails is null) + { + Console.WriteLine("Failed to reload patron details after renewal."); + return ConsoleState.PatronSearch; + } + return ConsoleState.PatronDetails; } @@ -231,6 +248,18 @@ async Task PatronDetails() async Task LoanDetails() { + if (selectedPatronDetails is null) + { + Console.WriteLine("No patron details are available."); + return ConsoleState.PatronSearch; + } + + if (selectedLoanDetails is null) + { + Console.WriteLine("No loan details are available."); + return ConsoleState.PatronDetails; + } + Console.WriteLine($"Book title: {selectedLoanDetails.BookItem!.Book!.Title}"); Console.WriteLine($"Book Author: {selectedLoanDetails.BookItem!.Book!.Author!.Name}"); Console.WriteLine($"Due date: {selectedLoanDetails.DueDate}"); @@ -245,19 +274,34 @@ async Task LoanDetails() var status = await _loanService.ExtendLoan(selectedLoanDetails.Id); Console.WriteLine(EnumHelper.GetDescription(status)); - // reload loan after extending - selectedPatronDetails = (await _patronRepository.GetPatron(selectedPatronDetails.Id))!; - selectedLoanDetails = (await _loanRepository.GetLoan(selectedLoanDetails.Id))!; + selectedPatronDetails = await _patronRepository.GetPatron(selectedPatronDetails.Id); + if (selectedPatronDetails is null) + { + Console.WriteLine("Failed to reload patron details after extending the loan."); + return ConsoleState.PatronSearch; + } + + selectedLoanDetails = await _loanRepository.GetLoan(selectedLoanDetails.Id); + if (selectedLoanDetails is null) + { + Console.WriteLine("Failed to reload loan details after extending the loan."); + return ConsoleState.PatronDetails; + } + return ConsoleState.LoanDetails; } else if (action == CommonActions.ReturnLoanedBook) { var status = await _loanService.ReturnLoan(selectedLoanDetails.Id); - Console.WriteLine(EnumHelper.GetDescription(status)); - _currentState = ConsoleState.LoanDetails; - // reload loan after returning + selectedLoanDetails = await _loanRepository.GetLoan(selectedLoanDetails.Id); + if (selectedLoanDetails is null) + { + Console.WriteLine("Failed to reload loan details after return."); + return ConsoleState.PatronDetails; + } + return ConsoleState.LoanDetails; } else if (action == CommonActions.Quit) @@ -271,4 +315,4 @@ async Task LoanDetails() throw new InvalidOperationException("An input option is not handled."); } -} +} \ No newline at end of file diff --git a/LabFiles/05-refactor-improve-existing-code/AccelerateDevGHCopilot/src/Library.Console/Json/Loans.json b/LabFiles/05-refactor-improve-existing-code/AccelerateDevGHCopilot/src/Library.Console/Json/Loans.json index a84491d..3df4f84 100644 --- a/LabFiles/05-refactor-improve-existing-code/AccelerateDevGHCopilot/src/Library.Console/Json/Loans.json +++ b/LabFiles/05-refactor-improve-existing-code/AccelerateDevGHCopilot/src/Library.Console/Json/Loans.json @@ -1,402 +1 @@ -[ - { - "Id": 1, - "BookItemId": 17, - "PatronId": 22, - "LoanDate": "2023-12-08T00:40:43.1808862", - "DueDate": "2023-12-22T00:40:43.1808862", - "ReturnDate": null - }, - { - "Id": 2, - "BookItemId": 6, - "PatronId": 28, - "LoanDate": "2023-12-17T00:40:43.1809243", - "DueDate": "2023-12-31T00:40:43.1809243", - "ReturnDate": null - }, - { - "Id": 3, - "BookItemId": 16, - "PatronId": 4, - "LoanDate": "2023-12-23T00:40:43.1809289", - "DueDate": "2024-01-06T00:40:43.1809289", - "ReturnDate": null - }, - { - "Id": 4, - "BookItemId": 17, - "PatronId": 14, - "LoanDate": "2023-12-22T00:40:43.1809292", - "DueDate": "2024-01-05T00:40:43.1809292", - "ReturnDate": null - }, - { - "Id": 5, - "BookItemId": 6, - "PatronId": 9, - "LoanDate": "2023-12-09T00:40:43.1809295", - "DueDate": "2023-12-23T00:40:43.1809295", - "ReturnDate": null - }, - { - "Id": 6, - "BookItemId": 14, - "PatronId": 25, - "LoanDate": "2023-12-27T00:40:43.18093", - "DueDate": "2024-01-10T00:40:43.18093", - "ReturnDate": null - }, - { - "Id": 7, - "BookItemId": 12, - "PatronId": 50, - "LoanDate": "2023-12-27T00:40:43.1809304", - "DueDate": "2024-01-10T00:40:43.1809304", - "ReturnDate": null - }, - { - "Id": 8, - "BookItemId": 18, - "PatronId": 28, - "LoanDate": "2023-12-26T00:40:43.1809306", - "DueDate": "2024-01-09T00:40:43.1809306", - "ReturnDate": null - }, - { - "Id": 9, - "BookItemId": 8, - "PatronId": 9, - "LoanDate": "2023-12-10T00:40:43.1809309", - "DueDate": "2023-12-24T00:40:43.1809309", - "ReturnDate": null - }, - { - "Id": 10, - "BookItemId": 16, - "PatronId": 3, - "LoanDate": "2023-12-26T00:40:43.1809312", - "DueDate": "2024-01-09T00:40:43.1809312", - "ReturnDate": null - }, - { - "Id": 11, - "BookItemId": 4, - "PatronId": 42, - "LoanDate": "2023-12-15T00:40:43.1809315", - "DueDate": "2023-12-29T00:40:43.1809315", - "ReturnDate": null - }, - { - "Id": 12, - "BookItemId": 17, - "PatronId": 7, - "LoanDate": "2023-12-23T00:40:43.1809331", - "DueDate": "2024-01-06T00:40:43.1809331", - "ReturnDate": null - }, - { - "Id": 13, - "BookItemId": 12, - "PatronId": 5, - "LoanDate": "2023-12-27T00:40:43.1809333", - "DueDate": "2024-01-10T00:40:43.1809333", - "ReturnDate": null - }, - { - "Id": 14, - "BookItemId": 4, - "PatronId": 9, - "LoanDate": "2023-12-10T00:40:43.1809337", - "DueDate": "2023-12-24T00:40:43.1809337", - "ReturnDate": null - }, - { - "Id": 15, - "BookItemId": 7, - "PatronId": 28, - "LoanDate": "2023-12-23T00:40:43.1809339", - "DueDate": "2024-01-06T00:40:43.1809339", - "ReturnDate": null - }, - { - "Id": 16, - "BookItemId": 14, - "PatronId": 3, - "LoanDate": "2023-12-08T00:40:43.1809342", - "DueDate": "2023-12-22T00:40:43.1809342", - "ReturnDate": null - }, - { - "Id": 17, - "BookItemId": 5, - "PatronId": 48, - "LoanDate": "2023-12-16T00:40:43.1809344", - "DueDate": "2023-12-30T00:40:43.1809344", - "ReturnDate": null - }, - { - "Id": 18, - "BookItemId": 4, - "PatronId": 49, - "LoanDate": "2023-12-19T00:40:43.1809348", - "DueDate": "2024-01-02T00:40:43.1809348", - "ReturnDate": null - }, - { - "Id": 19, - "BookItemId": 13, - "PatronId": 33, - "LoanDate": "2023-12-28T00:40:43.180935", - "DueDate": "2024-01-11T00:40:43.180935", - "ReturnDate": null - }, - { - "Id": 20, - "BookItemId": 14, - "PatronId": 48, - "LoanDate": "2023-12-27T00:40:43.1809353", - "DueDate": "2024-01-10T00:40:43.1809353", - "ReturnDate": null - }, - { - "Id": 21, - "BookItemId": 7, - "PatronId": 5, - "LoanDate": "2023-12-12T00:40:43.1809368", - "DueDate": "2023-12-26T00:40:43.1809368", - "ReturnDate": null - }, - { - "Id": 22, - "BookItemId": 9, - "PatronId": 1, - "LoanDate": "2023-12-09T00:40:43.1809371", - "DueDate": "2023-12-23T00:40:43.1809371", - "ReturnDate": null - }, - { - "Id": 23, - "BookItemId": 11, - "PatronId": 33, - "LoanDate": "2023-12-26T00:40:43.1809374", - "DueDate": "2024-01-09T00:40:43.1809374", - "ReturnDate": null - }, - { - "Id": 24, - "BookItemId": 10, - "PatronId": 46, - "LoanDate": "2023-12-28T00:40:43.1809376", - "DueDate": "2024-01-11T00:40:43.1809376", - "ReturnDate": null - }, - { - "Id": 25, - "BookItemId": 20, - "PatronId": 41, - "LoanDate": "2023-12-12T00:40:43.1809379", - "DueDate": "2023-12-26T00:40:43.1809379", - "ReturnDate": null - }, - { - "Id": 26, - "BookItemId": 13, - "PatronId": 15, - "LoanDate": "2023-12-16T00:40:43.1809382", - "DueDate": "2023-12-30T00:40:43.1809382", - "ReturnDate": null - }, - { - "Id": 27, - "BookItemId": 15, - "PatronId": 23, - "LoanDate": "2023-12-18T00:40:43.1809384", - "DueDate": "2024-01-01T00:40:43.1809384", - "ReturnDate": null - }, - { - "Id": 28, - "BookItemId": 15, - "PatronId": 31, - "LoanDate": "2023-12-11T00:40:43.1809387", - "DueDate": "2023-12-25T00:40:43.1809387", - "ReturnDate": null - }, - { - "Id": 29, - "BookItemId": 4, - "PatronId": 10, - "LoanDate": "2023-12-18T00:40:43.1809402", - "DueDate": "2024-01-01T00:40:43.1809402", - "ReturnDate": null - }, - { - "Id": 30, - "BookItemId": 6, - "PatronId": 18, - "LoanDate": "2023-12-12T00:40:43.1809405", - "DueDate": "2023-12-26T00:40:43.1809405", - "ReturnDate": null - }, - { - "Id": 31, - "BookItemId": 11, - "PatronId": 3, - "LoanDate": "2023-12-16T00:40:43.1809408", - "DueDate": "2023-12-30T00:40:43.1809408", - "ReturnDate": null - }, - { - "Id": 32, - "BookItemId": 8, - "PatronId": 20, - "LoanDate": "2023-12-22T00:40:43.1809411", - "DueDate": "2024-01-05T00:40:43.1809411", - "ReturnDate": null - }, - { - "Id": 33, - "BookItemId": 14, - "PatronId": 12, - "LoanDate": "2023-12-28T00:40:43.1809415", - "DueDate": "2024-01-11T00:40:43.1809415", - "ReturnDate": null - }, - { - "Id": 34, - "BookItemId": 19, - "PatronId": 29, - "LoanDate": "2023-12-28T00:40:43.1809458", - "DueDate": "2024-01-11T00:40:43.1809458", - "ReturnDate": "2023-12-29T00:40:54.582495" - }, - { - "Id": 35, - "BookItemId": 7, - "PatronId": 45, - "LoanDate": "2023-12-17T00:40:43.180946", - "DueDate": "2023-12-31T00:40:43.180946", - "ReturnDate": null - }, - { - "Id": 36, - "BookItemId": 11, - "PatronId": 3, - "LoanDate": "2023-12-10T00:40:43.1809463", - "DueDate": "2023-12-24T00:40:43.1809463", - "ReturnDate": null - }, - { - "Id": 37, - "BookItemId": 1, - "PatronId": 5, - "LoanDate": "2023-12-18T00:40:43.1809466", - "DueDate": "2024-01-18T00:40:43.1809466", - "ReturnDate": "2024-01-17T00:40:43.1809466" - }, - { - "Id": 38, - "BookItemId": 15, - "PatronId": 25, - "LoanDate": "2023-12-26T00:40:43.1809481", - "DueDate": "2024-01-09T00:40:43.1809481", - "ReturnDate": null - }, - { - "Id": 39, - "BookItemId": 4, - "PatronId": 33, - "LoanDate": "2023-12-18T00:40:43.1809484", - "DueDate": "2024-01-01T00:40:43.1809484", - "ReturnDate": null - }, - { - "Id": 40, - "BookItemId": 5, - "PatronId": 33, - "LoanDate": "2023-12-25T00:40:43.1809487", - "DueDate": "2024-01-08T00:40:43.1809487", - "ReturnDate": null - }, - { - "Id": 41, - "BookItemId": 14, - "PatronId": 13, - "LoanDate": "2023-12-15T00:40:43.1809489", - "DueDate": "2023-12-29T00:40:43.1809489", - "ReturnDate": null - }, - { - "Id": 42, - "BookItemId": 11, - "PatronId": 10, - "LoanDate": "2023-12-12T00:40:43.1809493", - "DueDate": "2023-12-26T00:40:43.1809493", - "ReturnDate": null - }, - { - "Id": 43, - "BookItemId": 9, - "PatronId": 45, - "LoanDate": "2023-12-14T00:40:43.1809496", - "DueDate": "2023-12-28T00:40:43.1809496", - "ReturnDate": "2023-12-29T00:49:42.3406277" - }, - { - "Id": 44, - "BookItemId": 3, - "PatronId": 46, - "LoanDate": "2023-12-08T00:40:43.1809498", - "DueDate": "2023-12-22T00:40:43.1809498", - "ReturnDate": null - }, - { - "Id": 45, - "BookItemId": 5, - "PatronId": 10, - "LoanDate": "2023-12-24T00:40:43.1809501", - "DueDate": "2024-01-07T00:40:43.1809501", - "ReturnDate": null - }, - { - "Id": 46, - "BookItemId": 1, - "PatronId": 49, - "LoanDate": "2024-07-09T00:40:43.1809503", - "DueDate": "2024-09-09T00:40:43.1809503", - "ReturnDate": null - }, - { - "Id": 47, - "BookItemId": 8, - "PatronId": 36, - "LoanDate": "2023-12-11T00:40:43.1809507", - "DueDate": "2023-12-25T00:40:43.1809507", - "ReturnDate": null - }, - { - "Id": 48, - "BookItemId": 5, - "PatronId": 10, - "LoanDate": "2023-12-18T00:40:43.1809509", - "DueDate": "2024-01-01T00:40:43.1809509", - "ReturnDate": null - }, - { - "Id": 49, - "BookItemId": 20, - "PatronId": 24, - "LoanDate": "2023-12-16T00:40:43.1809512", - "DueDate": "2023-12-30T00:40:43.1809512", - "ReturnDate": null - }, - { - "Id": 50, - "BookItemId": 3, - "PatronId": 45, - "LoanDate": "2023-12-13T00:40:43.1809514", - "DueDate": "2023-12-27T00:40:43.1809514", - "ReturnDate": "2023-12-29T00:49:48.9561798" - } - ] +[{"Id":1,"BookItemId":17,"PatronId":22,"Patron":null,"LoanDate":"2023-12-08T00:40:43.1808862","DueDate":"2023-12-22T00:40:43.1808862","ReturnDate":null,"BookItem":null},{"Id":2,"BookItemId":6,"PatronId":28,"Patron":null,"LoanDate":"2023-12-17T00:40:43.1809243","DueDate":"2023-12-31T00:40:43.1809243","ReturnDate":null,"BookItem":null},{"Id":3,"BookItemId":16,"PatronId":4,"Patron":null,"LoanDate":"2023-12-23T00:40:43.1809289","DueDate":"2024-01-06T00:40:43.1809289","ReturnDate":null,"BookItem":null},{"Id":4,"BookItemId":17,"PatronId":14,"Patron":null,"LoanDate":"2023-12-22T00:40:43.1809292","DueDate":"2024-01-05T00:40:43.1809292","ReturnDate":null,"BookItem":null},{"Id":5,"BookItemId":6,"PatronId":9,"Patron":null,"LoanDate":"2023-12-09T00:40:43.1809295","DueDate":"2023-12-23T00:40:43.1809295","ReturnDate":null,"BookItem":null},{"Id":6,"BookItemId":14,"PatronId":25,"Patron":null,"LoanDate":"2023-12-27T00:40:43.18093","DueDate":"2024-01-10T00:40:43.18093","ReturnDate":null,"BookItem":null},{"Id":7,"BookItemId":12,"PatronId":50,"Patron":null,"LoanDate":"2023-12-27T00:40:43.1809304","DueDate":"2024-01-10T00:40:43.1809304","ReturnDate":null,"BookItem":null},{"Id":8,"BookItemId":18,"PatronId":28,"Patron":null,"LoanDate":"2023-12-26T00:40:43.1809306","DueDate":"2024-01-09T00:40:43.1809306","ReturnDate":null,"BookItem":null},{"Id":9,"BookItemId":8,"PatronId":9,"Patron":null,"LoanDate":"2023-12-10T00:40:43.1809309","DueDate":"2023-12-24T00:40:43.1809309","ReturnDate":null,"BookItem":null},{"Id":10,"BookItemId":16,"PatronId":3,"Patron":null,"LoanDate":"2023-12-26T00:40:43.1809312","DueDate":"2024-01-09T00:40:43.1809312","ReturnDate":null,"BookItem":null},{"Id":11,"BookItemId":4,"PatronId":42,"Patron":null,"LoanDate":"2023-12-15T00:40:43.1809315","DueDate":"2023-12-29T00:40:43.1809315","ReturnDate":null,"BookItem":null},{"Id":12,"BookItemId":17,"PatronId":7,"Patron":null,"LoanDate":"2023-12-23T00:40:43.1809331","DueDate":"2024-01-06T00:40:43.1809331","ReturnDate":null,"BookItem":null},{"Id":13,"BookItemId":12,"PatronId":5,"Patron":null,"LoanDate":"2023-12-27T00:40:43.1809333","DueDate":"2024-01-10T00:40:43.1809333","ReturnDate":null,"BookItem":null},{"Id":14,"BookItemId":4,"PatronId":9,"Patron":null,"LoanDate":"2023-12-10T00:40:43.1809337","DueDate":"2023-12-24T00:40:43.1809337","ReturnDate":null,"BookItem":null},{"Id":15,"BookItemId":7,"PatronId":28,"Patron":null,"LoanDate":"2023-12-23T00:40:43.1809339","DueDate":"2024-01-06T00:40:43.1809339","ReturnDate":null,"BookItem":null},{"Id":16,"BookItemId":14,"PatronId":3,"Patron":null,"LoanDate":"2023-12-08T00:40:43.1809342","DueDate":"2023-12-22T00:40:43.1809342","ReturnDate":null,"BookItem":null},{"Id":17,"BookItemId":5,"PatronId":48,"Patron":null,"LoanDate":"2023-12-16T00:40:43.1809344","DueDate":"2023-12-30T00:40:43.1809344","ReturnDate":null,"BookItem":null},{"Id":18,"BookItemId":4,"PatronId":49,"Patron":null,"LoanDate":"2023-12-19T00:40:43.1809348","DueDate":"2024-01-02T00:40:43.1809348","ReturnDate":null,"BookItem":null},{"Id":19,"BookItemId":13,"PatronId":33,"Patron":null,"LoanDate":"2023-12-28T00:40:43.180935","DueDate":"2024-01-11T00:40:43.180935","ReturnDate":null,"BookItem":null},{"Id":20,"BookItemId":14,"PatronId":48,"Patron":null,"LoanDate":"2023-12-27T00:40:43.1809353","DueDate":"2024-01-10T00:40:43.1809353","ReturnDate":null,"BookItem":null},{"Id":21,"BookItemId":7,"PatronId":5,"Patron":null,"LoanDate":"2023-12-12T00:40:43.1809368","DueDate":"2023-12-26T00:40:43.1809368","ReturnDate":null,"BookItem":null},{"Id":22,"BookItemId":9,"PatronId":1,"Patron":null,"LoanDate":"2023-12-09T00:40:43.1809371","DueDate":"2023-12-23T00:40:43.1809371","ReturnDate":null,"BookItem":null},{"Id":23,"BookItemId":11,"PatronId":33,"Patron":null,"LoanDate":"2023-12-26T00:40:43.1809374","DueDate":"2024-01-09T00:40:43.1809374","ReturnDate":null,"BookItem":null},{"Id":24,"BookItemId":10,"PatronId":46,"Patron":null,"LoanDate":"2023-12-28T00:40:43.1809376","DueDate":"2024-01-11T00:40:43.1809376","ReturnDate":null,"BookItem":null},{"Id":25,"BookItemId":20,"PatronId":41,"Patron":null,"LoanDate":"2023-12-12T00:40:43.1809379","DueDate":"2023-12-26T00:40:43.1809379","ReturnDate":null,"BookItem":null},{"Id":26,"BookItemId":13,"PatronId":15,"Patron":null,"LoanDate":"2023-12-16T00:40:43.1809382","DueDate":"2023-12-30T00:40:43.1809382","ReturnDate":null,"BookItem":null},{"Id":27,"BookItemId":15,"PatronId":23,"Patron":null,"LoanDate":"2023-12-18T00:40:43.1809384","DueDate":"2024-01-01T00:40:43.1809384","ReturnDate":null,"BookItem":null},{"Id":28,"BookItemId":15,"PatronId":31,"Patron":null,"LoanDate":"2023-12-11T00:40:43.1809387","DueDate":"2023-12-25T00:40:43.1809387","ReturnDate":null,"BookItem":null},{"Id":29,"BookItemId":4,"PatronId":10,"Patron":null,"LoanDate":"2023-12-18T00:40:43.1809402","DueDate":"2024-01-01T00:40:43.1809402","ReturnDate":null,"BookItem":null},{"Id":30,"BookItemId":6,"PatronId":18,"Patron":null,"LoanDate":"2023-12-12T00:40:43.1809405","DueDate":"2023-12-26T00:40:43.1809405","ReturnDate":null,"BookItem":null},{"Id":31,"BookItemId":11,"PatronId":3,"Patron":null,"LoanDate":"2023-12-16T00:40:43.1809408","DueDate":"2023-12-30T00:40:43.1809408","ReturnDate":null,"BookItem":null},{"Id":32,"BookItemId":8,"PatronId":20,"Patron":null,"LoanDate":"2023-12-22T00:40:43.1809411","DueDate":"2024-01-05T00:40:43.1809411","ReturnDate":null,"BookItem":null},{"Id":33,"BookItemId":14,"PatronId":12,"Patron":null,"LoanDate":"2023-12-28T00:40:43.1809415","DueDate":"2024-01-11T00:40:43.1809415","ReturnDate":"2026-07-23T12:35:43.811849+09:00","BookItem":null},{"Id":34,"BookItemId":19,"PatronId":29,"Patron":null,"LoanDate":"2023-12-28T00:40:43.1809458","DueDate":"2024-01-11T00:40:43.1809458","ReturnDate":"2023-12-29T00:40:54.582495","BookItem":null},{"Id":35,"BookItemId":7,"PatronId":45,"Patron":null,"LoanDate":"2023-12-17T00:40:43.180946","DueDate":"2023-12-31T00:40:43.180946","ReturnDate":null,"BookItem":null},{"Id":36,"BookItemId":11,"PatronId":3,"Patron":null,"LoanDate":"2023-12-10T00:40:43.1809463","DueDate":"2023-12-24T00:40:43.1809463","ReturnDate":null,"BookItem":null},{"Id":37,"BookItemId":1,"PatronId":5,"Patron":null,"LoanDate":"2023-12-18T00:40:43.1809466","DueDate":"2024-01-18T00:40:43.1809466","ReturnDate":"2024-01-17T00:40:43.1809466","BookItem":null},{"Id":38,"BookItemId":15,"PatronId":25,"Patron":null,"LoanDate":"2023-12-26T00:40:43.1809481","DueDate":"2024-01-09T00:40:43.1809481","ReturnDate":null,"BookItem":null},{"Id":39,"BookItemId":4,"PatronId":33,"Patron":null,"LoanDate":"2023-12-18T00:40:43.1809484","DueDate":"2024-01-01T00:40:43.1809484","ReturnDate":null,"BookItem":null},{"Id":40,"BookItemId":5,"PatronId":33,"Patron":null,"LoanDate":"2023-12-25T00:40:43.1809487","DueDate":"2024-01-08T00:40:43.1809487","ReturnDate":null,"BookItem":null},{"Id":41,"BookItemId":14,"PatronId":13,"Patron":null,"LoanDate":"2023-12-15T00:40:43.1809489","DueDate":"2023-12-29T00:40:43.1809489","ReturnDate":null,"BookItem":null},{"Id":42,"BookItemId":11,"PatronId":10,"Patron":null,"LoanDate":"2023-12-12T00:40:43.1809493","DueDate":"2023-12-26T00:40:43.1809493","ReturnDate":null,"BookItem":null},{"Id":43,"BookItemId":9,"PatronId":45,"Patron":null,"LoanDate":"2023-12-14T00:40:43.1809496","DueDate":"2023-12-28T00:40:43.1809496","ReturnDate":"2023-12-29T00:49:42.3406277","BookItem":null},{"Id":44,"BookItemId":3,"PatronId":46,"Patron":null,"LoanDate":"2023-12-08T00:40:43.1809498","DueDate":"2023-12-22T00:40:43.1809498","ReturnDate":null,"BookItem":null},{"Id":45,"BookItemId":5,"PatronId":10,"Patron":null,"LoanDate":"2023-12-24T00:40:43.1809501","DueDate":"2024-01-07T00:40:43.1809501","ReturnDate":null,"BookItem":null},{"Id":46,"BookItemId":1,"PatronId":49,"Patron":null,"LoanDate":"2024-07-09T00:40:43.1809503","DueDate":"2024-09-09T00:40:43.1809503","ReturnDate":null,"BookItem":null},{"Id":47,"BookItemId":8,"PatronId":36,"Patron":null,"LoanDate":"2023-12-11T00:40:43.1809507","DueDate":"2023-12-25T00:40:43.1809507","ReturnDate":null,"BookItem":null},{"Id":48,"BookItemId":5,"PatronId":10,"Patron":null,"LoanDate":"2023-12-18T00:40:43.1809509","DueDate":"2024-01-01T00:40:43.1809509","ReturnDate":null,"BookItem":null},{"Id":49,"BookItemId":20,"PatronId":24,"Patron":null,"LoanDate":"2023-12-16T00:40:43.1809512","DueDate":"2023-12-30T00:40:43.1809512","ReturnDate":null,"BookItem":null},{"Id":50,"BookItemId":3,"PatronId":45,"Patron":null,"LoanDate":"2023-12-13T00:40:43.1809514","DueDate":"2023-12-27T00:40:43.1809514","ReturnDate":"2023-12-29T00:49:48.9561798","BookItem":null}] \ No newline at end of file From 40ec8f4b39148f23546dcc8f4f819cbdef7fc642 Mon Sep 17 00:00:00 2001 From: ABUna1 Date: Sun, 26 Jul 2026 10:08:10 +0900 Subject: [PATCH 2/3] Add ExtendLoan unit tests and improve time handling --- .../Services/LoanService.cs | 15 +++- .../ApplicationCore/LoanService/ExtendLoan.cs | 68 ++++++++++++++++++- 2 files changed, 79 insertions(+), 4 deletions(-) diff --git a/LabFiles/04-develop-unit-tests-xunit/AccelerateDevGHCopilot/src/Library.ApplicationCore/Services/LoanService.cs b/LabFiles/04-develop-unit-tests-xunit/AccelerateDevGHCopilot/src/Library.ApplicationCore/Services/LoanService.cs index 0f13d3a..b62c54a 100644 --- a/LabFiles/04-develop-unit-tests-xunit/AccelerateDevGHCopilot/src/Library.ApplicationCore/Services/LoanService.cs +++ b/LabFiles/04-develop-unit-tests-xunit/AccelerateDevGHCopilot/src/Library.ApplicationCore/Services/LoanService.cs @@ -4,11 +4,18 @@ public class LoanService : ILoanService { - private ILoanRepository _loanRepository; + private readonly ILoanRepository _loanRepository; + private readonly Func _nowProvider; public LoanService(ILoanRepository loanRepository) + : this(loanRepository, () => DateTime.Now) + { + } + + public LoanService(ILoanRepository loanRepository, Func nowProvider) { _loanRepository = loanRepository; + _nowProvider = nowProvider; } public async Task ReturnLoan(int loanId) @@ -46,14 +53,16 @@ public async Task ExtendLoan(int loanId) if (loan == null) return LoanExtensionStatus.LoanNotFound; + var now = _nowProvider(); + // Check if patron's membership is expired - if (loan.Patron!.MembershipEnd < DateTime.Now) + if (loan.Patron!.MembershipEnd <= now) return LoanExtensionStatus.MembershipExpired; if (loan.ReturnDate != null) return LoanExtensionStatus.LoanReturned; - if (loan.DueDate < DateTime.Now) + if (loan.DueDate <= now) return LoanExtensionStatus.LoanExpired; loan.DueDate = loan.DueDate.AddDays(ExtendByDays); diff --git a/LabFiles/04-develop-unit-tests-xunit/AccelerateDevGHCopilot/tests/UnitTests/ApplicationCore/LoanService/ExtendLoan.cs b/LabFiles/04-develop-unit-tests-xunit/AccelerateDevGHCopilot/tests/UnitTests/ApplicationCore/LoanService/ExtendLoan.cs index d3e695b..da997f4 100644 --- a/LabFiles/04-develop-unit-tests-xunit/AccelerateDevGHCopilot/tests/UnitTests/ApplicationCore/LoanService/ExtendLoan.cs +++ b/LabFiles/04-develop-unit-tests-xunit/AccelerateDevGHCopilot/tests/UnitTests/ApplicationCore/LoanService/ExtendLoan.cs @@ -101,4 +101,70 @@ public async Task ExtendLoan_ReturnsLoanExpired() Assert.Equal(LoanExtensionStatus.LoanExpired, extensionStatus); Assert.Equal(loanDueDate, loan.DueDate); } -} + [Fact(DisplayName = "LoanService.ExtendLoan: Returns LoanExpired when due date is exactly the current time")] + public async Task ExtendLoan_ReturnsLoanExpiredWhenDueDateIsExactlyNow() + { + // Arrange + var now = new DateTime(2026, 1, 1, 12, 0, 0); + var patron = PatronFactory.CreateCurrentPatron(); + var loan = LoanFactory.CreateCurrentLoanForPatron(patron); + var loanId = loan.Id; + loan.DueDate = now; + + var serviceWithFixedClock = new LoanService(_mockLoanRepository, () => now); + _mockLoanRepository.GetLoan(loanId).Returns(loan); + + // Act + LoanExtensionStatus extensionStatus = await serviceWithFixedClock.ExtendLoan(loanId); + + // Assert + Assert.Equal(LoanExtensionStatus.LoanExpired, extensionStatus); + Assert.Equal(now, loan.DueDate); + } + + [Fact(DisplayName = "LoanService.ExtendLoan: Returns MembershipExpired when membership ends exactly at the current time")] + public async Task ExtendLoan_ReturnsMembershipExpiredWhenMembershipEndsExactlyNow() + { + // Arrange + var now = new DateTime(2026, 1, 1, 12, 0, 0); + var patron = PatronFactory.CreateCurrentPatron(); + patron.MembershipEnd = now; + var loan = LoanFactory.CreateCurrentLoanForPatron(patron); + var loanId = loan.Id; + var loanDueDate = loan.DueDate; + + var serviceWithFixedClock = new LoanService(_mockLoanRepository, () => now); + _mockLoanRepository.GetLoan(loanId).Returns(loan); + + // Act + LoanExtensionStatus extensionStatus = await serviceWithFixedClock.ExtendLoan(loanId); + + // Assert + Assert.Equal(LoanExtensionStatus.MembershipExpired, extensionStatus); + Assert.Equal(loanDueDate, loan.DueDate); + } + + [Fact(DisplayName = "LoanService.ExtendLoan: Returns Error when UpdateLoan throws exception")] + public async Task ExtendLoan_ReturnsErrorWhenUpdateLoanThrowsException() + { + // Arrange + var patron = PatronFactory.CreateCurrentPatron(); + var loan = LoanFactory.CreateCurrentLoanForPatron(patron); + var loanId = loan.Id; + var loanDueDate = loan.DueDate; + + _mockLoanRepository.GetLoan(loanId).Returns(loan); + + _mockLoanRepository + .When(x => x.UpdateLoan(loan)) + .Do(_ => throw new Exception("Update failed")); + + // Act + LoanExtensionStatus extensionStatus = await _loanService.ExtendLoan(loanId); + + // Assert + Assert.Equal(LoanExtensionStatus.Error, extensionStatus); + Assert.Equal(loanDueDate.AddDays(LoanService.ExtendByDays), loan.DueDate); + await _mockLoanRepository.Received(1).UpdateLoan(loan); + } +} \ No newline at end of file From 9a7ad0203bbc01ea372c1f8c10bb49b4ceb03409 Mon Sep 17 00:00:00 2001 From: ABUna1 Date: Tue, 18 Aug 2026 09:05:47 +0900 Subject: [PATCH 3/3] Refactor pricing engine using GitHub Copilot Agent --- .../ECommercePricingDemo.cs | 461 +++++++++--------- 1 file changed, 230 insertions(+), 231 deletions(-) diff --git a/LabFiles/09-simplify-complex-conditionals/ECommercePricingEngine/ECommercePricingDemo.cs b/LabFiles/09-simplify-complex-conditionals/ECommercePricingEngine/ECommercePricingDemo.cs index 8e36856..1b53957 100644 --- a/LabFiles/09-simplify-complex-conditionals/ECommercePricingEngine/ECommercePricingDemo.cs +++ b/LabFiles/09-simplify-complex-conditionals/ECommercePricingEngine/ECommercePricingDemo.cs @@ -89,7 +89,7 @@ public static void CalculateFinalPrice(User user, Order order) } decimal baseTotal = order.GetSubtotal(); - + // Security: Validate base total is within reasonable bounds if (baseTotal <= 0 || baseTotal > MAX_ORDER_VALUE) { @@ -101,269 +101,268 @@ public static void CalculateFinalPrice(User user, Order order) decimal shippingCost = CalculateBaseShipping(order); var appliedDiscounts = new List(); - // 1. Membership-based discounts: Primary customer tier evaluation + discountPercent = ApplyMembershipDiscounts(user, order, baseTotal, discountPercent, appliedDiscounts); + discountPercent = ApplyCouponDiscounts(user, order, discountPercent, ref shippingCost, appliedDiscounts); + discountPercent = ApplyBulkDiscounts(order, discountPercent, appliedDiscounts); + + // Security: Final discount validation + discountPercent = Math.Min(discountPercent, MAX_DISCOUNT_PERCENT); + + // Apply final calculations with category-specific rules + var finalCalculation = ApplyCategorySpecificDiscounts(baseTotal, discountPercent, order); + decimal finalPrice = Math.Max(MIN_FINAL_PRICE, finalCalculation.finalPrice + shippingCost); + + // Display results + Console.WriteLine($"Base Total: ${baseTotal:F2}"); + Console.WriteLine($"Applied Discounts: {string.Join(", ", appliedDiscounts)}"); + Console.WriteLine($"Total Discount: {discountPercent:F1}% (Electronics capped at 15%)"); + Console.WriteLine($"Shipping Cost: ${shippingCost:F2}"); + Console.WriteLine($"Final Price: ${finalPrice:F2}"); + } + + private static decimal ApplyMembershipDiscounts(User user, Order order, decimal baseTotal, decimal discountPercent, List appliedDiscounts) + { if (user.Membership == MembershipLevel.Premium) { - discountPercent = SafeAddDiscount(discountPercent, 15, "Premium membership (15%)", appliedDiscounts); - - // 2. Premium high-value threshold: Escalating discounts for premium members - if (baseTotal > 10000) - { - discountPercent = SafeAddDiscount(discountPercent, 10, "Ultra high-value bonus (10%)", appliedDiscounts); - - // 3. Seasonal event multiplier: Premium seasonal benefits - if (order.ActiveEvent == SeasonalEvent.BlackFriday || order.ActiveEvent == SeasonalEvent.CyberMonday) - { - discountPercent = SafeAddDiscount(discountPercent, 8, "Premium seasonal bonus (8%)", appliedDiscounts); - - // 4. Corporate account benefits: B2B premium advantages - if (user.IsCorporateAccount) - { - discountPercent = SafeAddDiscount(discountPercent, 5, "Corporate account bonus (5%)", appliedDiscounts); - - // 5. Subscription service benefits: Recurring revenue incentives - if (user.HasActiveSubscription) - { - discountPercent = SafeAddDiscount(discountPercent, 3, "Subscription service bonus (3%)", appliedDiscounts); - - // 6. Loyalty tenure reward: Long-term premium customer benefits - if (user.YearsAsMember >= 5) - { - discountPercent = SafeAddDiscount(discountPercent, 5, "Veteran premium member (5%)", appliedDiscounts); - - // 7. Lifetime spending tier: Ultimate premium benefits - if (user.LifetimeSpent > 50000) - { - discountPercent = SafeAddDiscount(discountPercent, 7, "VIP status (7%)", appliedDiscounts); - - // 8. Express shipping optimization: Premium logistics benefits - if (order.HasExpressShipping) - { - discountPercent = SafeAddDiscount(discountPercent, 2, "Express shipping loyalty bonus (2%)", appliedDiscounts); - } - } - } - } - } - } - } - else if (baseTotal > 5000) - { - discountPercent = SafeAddDiscount(discountPercent, 5, "High-value bonus (5%)", appliedDiscounts); - } + return ApplyPremiumMemberDiscounts(user, order, baseTotal, discountPercent, appliedDiscounts); } - else if (user.Membership == MembershipLevel.Gold) + + if (user.Membership == MembershipLevel.Gold) { - discountPercent = SafeAddDiscount(discountPercent, 12, "Gold membership (12%)", appliedDiscounts); - - // 2. Gold seasonal benefits: Mid-tier seasonal advantages - if (order.ActiveEvent != SeasonalEvent.None) - { - discountPercent = SafeAddDiscount(discountPercent, 6, "Gold seasonal bonus (6%)", appliedDiscounts); - - // 3. Gold volume threshold: Quantity-based gold benefits - if (order.Items.Count >= 15) - { - discountPercent = SafeAddDiscount(discountPercent, 4, "Gold bulk bonus (4%)", appliedDiscounts); - - // 4. Category diversity bonus: Multi-category gold rewards - if (order.HasMixedCategories()) - { - discountPercent = SafeAddDiscount(discountPercent, 3, "Category diversity bonus (3%)", appliedDiscounts); - - // 5. Employee discount stacking: Staff gold benefits - if (user.IsEmployee) - { - discountPercent = SafeAddDiscount(discountPercent, 10, "Employee gold discount (10%)", appliedDiscounts); - - // 6. Pre-order benefits: Early access inventory rewards - if (order.IsPreOrder) - { - discountPercent = SafeAddDiscount(discountPercent, 5, "Pre-order employee bonus (5%)", appliedDiscounts); - - // 7. Payment method optimization: Financial processing benefits - if (order.PaymentMethod == PaymentMethod.BankTransfer || order.PaymentMethod == PaymentMethod.Cryptocurrency) - { - discountPercent = SafeAddDiscount(discountPercent, 3, "Alternative payment bonus (3%)", appliedDiscounts); - } - } - } - } - } - } + return ApplyGoldMemberDiscounts(user, order, discountPercent, appliedDiscounts); } - else if (user.Membership == MembershipLevel.Silver) + + if (user.Membership == MembershipLevel.Silver) { - discountPercent = SafeAddDiscount(discountPercent, 8, "Silver membership (8%)", appliedDiscounts); - - // 2. Silver student benefits: Educational discounts - if (user.IsStudent) + return ApplySilverMemberDiscounts(user, order, discountPercent, appliedDiscounts); + } + + if (user.IsFirstTimeBuyer) + { + return ApplyFirstTimeBuyerDiscounts(user, order, discountPercent, appliedDiscounts); + } + + return discountPercent; + } + + private static decimal ApplyPremiumMemberDiscounts(User user, Order order, decimal baseTotal, decimal discountPercent, List appliedDiscounts) + { + discountPercent = SafeAddDiscount(discountPercent, 15, "Premium membership (15%)", appliedDiscounts); + + if (baseTotal > 10000) + { + discountPercent = SafeAddDiscount(discountPercent, 10, "Ultra high-value bonus (10%)", appliedDiscounts); + + if (order.ActiveEvent == SeasonalEvent.BlackFriday || order.ActiveEvent == SeasonalEvent.CyberMonday) { - discountPercent = SafeAddDiscount(discountPercent, 5, "Student silver bonus (5%)", appliedDiscounts); - - // 3. Back-to-school special: Seasonal student benefits - if (order.ActiveEvent == SeasonalEvent.BackToSchool) - { - discountPercent = SafeAddDiscount(discountPercent, 7, "Back-to-school bonus (7%)", appliedDiscounts); - - // 4. Student bulk purchase: Educational volume discounts - if (order.Items.Count >= 8) - { - discountPercent = SafeAddDiscount(discountPercent, 4, "Student bulk discount (4%)", appliedDiscounts); - - // 5. Student electronics focus: Technology education discounts - if (SafeGetCategoryPercentage(order, "Electronics") > 0.6m) - { - discountPercent = SafeAddDiscount(discountPercent, 6, "Student tech focus bonus (6%)", appliedDiscounts); - - // 6. Gift wrap service: Student presentation benefits - if (order.HasGiftWrap) - { - discountPercent = SafeAddDiscount(discountPercent, 2, "Gift presentation bonus (2%)", appliedDiscounts); - - // 7. Express delivery educational: Time-sensitive learning benefits - if (order.HasExpressShipping) - { - discountPercent = SafeAddDiscount(discountPercent, 3, "Express education bonus (3%)", appliedDiscounts); - } - } - } - } - } + discountPercent = SafeAddDiscount(discountPercent, 8, "Premium seasonal bonus (8%)", appliedDiscounts); + + if (!user.IsCorporateAccount) return discountPercent; + + discountPercent = SafeAddDiscount(discountPercent, 5, "Corporate account bonus (5%)", appliedDiscounts); + + if (!user.HasActiveSubscription) return discountPercent; + + discountPercent = SafeAddDiscount(discountPercent, 3, "Subscription service bonus (3%)", appliedDiscounts); + + if (user.YearsAsMember < 5) return discountPercent; + + discountPercent = SafeAddDiscount(discountPercent, 5, "Veteran premium member (5%)", appliedDiscounts); + + if (user.LifetimeSpent <= 50000) return discountPercent; + + discountPercent = SafeAddDiscount(discountPercent, 7, "VIP status (7%)", appliedDiscounts); + + if (!order.HasExpressShipping) return discountPercent; + + discountPercent = SafeAddDiscount(discountPercent, 2, "Express shipping loyalty bonus (2%)", appliedDiscounts); + return discountPercent; } + + return discountPercent; } - else if (user.IsFirstTimeBuyer) + + if (baseTotal > 5000) { - discountPercent = SafeAddDiscount(discountPercent, 10, "First-time buyer (10%)", appliedDiscounts); - - // 2. New customer seasonal welcome: Event-based new customer benefits - if (order.ActiveEvent != SeasonalEvent.None) + discountPercent = SafeAddDiscount(discountPercent, 5, "High-value bonus (5%)", appliedDiscounts); + } + + return discountPercent; + } + + private static decimal ApplyGoldMemberDiscounts(User user, Order order, decimal discountPercent, List appliedDiscounts) + { + discountPercent = SafeAddDiscount(discountPercent, 12, "Gold membership (12%)", appliedDiscounts); + + if (order.ActiveEvent == SeasonalEvent.None) return discountPercent; + + discountPercent = SafeAddDiscount(discountPercent, 6, "Gold seasonal bonus (6%)", appliedDiscounts); + + if (order.Items.Count < 15) return discountPercent; + + discountPercent = SafeAddDiscount(discountPercent, 4, "Gold bulk bonus (4%)", appliedDiscounts); + + if (!order.HasMixedCategories()) return discountPercent; + + discountPercent = SafeAddDiscount(discountPercent, 3, "Category diversity bonus (3%)", appliedDiscounts); + + if (!user.IsEmployee) return discountPercent; + + discountPercent = SafeAddDiscount(discountPercent, 10, "Employee gold discount (10%)", appliedDiscounts); + + if (!order.IsPreOrder) return discountPercent; + + discountPercent = SafeAddDiscount(discountPercent, 5, "Pre-order employee bonus (5%)", appliedDiscounts); + + if (order.PaymentMethod != PaymentMethod.BankTransfer && order.PaymentMethod != PaymentMethod.Cryptocurrency) return discountPercent; + + discountPercent = SafeAddDiscount(discountPercent, 3, "Alternative payment bonus (3%)", appliedDiscounts); + return discountPercent; + } + + private static decimal ApplySilverMemberDiscounts(User user, Order order, decimal discountPercent, List appliedDiscounts) + { + discountPercent = SafeAddDiscount(discountPercent, 8, "Silver membership (8%)", appliedDiscounts); + + if (!user.IsStudent) return discountPercent; + + discountPercent = SafeAddDiscount(discountPercent, 5, "Student silver bonus (5%)", appliedDiscounts); + + if (order.ActiveEvent != SeasonalEvent.BackToSchool) return discountPercent; + + discountPercent = SafeAddDiscount(discountPercent, 7, "Back-to-school bonus (7%)", appliedDiscounts); + + if (order.Items.Count < 8) return discountPercent; + + discountPercent = SafeAddDiscount(discountPercent, 4, "Student bulk discount (4%)", appliedDiscounts); + + if (SafeGetCategoryPercentage(order, "Electronics") <= 0.6m) return discountPercent; + + discountPercent = SafeAddDiscount(discountPercent, 6, "Student tech focus bonus (6%)", appliedDiscounts); + + if (!order.HasGiftWrap) return discountPercent; + + discountPercent = SafeAddDiscount(discountPercent, 2, "Gift presentation bonus (2%)", appliedDiscounts); + + if (!order.HasExpressShipping) return discountPercent; + + discountPercent = SafeAddDiscount(discountPercent, 3, "Express education bonus (3%)", appliedDiscounts); + return discountPercent; + } + + private static decimal ApplyFirstTimeBuyerDiscounts(User user, Order order, decimal discountPercent, List appliedDiscounts) + { + discountPercent = SafeAddDiscount(discountPercent, 10, "First-time buyer (10%)", appliedDiscounts); + + if (order.ActiveEvent == SeasonalEvent.None) return discountPercent; + + discountPercent = SafeAddDiscount(discountPercent, 5, "Seasonal welcome bonus (5%)", appliedDiscounts); + + if (order.Items.Count < 5) return discountPercent; + + discountPercent = SafeAddDiscount(discountPercent, 4, "First-order volume bonus (4%)", appliedDiscounts); + + if (order.PaymentMethod != PaymentMethod.PayPal && order.PaymentMethod != PaymentMethod.CreditCard) return discountPercent; + + discountPercent = SafeAddDiscount(discountPercent, 3, "Premium payment newcomer bonus (3%)", appliedDiscounts); + + if (!order.IsHighValueOrder()) return discountPercent; + + discountPercent = SafeAddDiscount(discountPercent, 6, "High-value newcomer bonus (6%)", appliedDiscounts); + + if (order.ShippingRegion != RegionType.PremiumZone) return discountPercent; + + discountPercent = SafeAddDiscount(discountPercent, 4, "Premium zone newcomer bonus (4%)", appliedDiscounts); + + if (!order.HasExpressShipping) return discountPercent; + + discountPercent = SafeAddDiscount(discountPercent, 3, "Express shipping trial bonus (3%)", appliedDiscounts); + return discountPercent; + } + + private static decimal ApplyCouponDiscounts(User user, Order order, decimal discountPercent, ref decimal shippingCost, List appliedDiscounts) + { + if (order.Coupon == null) + { + return discountPercent; + } + + if (!order.Coupon.IsValid) + { + if (order.Coupon.IsExpired) { - discountPercent = SafeAddDiscount(discountPercent, 5, "Seasonal welcome bonus (5%)", appliedDiscounts); - - // 3. New customer volume commitment: Encouraging larger first orders - if (order.Items.Count >= 5) - { - discountPercent = SafeAddDiscount(discountPercent, 4, "First-order volume bonus (4%)", appliedDiscounts); - - // 4. Premium payment method: Financial service onboarding - if (order.PaymentMethod == PaymentMethod.PayPal || order.PaymentMethod == PaymentMethod.CreditCard) - { - discountPercent = SafeAddDiscount(discountPercent, 3, "Premium payment newcomer bonus (3%)", appliedDiscounts); - - // 5. High-value first purchase: Premium new customer treatment - if (order.IsHighValueOrder()) - { - discountPercent = SafeAddDiscount(discountPercent, 6, "High-value newcomer bonus (6%)", appliedDiscounts); - - // 6. Premium zone shipping: Geographic expansion incentives - if (order.ShippingRegion == RegionType.PremiumZone) - { - discountPercent = SafeAddDiscount(discountPercent, 4, "Premium zone newcomer bonus (4%)", appliedDiscounts); - - // 7. Express shipping trial: Premium service introduction - if (order.HasExpressShipping) - { - discountPercent = SafeAddDiscount(discountPercent, 3, "Express shipping trial bonus (3%)", appliedDiscounts); - } - } - } - } - } + appliedDiscounts.Add($"Coupon {order.Coupon.Code} expired - no discount"); + Console.WriteLine("Coupon expired. No discount applied."); } + + return discountPercent; } - // 1. Coupon validation and application: Secondary discount layer - if (order.Coupon != null) + if (order.Coupon.Type == "percent") { - // 2. Valid coupon: Apply coupon benefits with membership multipliers - if (order.Coupon.IsValid) + decimal couponValue = Math.Max(0, Math.Min(50, order.Coupon.Value)); + + if (user.Membership == MembershipLevel.Premium) { - // 3. Percentage discount coupon: Membership-enhanced coupon benefits - if (order.Coupon.Type == "percent") + couponValue = Math.Min(50, couponValue * 1.3m); + appliedDiscounts.Add($"Premium-enhanced coupon {order.Coupon.Code} ({couponValue:F1}%)"); + + if (order.ActiveEvent == SeasonalEvent.BlackFriday) { - decimal couponValue = Math.Max(0, Math.Min(50, order.Coupon.Value)); // Security: Cap coupon at 50% - - // 4. Membership coupon enhancement: Tier-based coupon boosts - if (user.Membership == MembershipLevel.Premium) + couponValue = Math.Min(55, couponValue + 5); + appliedDiscounts.Add("Black Friday premium coupon boost (5%)"); + + if (user.IsCorporateAccount && order.PaymentMethod == PaymentMethod.BankTransfer) { - couponValue = Math.Min(50, couponValue * 1.3m); // 30% coupon boost for Premium, capped at 50% - appliedDiscounts.Add($"Premium-enhanced coupon {order.Coupon.Code} ({couponValue:F1}%)"); - - // 5. Seasonal coupon stacking: Event-based premium coupon benefits - if (order.ActiveEvent == SeasonalEvent.BlackFriday) + couponValue = Math.Min(60, couponValue * 1.15m); + appliedDiscounts.Add($"Corporate payment multiplier (total: {couponValue:F1}%)"); + + if (order.IsBulkOrder) { - couponValue = Math.Min(55, couponValue + 5); // Black Friday premium coupon boost, capped at 55% - appliedDiscounts.Add("Black Friday premium coupon boost (5%)"); - - // 6. Corporate payment optimization: B2B financial processing benefits - if (user.IsCorporateAccount && order.PaymentMethod == PaymentMethod.BankTransfer) - { - couponValue = Math.Min(60, couponValue * 1.15m); // 15% corporate payment multiplier, capped at 60% - appliedDiscounts.Add($"Corporate payment multiplier (total: {couponValue:F1}%)"); - - // 7. Bulk order corporate: Large-scale business benefits - if (order.IsBulkOrder) - { - couponValue = Math.Min(65, couponValue + 2); // Bulk corporate bonus, capped at 65% - appliedDiscounts.Add("Bulk corporate bonus (2%)"); - } - } + couponValue = Math.Min(65, couponValue + 2); + appliedDiscounts.Add("Bulk corporate bonus (2%)"); } } - else if (user.Membership == MembershipLevel.Gold) - { - couponValue = Math.Min(40, couponValue * 1.2m); // 20% coupon boost for Gold, capped at 40% - appliedDiscounts.Add($"Gold-enhanced coupon {order.Coupon.Code} ({couponValue:F1}%)"); - } - else - { - appliedDiscounts.Add($"Coupon {order.Coupon.Code} ({couponValue}%)"); - } - - discountPercent = SafeAddDiscount(discountPercent, couponValue, "", appliedDiscounts, false); - } - // 3. Free shipping coupon: Enhanced shipping benefits - else if (order.Coupon.Type == "shipping") - { - if (order.IsDomestic || user.Membership == MembershipLevel.Premium) - { - shippingCost = 0; - appliedDiscounts.Add($"Free shipping coupon {order.Coupon.Code}"); - } } } - // 2. Expired coupon: Handle invalid coupon state - else if (order.Coupon.IsExpired) + else if (user.Membership == MembershipLevel.Gold) { - appliedDiscounts.Add($"Coupon {order.Coupon.Code} expired - no discount"); - Console.WriteLine("Coupon expired. No discount applied."); + couponValue = Math.Min(40, couponValue * 1.2m); + appliedDiscounts.Add($"Gold-enhanced coupon {order.Coupon.Code} ({couponValue:F1}%)"); + } + else + { + appliedDiscounts.Add($"Coupon {order.Coupon.Code} ({couponValue}%)"); + } + + return SafeAddDiscount(discountPercent, couponValue, "", appliedDiscounts, false); + } + + if (order.Coupon.Type == "shipping") + { + if (order.IsDomestic || user.Membership == MembershipLevel.Premium) + { + shippingCost = 0; + appliedDiscounts.Add($"Free shipping coupon {order.Coupon.Code}"); } } - // 1. Bulk purchase incentive: Volume-based discount with category considerations + return discountPercent; + } + + private static decimal ApplyBulkDiscounts(Order order, decimal discountPercent, List appliedDiscounts) + { if (order.Items.Count >= 20) { - discountPercent = SafeAddDiscount(discountPercent, 8, "Major bulk purchase (8%)", appliedDiscounts); + return SafeAddDiscount(discountPercent, 8, "Major bulk purchase (8%)", appliedDiscounts); } - else if (order.Items.Count >= 10) + + if (order.Items.Count >= 10) { - discountPercent = SafeAddDiscount(discountPercent, 5, "Bulk purchase (5%)", appliedDiscounts); + return SafeAddDiscount(discountPercent, 5, "Bulk purchase (5%)", appliedDiscounts); } - // Security: Final discount validation - discountPercent = Math.Min(discountPercent, MAX_DISCOUNT_PERCENT); - - // Apply final calculations with category-specific rules - var finalCalculation = ApplyCategorySpecificDiscounts(baseTotal, discountPercent, order); - decimal finalPrice = Math.Max(MIN_FINAL_PRICE, finalCalculation.finalPrice + shippingCost); - - // Display results - Console.WriteLine($"Base Total: ${baseTotal:F2}"); - Console.WriteLine($"Applied Discounts: {string.Join(", ", appliedDiscounts)}"); - Console.WriteLine($"Total Discount: {discountPercent:F1}% (Electronics capped at 15%)"); - Console.WriteLine($"Shipping Cost: ${shippingCost:F2}"); - Console.WriteLine($"Final Price: ${finalPrice:F2}"); + return discountPercent; } ///