Files
ophelias-oasis/OpheliasOasis/Program.cs
雲華 0ce34d9d23 Fixed an incorrect calulation
The calculations used in the reports were calulating based off the
owed column which mathematically did not make sense since we are looking
for the daily.
2022-04-17 17:44:41 -04:00

1386 lines
65 KiB
C#

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<string> 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<Reservation> 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<Reservation> 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<Reservation> 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<Reservation> 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.");
}
}