using Ophelias.Expressions; using Ophelias.Managers; using Ophelias.Models; using Ophelias.Reporting; using System.Data.SQLite; internal class Program { private static string GetGuestEmail() { /* * This is a function that previously resided * within the GuestMode(), however this functionality * has been moved out as the AdminMode() relies on it * since this essentially is a email validation prompt. */ Console.Write("Specify email: "); string Email = ""; while (!Validation.ValidateEmail(Email)) { Email = Console.ReadLine(); if (!Validation.ValidateEmail(Email)) { Console.Write("Please enter a valid email: "); } } return Email; } private static void GuestMode() { /* * Guest mode is meant for guests to create an account and make a reservation. * Specifically this mode allows for guests to create an account containing their * personal and payment information, create a reservation, manage that reservation, and/ or * pay for that reservation. */ Reservation? activeReservation = null; // Holds the current reservation if one is loaded Guest? activeGuest = null; // Holds the active guest if one logs in void help() { /* * Used to inform users on what the commands do. See * the main while loop in GuestMode() for more details * on what these mean. */ Console.WriteLine( "Reservation Commands:\n" + "\treservation create - Create a new reservation.\n" + "\treservation update - Update your active reservation.\n" + "\treservation cancel - Cancel a reservation.\n" + "\treservation pay - Pays in full for an active reservation.\n" + "Account Commands:\n" + "\taccount create - Create a new guest account.\n" + "\taccount update - Update your account information.\n" + "\taccount login - Log into your guest account." + "Enter Q or q to quit.\n" ); return; } (string?, string?, string?) GetCreditCardInformation() { /* * This function attempts to gather creditcard information. * Since we need all three pieces, card number, expiration, and CCV * we provide options to quit for which the information is discarded * and null values are returned. If valid inputs are input and all * three pieces are collected, they are returned. */ Console.Write("What is your credit card number: "); string CreditCard = ""; while (!Validation.ValidateCreditCard(CreditCard)) { CreditCard = Console.ReadLine().Trim().Replace("\t", ""); if (CreditCard == "q" || CreditCard == "Q") { return (null, null, null); } if (!Validation.ValidateCreditCard(CreditCard)) { Console.Write("Please enter a valid credit card. If your card is expired, enter Q to cancel: "); } } Console.Write("What is your credit card expiration date (MM/yy): "); string CardExpiration = ""; while (!Validation.ValidateExpirationDate(CardExpiration)) { CardExpiration = Console.ReadLine().Trim().Replace("\t", ""); if (CardExpiration == "q" || CardExpiration == "Q") { return (null, null, null); } if (!Validation.ValidateExpirationDate(CardExpiration)) { Console.Write("Please enter a valid expiration date. If your card is expired, enter Q to cancel: "); } } Console.Write("What is your credit card CCV: "); string CCV = ""; while (!Validation.ValidateCCV(CCV)) { CCV = Console.ReadLine().Trim().Replace("\t", ""); if (CCV == "q" || CCV == "Q") { return (null, null, null); } if (!Validation.ValidateCCV(CCV)) { Console.Write("Please enter a valid credit card CCV. If your card is expired, enter Q to cancel: "); } } return (CreditCard, CardExpiration, CCV); } (string, string) GetGuestName() { /* * This function is a series of while loops that will attempt * to collect a users first and last name, these are then returned * to the call location. */ Console.Write("What is your first name: "); string FirstName = ""; while (FirstName.Length == 0) { FirstName = Console.ReadLine(); if (FirstName == "") { Console.Write("What is your first name: "); } } Console.Write("What is your last name: "); string LastName = ""; while (LastName.Length == 0) { LastName = Console.ReadLine(); if (LastName == "") { Console.Write("What is your last name: "); } } return (FirstName, LastName); } void GuestLogin() { /* * This function will attempt to login using an email specified by * the guest. We use email because they are designated as unique * identifiers and no two users can have the same email. For that reason * we use emails to login rather than collecting multiple pieces of * information or returning a list of accounts to scroll through. */ Console.Write("\nEnter your email address: "); string email = ""; while (!Validation.ValidateEmail(email)) { /* * This loop attepts to get a valid email. If there is no valid * formatted email it will keep trying. */ email = Console.ReadLine(); if (!Validation.ValidateEmail(email)) { Console.Write("Please enter a valid email: "); } } activeGuest = Hotel.GetGuestByEmail(email); if (activeGuest == null) { Console.WriteLine($"\nNo account was found with the email {email}."); return; } Console.WriteLine($"\nYou have logged into {activeGuest.FirstName} {activeGuest.LastName} ({email})."); activeReservation = Hotel.GetResByGuest(activeGuest); } void CreateNewGuestPrompt() { /* * This prompt allows guest to create a new account using functionality that * has been split into other functions. See those functions for details on how * they work. */ activeReservation = null; activeGuest = null; (string FirstName, string LastName) = GetGuestName(); string Email = GetGuestEmail(); activeGuest = Hotel.GetGuestByEmail(Email); if (activeGuest != null) { Console.WriteLine($"\nThere is already a guest with the email {Email}."); return; } Console.Write("Would you like to enter your credit card details? (Y/n): "); string? CreditCard = null; string? CardExpiration = null; string? CCV = null; while (true) { string input = Console.ReadLine(); if (input.Equals("y") || input.Equals("Y")) { (CreditCard, CardExpiration, CCV) = GetCreditCardInformation(); break; } else if (input.Equals("n") || input.Equals("N")) { break; } else { Console.Write("Input must be Y, y or N, n: "); } } activeGuest = new(FirstName, LastName, Email, CreditCard: CreditCard, Expiration: CardExpiration, CCV: CCV); Console.Write($"You are now logged in as {FirstName} {LastName} ({Email})"); } void UpdateGuestInformation() { /* * This function allows a guest, if logged in, to update any piece of their * personal information. This includes name, email, and creditcard information. * For more information on how this gets set on the backend, see UpdateGuest() * in the Guest model. */ if (activeGuest == null) { Console.WriteLine("No guest is currently logged in, please login."); return; } string? NewFname = null, NewLname = null, NewEmail = null, NewCard = null, NewExpiry = null, NewCCV = null; void SavePrompt() { /* * This save prompt is just to inform users of their changes and show them * what information gets updated. Also calls the UpdateGuest() function to * update the database. */ if (NewFname == null && NewLname == null && NewEmail == null && NewCard == null && NewExpiry == null && NewCCV == null) { Console.WriteLine("No changes have been made."); return; } string changes = ""; List NewDetails = new(); if (!string.IsNullOrEmpty(NewFname)) { NewDetails.Add($"\tFirst Name: {activeGuest.FirstName} -> {NewFname}"); activeGuest.FirstName = NewFname; } if (!string.IsNullOrEmpty(NewLname)) { NewDetails.Add($"\tLast Name: {activeGuest.LastName} -> {NewLname}"); activeGuest.LastName = NewLname; } if (!string.IsNullOrEmpty(NewEmail)) { NewDetails.Add($"\tEmail: {activeGuest.Email} -> {NewEmail}"); activeGuest.Email = NewEmail; } if (!string.IsNullOrEmpty(NewCard)) { NewDetails.Add($"\tCredit Card: {activeGuest.CreditCard} -> {NewCard}"); activeGuest.CreditCard = NewCard; } if (!string.IsNullOrEmpty(NewExpiry)) { NewDetails.Add($"\tCard Expiration Date: {activeGuest.Expiration} -> {NewExpiry}"); activeGuest.Expiration = NewExpiry; } if (!string.IsNullOrEmpty(NewCCV)) { NewDetails.Add($"\tCard CCV: {activeGuest.CCV} -> {NewCCV}"); activeGuest.CCV = NewCCV; } if (NewDetails.Count > 0) { changes += string.Join("\n", NewDetails); } Console.WriteLine($"The following changes have been made:\n {changes}"); activeGuest.UpdateGuest(activeGuest.Id, NewFname, NewLname, NewEmail, NewCard, NewExpiry, NewCCV); activeReservation.Guest = activeGuest; return; } Console.WriteLine($"You are currently logged in as {activeGuest.FirstName} {activeGuest.LastName} ({activeGuest.Email}).\n" + $"If this is not your account, please (Q)uit and log into your account. Changes will be recorded, but not saved\n" + $"until you enter S.\n"); do { /* * This loop traps users until the enter one of the cases which results * in updating a piece of information, saving their changes, or discarding * them. */ Console.Write("Please select the option you would like to change or enter S to save:\n" + "1. First Name and Last Name\n" + "2. Email Address\n" + "3. Credit Card Information\n"); switch (Console.ReadLine()) { case "q": return; case "Q": return; case "1": (NewFname, NewLname) = GetGuestName(); break; case "2": NewEmail = GetGuestEmail(); break; case "3": (NewCard, NewExpiry, NewCCV) = GetCreditCardInformation(); break; case "S": SavePrompt(); return; default: break; } } while (true); } ReservationType SelectReservation() { /* * This function was broken out to allow it to be reused in multiple sections. * It prompts a user for their selection of reservation type and that type * is then returned. We subtract 1 from the type since we start our input * options at 1 instead of 0 (Enums start at 0). */ string input = ""; Console.Write("What kind of reservation would you like to make?\n" + "1. Conventional\n" + "2. Prepaid\n" + "3. 60 Day Advance\n" + "4. Incentive\n" + "Input a number: "); while (true) { input = Console.ReadLine(); if (input == "1" || input == "2" || input == "3" || input == "4") { break; } Console.Write("Please enter either 1, 2, 3, or 4: "); } return (ReservationType)(Convert.ToInt32(input) - 1); } bool CheckReservationRestrictions(DateTime Date, ReservationType Type) { /* * This function checks to see if there are reservation restrictions. * Prepaid reservations must be made 90 days ahead of the start date * and 60-day 60 days. This function informs the user if their reservation * does not adhere to these constraints. */ if (Type == ReservationType.Prepaid) { if ((int)(Date - DateTime.Now).TotalDays < 90) { Console.WriteLine("Prepaid reservations must be made 90 days in advance."); return false; } else { return true; } } else if (Type == ReservationType.SixtyDayAdvance) { if ((int)(Date - DateTime.Now).TotalDays < 60) { Console.WriteLine("Sixty-days-in-advance reservations must be made 60 days in advance."); return false; } else { return true; } } return true; } (DateTime?, DateTime?) SelectDate(ReservationType Type) { (DateTime, DateTime) Dates() { string input = ""; DateTime _StartDate; DateTime _EndDate; Console.Write("When would you like to begin your stay.\n" + "Your date input should be in in the following format - yyyy-MM-dd. Example: (2021-12-31)?\n" + "Input a date (2021-12-31): "); while (true) { input = Console.ReadLine(); if (DateTime.TryParse(input, out _StartDate) && _StartDate >= DateTime.Now) { break; } if (_StartDate <= DateTime.Now) { Console.Write("Start date cannot be before current date. Please enter a valid date (2021-12-31): "); } else { Console.Write("Please enter a valid date (2021-12-31): "); } } Console.Write("When would you like to end your stay.\n" + "Your date input should be in in the following format - yyyy-MM-dd. Example: (2021-12-31)?\n" + "Input a date (2021-12-31): "); while (true) { input = Console.ReadLine(); if (DateTime.TryParse(input, out _EndDate) && _EndDate > _StartDate) { break; } if (_EndDate < _StartDate) { Console.Write("End date must be after start date. Please enter a valid date (2021-12-31): "); } else { Console.Write("Please enter a valid date (2021-12-31): "); } } return (_StartDate.Date, _EndDate.Date); } DateTime StartDate; DateTime EndDate; string input; while (true) { bool maxCapacity = false; (StartDate, EndDate) = Dates(); if (StartDate == null || EndDate == null) { (_, maxCapacity) = Hotel.AvgOccupancySpan(StartDate, EndDate); } if (!maxCapacity) { if (!CheckReservationRestrictions(StartDate, Type)) { Console.Write("Do you want to quit? Type YES to quit and discard, enter anything or nothing to select another date range.\n" + ": "); input = Console.ReadLine(); if (input == "YES") { Console.WriteLine("Aborting reservation changes."); return (null, null); } } else { break; } } else { Console.WriteLine("Your reservation covers a range where we are already at max capacity, please change your reservation dates."); } } return (StartDate, EndDate); } void CreateNewReservation() { /* * Allows users with no existing reservation to make a new reservation. * If a user is not logged in, they will not be able to register either. * The reservation will collect all the necessary information depending * on requirements set by the reservation. Specifically if the reservation * is anything but 60-day-in-advance, the creditcard is required. If users * do not want to proceed, the are free to cancel and abort the creation * of their reservation. */ if (activeGuest == null) { Console.WriteLine("No guest is currently logged in, please login."); return; } if (activeReservation != null) { Console.WriteLine("You currently have an active registration."); return; } if (Hotel.GetBaseRate() == null) { Console.WriteLine("Unable to proceed with reservation due to no base rate being set. Please inform an employee."); return; } string input; ReservationType Type; DateTime? StartDate; DateTime? EndDate; Type = SelectReservation(); (StartDate, EndDate) = SelectDate(Type); if (StartDate == null || EndDate == null) { Console.WriteLine("Aborting reservation creation."); return; } string? cc, exp, ccv; while ((Type != ReservationType.SixtyDayAdvance && (activeGuest.CreditCard == null || activeGuest.Expiration == null || activeGuest.CCV == null))) { Console.Write("The reservation type you chose requires you to specify your credit card.\n" + "If you are unable to provide one, enter Q to quit or hit enter to continue.\n" + ": "); input = Console.ReadLine(); if (input == "Q") { return; } (cc,exp,ccv) = GetCreditCardInformation(); if (cc != null && exp != null && ccv != null) activeGuest.UpdateGuest(activeGuest.Id, CreditCard: cc, Expiration: exp, CCV: ccv); } Console.Write($"Thank you for filling out your reservation details. Currently you have made a {Type} " + $"reservation and are scheduled to stay from {StartDate.Value.ToString("yyyy-MM-dd")} to {EndDate.Value.ToString("yyyy-MM-dd")}." + $"If these details are correct, enter YES to complete. To cancel your reservation enter Q.\n" + $": "); input = Console.ReadLine(); while (input != "YES") { if (input == "Q") { Console.WriteLine("Reservation has been deleted."); return; } else { Console.Write("Input must be YES or Q.\n: "); input = Console.ReadLine(); } } if (Type == ReservationType.Prepaid) // Creates a new reservation and set it as the active. Also pays for the reservation if the reservation type was prepaid. { activeReservation = new(activeGuest, Type, DateTime.Now.Date, StartDate.Value, EndDate.Value); activeReservation.Transaction.Pay(activeReservation.Transaction.Owed, activeGuest.CreditCard); } else // Creates a new reservation and set it as the active. { activeReservation = new(activeGuest, Type, DateTime.Now.Date, StartDate.Value, EndDate.Value); } Console.WriteLine("Your reservation has been made."); } void UpdateReservation() { /* * Update reservation allows guests to change their reservation at the * cost of having to pay a new amount based on the current base rate * with a multiplier of 110% or 1.1. If a user opts to not update * their reservation the changes will be discarded. Furthermore the * system prevents people from registering when the hotel is at max * capacity for a certain date. For more details, see the SelectDate() * function. If a user is not logged in, they will not be able to update * their reservation. */ string input = "NO"; if (activeGuest == null) { Console.WriteLine("No guest is currently logged in, please login."); return; } if (activeReservation == null) { Console.WriteLine("You currently do not have an active registration."); return; } Console.Write("Your current reservation details are:\n" + $"\tStarts on: {activeReservation.StartDate.ToString("yyyy-MM-dd")}\n" + $"\tEnds on: {activeReservation.EndDate.ToString("yyyy-MM-dd")}\n"); DateTime? _StartDate = null, _EndDate = null; do { if (input == "NO") { (_StartDate, _EndDate) = SelectDate(activeReservation.Type); if (_StartDate.HasValue && _EndDate.HasValue) { Console.Write("Your new reservation details are:\n" + $"\tStarts on: {activeReservation.StartDate.ToString("yyyy-MM-dd")} -> {_StartDate.Value.ToString("yyyy-MM-dd")}\n" + $"\tEnds on: {activeReservation.EndDate.ToString("yyyy-MM-dd")} -> {_EndDate.Value.ToString("yyyy-MM-dd")}\n"); } Console.Write("Are you done with your changes?\n" + "Enter YES to finish and save, NO to continue editing.\n" + ": "); } else if (input == "Q") { Console.WriteLine("Your changes have been discarded."); return; } else { Console.WriteLine("Input must be YES, NO, or Q."); Console.Write(": "); } input = Console.ReadLine(); } while (input != "YES"); if (_StartDate != null && _EndDate != null) { activeReservation.ChangeReservationDates((DateTime)_StartDate, (DateTime)_EndDate); Console.WriteLine("Your changes have been saved."); } return; } void CancelReservation() { /* * This function will provide guests with the option to cancel their reservation * or abort if they decide they do not want to cancel. If the reservation is cancelled, * the reservation cancellation function CancelReservation() is called on the reservation * and the database is updated accordingly. See CancelReservation() for more details. * If a user is not logged in, they will not be able to cancel either. */ if (activeGuest == null) { Console.WriteLine("No guest is currently logged in, please login."); return; } if (activeReservation == null) { Console.WriteLine("You currently do not have an active registration."); return; } string input; Console.Write("Would you like to cancel your reservation?\n" + "You may be charged depending on your reservation.\n" + "Enter YES to confirm or NO to exit: "); input = Console.ReadLine(); while (input != "YES") { if (input == "NO") { Console.Write("Reservation has not been cancelled."); return; } else { Console.Write("Your input must be YES or NO: "); Console.ReadLine(); } } activeReservation.CancelReservation(); activeReservation = null; Console.Write("Reservation has been cancelled, you may be charged based on your reservation."); } void PayForReservation() { /* * This function allows for guests to pay for their 60-day-in-advance if they * meet the 30-45 day before start date window. All other reservations are denied * the ability to pay since they have specific points at which they are charged. * If there is no creditcard information, this function will attempt to collect it * and then update the guests information before paying for the transaction. Depending * on various factors, the transaction may be completed or invalid. However * since the current design is to have members pay in full, most of these cases * should be impossible to trigger. Currently they are implemented for future changes * or if they do trigger. */ if (activeGuest == null) { Console.WriteLine("No guest is currently logged in, please login."); return; } if (activeReservation == null) { Console.WriteLine("You currently do not have an active registration."); return; } if (ReservationType.Incentive == activeReservation.Type) { Console.WriteLine("Incentive reservations are paid for when you check-out."); return; } else if (ReservationType.Conventional == activeReservation.Type) { Console.WriteLine("Conventional reservations are paid for when you check-out."); return; } else if (ReservationType.Prepaid == activeReservation.Type) { Console.WriteLine("Prepaid reservations are paid for when you create them."); return; } else if (ReservationType.SixtyDayAdvance == activeReservation.Type) { /* * Checks to see if the reservation has been paid for are is past the latest possible * payment date. If it has been payed, the user will be informed, if it is past the * payment collection date, the reservation is cancelled and user is notified. */ if(activeReservation.StartDate.AddDays(-30).Date < DateTime.Now.Date) { if (activeReservation.Transaction.Owed <= activeReservation.Transaction.AmountPaid) { Console.WriteLine("Your reservation has already been paid for."); return; } activeReservation.CancelReservation(); Console.WriteLine("Your reservation has been cancelled because you failed to pay within the payment period."); return; } } string? cc, exp, ccv; cc = activeGuest.CreditCard; exp = activeGuest.Expiration; ccv = activeGuest.CCV; while (true) { /* * This loop traps the user until they either choose to pay or not. * If the card details they specify are invalid at any point within the latest * date of the payment, we choose to reject that as it theoretically could cause * problems. In this hypothetical situation is would not but in the real world it * might depending on the card vendor. If a user choses to abort payment, the function * will discard any changes to that guests payment information if changes were made. * In the case where a customers payment information is already valid, they will pay for * the reservation in full without any prompts. */ if (cc == null || exp == null || ccv == null) // Trigger to get new card information { Console.Write("Would you like to try again and enter your payment information? (Your reservation will not be canceled)\n" + "YES or NO: "); while (true) { string input = Console.ReadLine(); if (input == "YES") { break; } else if (input == "NO") { Console.WriteLine("Payment has been aborted."); return; } else { Console.WriteLine("Input must be YES or NO."); } } (cc, exp, ccv) = GetCreditCardInformation(); // See for more information on how input is collected. } else if (cc != null && exp != null && ccv != null) { /* * If the creditcard is not null, this section checks to see if payment can be made. * If payment can be made the guest information is updated and an attempt to pay is made. * The payment can result in any of the following: * * Successful * Payment was made and recorded * * Failed * An error occurred and the staff should be notified * * Attempt to pay $0 or less * Self explanatory * * Missing card information * If the card is missing payments cannot be made * * Already Paid * The payment has already been paid at an earlier date * or due to some other factor, therefore there was no need to charge. */ if (ReservationType.SixtyDayAdvance == activeReservation.Type && ((DateTime.Parse(exp).Date < activeReservation.StartDate.AddDays(-30).Date) && (DateTime.Parse(exp).Date < activeReservation.StartDate.AddDays(-45).Date))) { Console.WriteLine("Your card expires too soon. Please enter the correct expiration date if incorrect or a new card."); cc = null; exp = null; ccv = null; } else if (ReservationType.SixtyDayAdvance == activeReservation.Type && ((DateTime.Parse(exp).Date > activeReservation.StartDate.AddDays(-30).Date) && (DateTime.Parse(exp).Date > activeReservation.StartDate.AddDays(-45).Date))) { if (cc != activeGuest.CreditCard && exp != activeGuest.Expiration && ccv != activeGuest.CCV) { activeGuest.UpdateGuest(activeGuest.Id, CreditCard: cc, Expiration: exp, CCV: ccv); activeReservation.Guest = activeGuest; } PaymentStatus ps = activeReservation.Transaction.Pay(activeReservation.Transaction.Owed, activeGuest.CreditCard); if (ps == PaymentStatus.SuccessfulPayment) { Console.WriteLine("Your reservation has been paid for."); return; } else if (ps == PaymentStatus.FailedPayment) { Console.WriteLine("There was an error paying for your resevation. Try again later or contact the Ophelias Oasis team."); return; } else if (ps == PaymentStatus.AmountCannotZero) { Console.WriteLine("You cannot pay $0 or less than $0."); return; } else if (ps == PaymentStatus.MissingCreditCard) { Console.WriteLine("Credit Card information is missing. Payment could not be made."); } else if (ps == PaymentStatus.AlreadyPaid) { Console.WriteLine("Your reservation has already been paid for."); return; } } } } } Console.Write( "\nWelcome to the Ophelias Oasis Hotel Registration System!\n" + "Type help to get a full list of commands or enter a command.\n" + "Command: " ); while (true) { /* * This is the main administrator loop. * The following are valid operations: * * reservation create: * Creates a new reservation with the information specified by the guest. * For more information see the CreateNewReservation() function. * * reservation update: * Updates an existing reservation if there is one. If there is no existing * reservation the function will immediately return. To get more information * see the UpdateReservation() function. * * reservation cancel: * Cancels a reservation. If the reservation is subject to penalties, the * guest will be charged. For more infromation, see CancelReservation(). * * reservation pay: * Enables 60-day-in-advance reservees to pay for their reservations if * they are within the valid payment window. Other reservation types will * be blocked. For more details, see PayForReservation(). * * account create: * Enables guests to create a new account. See CreateNewGuestPrompt for * more details on account creation. * * account update: * Update an existing guests information, such as name, email, and * creditcard information. For more details on how this is handled, * see UpdateGuestInformation(). * * account login: * Logs into an account using a guests email if they have an account. * For more details see GuestLogin(). * * Q and q: * Returns to the main loop for choosing either the Guest or Admin flow. */ Hotel.CheckBaseRate(); string? input = Console.ReadLine(); switch (input) { case "help": help(); break; // Gets help text case "reservation create": CreateNewReservation(); break; // Allows guests to create a reservation case "reservation update": UpdateReservation(); break; // Allows guests to update a reservation if they have one case "reservation cancel": CancelReservation(); break; // Allows guests to cancel a reservation if they have one case "reservation pay": PayForReservation(); break; // Allows guests with 60-day reservations to pay for it case "account create": CreateNewGuestPrompt(); break; // Creates a new guest case "account update": UpdateGuestInformation(); break; // Updates a guests personal and/ or payment information case "account login": GuestLogin(); break; // Logs a guest in case "q": return; // Quit to mode loop case "Q": return; // Quit to mode loop default: Console.WriteLine("Unknown command, enter help for more inforamtion."); break; } Console.Write("\nCommand: "); } } private static void AdminMode() { /* * Admin mode is meant for employees. There is no authetication or security measures * for this class as the design specification did not include that and for the goals * of this project, security was not to be a concern. Realistically this would be * handled differently and securely. * * The pupose of this class is to provide employees with the ability to perform * specific operations such as checking/ checking out a guest, generating * reports that management needs to understand how the hotel is operating, * a system to set the hotels rates, and the ability to issue penalties and/ or * notify outstanding payments. For more details on how each of these components work, * see the documentation within their functions. */ void help() { /* * Help text to understand commands. */ Console.WriteLine( "Report Commands:\n" + "\tgenerate management report - Generates a daily management report on expected occupancy, room income, and incentive losses.\n" + "\tgenerate operational report - Generates a report of daily arrivals and occupancy.\n" + "\tgenerate accommodation bills - Generates an accommodation bill that will be handed to guests upon checkout.\n" + "\nManagement Commands:\n" + "\tnotify pending payments - Generates and emails 60 day advance reservations that they must pay for their reservation or it will be cancelled.\n" + "\tissue penalties - Issues penalties for guests that are no shows.\n" + "\tcheckin guest - Checks in a guest and assigns them a room.\n" + "\tcheckout guest - Checks out a guest and removes their room assignment.\n" + "\tset rate - Sets a new base rate to begin after a specified date.\n" + "\nEnter Q or q to quit.\n" ); return; } void SetFutureBaseRate() { /* * Allows employees to configure a future base rate. See SetRatePrompt() for the * details as it was broken out into a nested function. */ bool SetRatePrompt(DateTime? FixedDate = null) { /* * This function promopts an employee with a base rate configuration prompt. * If this is the first time configuring the base rate, in cases where one is not set, * the default rate will be set effective immediately, otherwise the base rate prompt * will require the employee to set the base rate sometime in the future. The accepted inputs * are checked against the function ValidateMoney() which basically checks for how money is typically * represented in the United States. */ string input; string amount; amount = Console.ReadLine(); if (Validation.ValidateMoney(amount)) { Console.Write($"Is this the correct base rate {amount}?\n" + $"Enter YES or NO: "); input = Console.ReadLine(); while (input != "YES") { if (input == "NO") { break; } else { Console.Write("Input must be YES or NO: "); } input = Console.ReadLine(); } if (input == "YES") { if (FixedDate != null) { if (Hotel.GetBaseRateByDate((DateTime)FixedDate)) { Console.Write("There is already a base rate configured for this date.\n" + "If you are entering a new rate because one was not configured and got this error, type YES.\n" + "Would you like to overwrite it (YES)? If not enter anything to continue.\n" + ": "); if (Console.ReadLine() == "YES") { Hotel.UpdateBaseRate(Convert.ToDouble(amount), FixedDate.Value.Date, DefaultRate: true); // Replaces the existing base rate with a new value if the date matches. Also marks as default. return true; } } else { Hotel.SetBaseRate(Convert.ToDouble(amount), (DateTime)FixedDate, true); // Sets the base rate as the default. } Console.WriteLine($"A base rate of {amount} has been set and will take effect immediately."); } else { DateTime StartDate; Console.Write("When would you like the new rate to take effect.\n" + "Your date input should be in in the following format - yyyy-MM-dd. Example: (2021-12-31)?\n" + "Input a date (2021-12-31): "); while (true) { input = Console.ReadLine(); if (DateTime.TryParse(input, out StartDate)) { if (StartDate.Date <= DateTime.Now.Date) { Console.WriteLine("Base rate must be configured for a future night. Not the same day or past."); } else if (Hotel.GetBaseRateByDate(StartDate)) { Console.Write("There is already a base rate configured for this date.\n" + "Would you like to overwrite it (YES)? If not enter anything to continue." + ": "); if (Console.ReadLine() == "YES") { Hotel.UpdateBaseRate(Convert.ToDouble(amount), StartDate.Date); // Replaces the existing base rate with a new value if the date matches. return true; } } else { break; } } Console.Write("Please enter a valid date (2021-12-31): "); } Hotel.SetBaseRate(Convert.ToDouble(amount), StartDate); // Sets the base rate as a future base rate. Console.WriteLine($"A base rate of {amount} has been set and will take effect on {DateTime.Now.Date.ToString("yyyy-MM-dd")}."); } return true; } } else { Console.WriteLine("Your input was not a valid input, an example of a valid input would be 100 or 100.00."); } Console.Write("Enter new rate: "); return false; } if (Hotel.GetBaseRate() == null) { // This is for the specific case where there is no configured/ default rate. Console.Write("No base rate has been configured. " + "You must set one for the current date.\n" + "Enter new rate: "); while (!SetRatePrompt(DateTime.Now.Date)) { } // Continues to loop until a base rate is successfully configured. } else { Console.Write("What is the value of the rate you would like to set.\n" + "Enter new rate: "); while (!SetRatePrompt()) { } // Continues to loop until a base rate is successfully configured. } } void CheckIn() { /* * Checks in a guest if they have an existing reservation that can be checked in. * If not the function informs the employee that no reservation exists or no guest * guest account could be found. This function will also assign the guest their room * number or cancel their reservation if they attempt to check in after their start date * as the policy calls for "no-shows" to be charged and therefore are canceled due to a * no refund policy. Guests also cannot check in ahead of time, only on the day of. */ string Email = GetGuestEmail(); TimeRefs? status = Hotel.CanBeCheckedIn(Email); Guest? g = Hotel.GetGuestByEmail(Email); if (g == null) { Console.WriteLine("No guest with that email exists."); return; } if (!status.HasValue) { Console.WriteLine("No reservation exists for this email."); return; } if (status.Value == TimeRefs.OnTime) { int? RoomID = Hotel.CheckInGuest(Email, DateTime.Now.Date); if (RoomID != null) { Console.WriteLine($"Guest has been checked in, their room number is {RoomID}."); } } else if (status.Value == TimeRefs.Late) { Console.WriteLine("Since the reservation was not checked in on the date scheduled, it has been cancelled. The guest will be charged the \"no show\" penalty."); Reservation? r = Hotel.GetResByGuest(g); if (r == null) { throw new Exception(); } r.CancelReservation(); } else if (status.Value == TimeRefs.Early) { Console.WriteLine("Since the reservation was not checked in on the date scheduled, it has been cancelled. The guest will be charged the \"no show\" penalty."); } return; } void CheckOut() { /* * Checks out a guest and charges them if they have an incentive or conventional reservation. * For more details on how the data is manipulated, see Pay() and CheckOutGuest(). While * there are null refs warnings here, they should never reach this point * so we choose not to handle them. */ string Email = GetGuestEmail(); if (Hotel.GuestCurrentlyCheckedIn(Email)) { Reservation r = Hotel.GetResByGuest(Hotel.GetGuestByEmail(Email)); if (r.Type == ReservationType.Incentive || r.Type == ReservationType.Conventional) { r.Transaction.Pay(r.Transaction.Owed, r.Guest.CreditCard); } Hotel.CheckOutGuest(Email, DateTime.Now.Date); Console.WriteLine($"Guest has been checked out."); } else { Console.WriteLine($"Can't checkout a guest that hasn't been checked in."); } return; } void NotifyOutstandingPayments() { /* * This function is used to notify any 60-day-in-advance reservation that they have a payment * due. If the payment is past the number of days then the reservation is canceled. If the * reservation is within the 30-45 day window then an email is sent out for a customer to * pay through the payment system. For more details on how the email works, see the Email class. */ List activeSixtyRes = Hotel.GetActiveSixtyDayRes(); foreach (Reservation r in activeSixtyRes) { int days = (int)(r.StartDate.Date - DateTime.Now.Date).TotalDays - 30; if (days < 0) { r.CancelReservation(); } else if (days <= 15) { Email e = new(r.Guest.Email); e.Send(); } } if (File.Exists("Emails.txt") && activeSixtyRes.Count != 0) { FileInfo f = new("Emails.txt"); Console.WriteLine($"Emails have been written and appended to {f.FullName}"); } else { Console.WriteLine("There are no emails to send."); } } void GenerateManagementReports() { /* * GenerateManagementReports is used to generate the following reports: * * - Expected Occupancy * Reports the expected hotel occupancy and relies on GetExpectedOccupancyCount() * for the data used in reporting. See CalculateExpectedOccupancy() for more details * on the output. * * - Expected Income * Reports the expected income for the hotel and relies on GetExpectedIncomeCount() * for the data used in reporting. See CalculateExpectedIncome() for more details * on the output. * * - Incentive Losses * Reports the revenue lost as a result of providing incentives and relies on * GetIncentiveTransactions() for the data used in reporting. See CalculateIncentiveLosses() * for more details on the output. */ var w = Hotel.GetExpectedOccupancyCount(); Management.CalculateExpectedOccupancy(w.Item2, w.Item1); if (File.Exists("ExpectedOccupancy.txt")) { FileInfo f = new("ExpectedOccupancy.txt"); Console.WriteLine($"Expected occupancy has been written and appended to {f.FullName}"); } var x = Hotel.GetExpectedIncomeCount(); Management.CalculateExpectedIncome(x.Item1, x.Item2, x.Item3); if (File.Exists("ExpectedIncome.txt")) { FileInfo f = new("ExpectedIncome.txt"); Console.WriteLine($"Expected income has been written and appended to {f.FullName}"); } var y = Hotel.GetIncentiveTransactions(); Management.CalculateIncentiveLosses(y.Item3, y.Item1, y.Item2); if (File.Exists("IncentiveLosses.txt")) { FileInfo f = new("IncentiveLosses.txt"); Console.WriteLine($"Incentive losses have been written and appended to {f.FullName}"); } } void GenerateAccommodationBills() { /* * This generates a single report called accommodation bills. If there * are no reservations there will be no accomodation bills to generate. * The reservations are returned by GetDailyArrivals(). */ List reservations = Hotel.GetDailyArrivals(); if (reservations.Count == 0) { Console.WriteLine($"No accommodation bills to generate."); return; } Accommodation.GenerateAccommodationBills(reservations); if (File.Exists("AccommodationBills.txt")) { FileInfo f = new("AccommodationBills.txt"); Console.WriteLine($"Accommodation bills have been written and appended to {f.FullName}"); } } void GenerateOperationalReports() { /* * This generates two operational reports. The daily arrivals and the daily occupancy. * The daily arrival report is generated based off list of reservations returned by * GetDailyArrivals(). This is then passed off to the Operational reporting class * where it writes out to DailyArrivals.txt. THe same applies to the daily occupancy * however the GetDailyOccupancy() function is a litte more complex returning two lists, * one with the occupancy for that day and one with rooms occupied the night before. * If the file(s) are successfully written, the output is printed to the console for * staff to find them. */ List reservations = Hotel.GetDailyArrivals(); if (reservations.Count == 0) { Console.WriteLine($"No daily arrivals to report."); } else { Operational.FetchDailyArriavals(reservations); if (File.Exists("DailyArrivals.txt")) { FileInfo f = new("DailyArrivals.txt"); Console.WriteLine($"Daily arrivals have been written and appended to {f.FullName}"); } } var x = Hotel.GetDailyOccupancy(); // Using var instead of explicity breaking out the tuple since it's simpler if (x.Item1.Count == 0) { Console.WriteLine($"No daily occupancies to report."); } else { Operational.FetchDailyOccupancy(x.Item1, x.Item2); if (File.Exists("DailyOccupancy.txt")) { FileInfo f = new("DailyOccupancy.txt"); Console.WriteLine($"Daily occupancy has been written and appended to {f.FullName}"); } } } void IssuePenalties() { /* * Iterates over a list of reservations returned by GetPastDueReservations(), * a function of the Hotel class. Since all the reservations returned are known * to be past due, the cancellation function is called and the reservation is * canceled and charged if applicable. */ List reservations = Hotel.GetPastDueReservations(); foreach (Reservation reservation in reservations) { reservation.CancelReservation(); } Console.WriteLine("Penalties have been issued."); } Console.Write( "\nWelcome to the Ophelias Oasis Hotel Management System!\n" + "Type help to get a full list of commands or enter a command.\n" + "Command: "); while (true) { /* * This is the main administrator loop. * The following are valid operations: * * generate management report: * Generates a series of report detailing the expected occupancy, expected room income, * and the losses incurred due to incentive reservations. For more information see the * GenerateManagementReport() function. * * generate operational report: * Generates a series of reports detailing the daily arrivals and daily occupancy. * For more information see the GenerateOperationalReports() function. * * generate accommodation bills: * Generates the accommodation bills for reservations arriving that day. These will * be handed out upon checkout (No process for this since there is no printer * connected to this application). For more information see the GenerateAccommodationBills() * function. * * notify pending payments: * Generates an email for any outstanding payments within the payment window for * 60-day-in-advance reservations. For more information see the NotifyOutstandingPayments() * function. * * issue penalties: * Issues penalties to reservations that end up as "no-show". Furthermore, it will * also charge them if applicable. This is mostly just to pick up any reservations * that are not canceled by users or canceled as a result of attempting to checkin * or pay for a reservation that would be market as "no-show". For more information * see the IssuePenalties() function. * * checkin guest: * Checks in the guest and assigns them a room number. For more information see the * CheckIn() function. * * checkout guest: * Checks a guest out and marks their reservation as ended. The room number is * not removed for historical purposes. For more information see the CheckOut() * function. * * set rate: * Set rate allows employees to set a specific rate for a specific day. This * rate takes effect on the date set. For more information see the SetFutureBaseRate() * function. * * Q or Q: * Returns to the main loop for choosing either the Guest or Admin flow. */ Hotel.CheckBaseRate(); string? input = Console.ReadLine(); switch (input) { case "help": help(); break; // Gets help text case "generate management report": GenerateManagementReports(); break; // Generates management reports and outputs to text files case "generate operational report": GenerateOperationalReports(); break; // Generates operational reports and outputs to text files case "generate accommodation bills": GenerateAccommodationBills(); break; // Generates accommodation bills and outputs to a text file case "notify pending payments": NotifyOutstandingPayments(); break; // Generates an email and outputs to a text file case "issue penalties": IssuePenalties(); break; // Cancels "no-show" reservations and charges their accounts case "checkin guest": CheckIn(); break; // Checks a guest in and assigns a room case "checkout guest": CheckOut(); break; // Checks out a guest and marks their reservation as ended case "set rate": SetFutureBaseRate(); break; // Allows employees to configure base rates case "Q": return; // Quit to mode loop case "q": return; // Quit to mode loop default: Console.WriteLine("Unknown command, enter help for more inforamtion."); break; } Console.Write("\nCommand: "); } } private static void Main() { if (!File.Exists("database.sqlite3") || new FileInfo("database.sqlite3").Length == 0) { /* * Checks to see if the databse is created. If the database file is missing, that means * it has not been initialized. Upon the initialization process, the tables are created * and the rooms table is populated with 45 rooms. This is a preconfigured value within * the InitializeRoomsTable() function since there was no requirement to make this configurable. */ SQLiteConnection.CreateFile("database.sqlite3"); using Database Manager = new(); Manager.InitializeTables(); Manager.InitializeRoomsTable(); } if (!Hotel.CheckBaseRate()) { // Checks to see if the base rate is configured, if not a warning will be shown. Console.WriteLine("No base rate is configured. As a result reservations cannot be made until one is configured."); } bool run = true; while (run) { // Main loop for checking if the person is a customer or employee. Console.Write( "Are you an employee or customer?\n" + "1. Employee\n" + "2. Customer/ Guest\n" + ": " ); switch (Console.ReadLine().ToUpper()) // Using .ToUpper() to support "q" without having to write a case for it. { case "1": AdminMode(); break; // Enter employee (administrative) mode case "2": GuestMode(); break; // Enter guest (customer) mode case "Q": run = false; break; // Quit default: Console.WriteLine("You must either specify 1 for Employee, 2 for Customer/ Guest, or Q to quit.\n\n"); break; } } Console.WriteLine("Shutting system down."); } }