Merged PR 1110: Fix user management, customer menu, inventory, notification, and validation issues
Changes: - Added validation in AdminMenu::viewStockLevels and improved stock level formatting with headers, column widths, and pressEnter prompts - Enhanced AdminMenu::checkStockAvailability with header, clear screen, and missing item validation - Updated AdminMenu::removeUser with header and excluded ADMIN users from active list - Improved CustomerMenu::completePayments with pending invoice checks, error handling, and clearer headers - Refined selectInvoiceFromUserForPayment in MenuHelper.h (inline, clear screen, updated headers) - Added "View Service History" header in CustomerMenu::viewServiceHistory and widened table columns - Improved notification handling: added headers, empty list validation, pressEnter prompts, and adjusted column widths - Simplified notification titles in Inventory, Payment, and Service Management services - Strengthened UserManagementService::updateUserDetails with duplicate checks (email, phone), clearer errors, and Validator integration - Implemented new duplicate validation functions (isUsernameDuplicate, isPhoneDuplicate, isEmailDuplicate) in Validator utilities - Updated CustomerMenu::updateDetails with header, improved error/success message formatting, and validation integration Related work items: #1646, #1739, #1741, #1742, #1746, #1748, #1750
This commit is contained in:
+1
-1
@@ -323,7 +323,7 @@ void InventoryManagementService::sendNotification(User* user, const std::string&
|
||||
Factory::getObject<Notification>(
|
||||
user->getId(),
|
||||
user,
|
||||
"InventoryManagementService: " + title,
|
||||
title,
|
||||
message,
|
||||
util::Timestamp()
|
||||
);
|
||||
|
||||
+1
-1
@@ -86,7 +86,7 @@ void PaymentManagementService::sendNotification(User* user, const std::string& t
|
||||
Factory::getObject<Notification>(
|
||||
user->getId(),
|
||||
user,
|
||||
"PaymentManagementService: " + title,
|
||||
title,
|
||||
message,
|
||||
util::Timestamp()
|
||||
);
|
||||
|
||||
+1
-1
@@ -175,7 +175,7 @@ void ServiceManagementService::sendNotification(User* user, const std::string& t
|
||||
Factory::getObject<Notification>(
|
||||
user->getId(),
|
||||
user,
|
||||
"ServiceManagementService: " + title,
|
||||
title,
|
||||
message,
|
||||
util::Timestamp()
|
||||
);
|
||||
|
||||
+15
-1
@@ -111,9 +111,23 @@ void UserManagementService::updateUserDetails(const std::string& userID, const s
|
||||
int index = usersMap.find(userID);
|
||||
if (index == -1)
|
||||
{
|
||||
throw std::runtime_error("User does not exist!");
|
||||
throw std::runtime_error("User does not exist!\n");
|
||||
}
|
||||
User* user = usersMap.getValueAt(index);
|
||||
if (email != user->getEmail())
|
||||
{
|
||||
if (util::isEmailDuplicate(email, usersMap))
|
||||
{
|
||||
throw std::runtime_error("Email already exists!\n");
|
||||
}
|
||||
}
|
||||
if (phone != user->getPhone())
|
||||
{
|
||||
if (util::isPhoneDuplicate(phone, usersMap))
|
||||
{
|
||||
throw std::runtime_error("Phone number already exists!\n");
|
||||
}
|
||||
}
|
||||
user->setEmail(email);
|
||||
user->setPhone(phone);
|
||||
}
|
||||
|
||||
@@ -108,6 +108,17 @@ bool util::isPasswordValid(const std::string& password)
|
||||
return hasUpper && hasLower && hasDigit && hasSpecial;
|
||||
}
|
||||
|
||||
/*
|
||||
* Function: isUsernameDuplicate
|
||||
* Description: Checks if the given username already exists among active users.
|
||||
* Parameters:
|
||||
* username - string containing the username to validate
|
||||
* usersMap - map of user objects keyed by identifier
|
||||
* Returns:
|
||||
* bool - true if the username is already in use by an active user, false otherwise
|
||||
* Notes:
|
||||
* - Only considers users with state util::State::ACTIVE
|
||||
*/
|
||||
bool util::isUsernameDuplicate(const std::string& username, const util::Map<std::string, User*>& usersMap)
|
||||
{
|
||||
int index = usersMap.findIf(
|
||||
@@ -119,6 +130,17 @@ bool util::isUsernameDuplicate(const std::string& username, const util::Map<std:
|
||||
return index != -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Function: isPhoneDuplicate
|
||||
* Description: Checks if the given phone number already exists among active users.
|
||||
* Parameters:
|
||||
* phone - string containing the phone number to validate
|
||||
* usersMap - map of user objects keyed by identifier
|
||||
* Returns:
|
||||
* bool - true if the phone number is already in use by an active user, false otherwise
|
||||
* Notes:
|
||||
* - Only considers users with state util::State::ACTIVE
|
||||
*/
|
||||
bool util::isPhoneDuplicate(const std::string& phone, const util::Map<std::string, User*>& usersMap)
|
||||
{
|
||||
int index = usersMap.findIf(
|
||||
@@ -130,6 +152,17 @@ bool util::isPhoneDuplicate(const std::string& phone, const util::Map<std::strin
|
||||
return index != -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Function: isEmailDuplicate
|
||||
* Description: Checks if the given email address already exists among active users.
|
||||
* Parameters:
|
||||
* email - string containing the email address to validate
|
||||
* usersMap - map of user objects keyed by identifier
|
||||
* Returns:
|
||||
* bool - true if the email address is already in use by an active user, false otherwise
|
||||
* Notes:
|
||||
* - Only considers users with state util::State::ACTIVE
|
||||
*/
|
||||
bool util::isEmailDuplicate(const std::string& email, const util::Map<std::string, User*>& usersMap)
|
||||
{
|
||||
int index = usersMap.findIf(
|
||||
@@ -139,4 +172,4 @@ bool util::isEmailDuplicate(const std::string& email, const util::Map<std::strin
|
||||
}
|
||||
);
|
||||
return index != -1;
|
||||
}
|
||||
}
|
||||
@@ -158,10 +158,33 @@ void AdminMenu::viewStockLevels()
|
||||
{
|
||||
util::clear();
|
||||
auto inventoryItems = m_controller.getInventoryItems();
|
||||
bool hasActiveItems = false;
|
||||
std::cout << "View Stock Levels" << std::endl;
|
||||
if (inventoryItems.isEmpty())
|
||||
{
|
||||
std::cout << "No items found in Inventory.\n";
|
||||
util::pressEnter();
|
||||
return;
|
||||
}
|
||||
for (int index = 0; index < inventoryItems.getSize(); index++)
|
||||
{
|
||||
const InventoryItem* item = inventoryItems.getValueAt(index);
|
||||
if (item->getState() == util::State::ACTIVE)
|
||||
{
|
||||
hasActiveItems = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hasActiveItems)
|
||||
{
|
||||
std::cout << "No active Inventory Item found.\n";
|
||||
util::pressEnter();
|
||||
return;
|
||||
}
|
||||
std::cout << std::left << std::setw(15) << "Item ID"
|
||||
<< std::setw(25) << "Part Name"
|
||||
<< std::setw(10) << "Quantity"
|
||||
<< std::setw(10) << "Price"
|
||||
<< std::setw(15) << "Quantity"
|
||||
<< std::setw(15) << "Price"
|
||||
<< std::endl;
|
||||
for (int iterator = 0; iterator < inventoryItems.getSize(); ++iterator)
|
||||
{
|
||||
@@ -172,12 +195,14 @@ void AdminMenu::viewStockLevels()
|
||||
{
|
||||
std::cout << std::left << std::setw(15) << item->getId()
|
||||
<< std::setw(25) << item->getPartName()
|
||||
<< std::setw(10) << item->getQuantity()
|
||||
<< std::setw(10) << item->getPrice()
|
||||
<< std::setw(15) << item->getQuantity()
|
||||
<< std::setw(15) << item->getPrice()
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
std::cout << "\n";
|
||||
util::pressEnter();
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -273,8 +298,10 @@ void AdminMenu::checkStockAvailability()
|
||||
{
|
||||
util::clear();
|
||||
std::string itemId;
|
||||
std::cout << "Enter the Item Id : ";
|
||||
std::cout << "Check Stock Availability \n";
|
||||
std::cout << "Enter the Item ID : ";
|
||||
util::read(itemId);
|
||||
util::clear();
|
||||
const InventoryItem* selectedItem = m_controller.getInventoryItem(itemId);
|
||||
if (selectedItem != nullptr)
|
||||
{
|
||||
@@ -287,6 +314,10 @@ void AdminMenu::checkStockAvailability()
|
||||
std::cout << "Quantity : " << selectedItem->getQuantity() << "\n";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cout << "Item not Found" << std::endl;
|
||||
}
|
||||
util::pressEnter();
|
||||
}
|
||||
|
||||
@@ -443,6 +474,7 @@ void AdminMenu::removeUser()
|
||||
auto listOfUsers = m_controller.getUsers();
|
||||
auto listOfActiveUsers = filterActiveUsers(listOfUsers);
|
||||
int activeUserCount = listOfActiveUsers.getSize();
|
||||
std::cout << "Remove User \n";
|
||||
if (activeUserCount < 1)
|
||||
{
|
||||
std::cout << "No Active users." << std::endl;
|
||||
|
||||
@@ -144,11 +144,12 @@ void CustomerMenu::updateDetails()
|
||||
{
|
||||
std::string email, phone;
|
||||
util::clear();
|
||||
std::cout << "Update Details\n";
|
||||
std::cout << "Enter new email: ";
|
||||
util::read(email);
|
||||
if (!util::isEmailValid(email))
|
||||
{
|
||||
std::cout << "Error: Email is invalid!";
|
||||
std::cout << "Error: Email is invalid!\n";
|
||||
util::pressEnter();
|
||||
return;
|
||||
}
|
||||
@@ -156,12 +157,12 @@ void CustomerMenu::updateDetails()
|
||||
util::read(phone);
|
||||
if (!util::isPhoneNumberValid(phone))
|
||||
{
|
||||
std::cout << "Error: Phone number is invalid!";
|
||||
std::cout << "Error: Phone number is invalid!\n";
|
||||
util::pressEnter();
|
||||
return;
|
||||
}
|
||||
m_controller.updateUserDetails(email, phone);
|
||||
std::cout << "Profile details updated successfully";
|
||||
std::cout << "Profile details updated successfully\n";
|
||||
util::pressEnter();
|
||||
}
|
||||
|
||||
@@ -260,16 +261,17 @@ void CustomerMenu::viewServiceHistory()
|
||||
const User* currentUser = m_controller.getAuthenticatedUser();
|
||||
std::string currentUserID = currentUser->getId();
|
||||
util::Map<std::string, const ServiceBooking*> serviceBookingsByCurrentUser = m_controller.getServiceBookingsByUser(currentUserID);
|
||||
std::cout << "View Service History" << std::endl;
|
||||
if (serviceBookingsByCurrentUser.getSize() != 0)
|
||||
{
|
||||
std::cout << std::left
|
||||
<< std::setw(12) << "Booking ID"
|
||||
<< std::setw(15) << "Booking ID"
|
||||
<< std::setw(20) << "Technician"
|
||||
<< std::setw(15) << "Vehicle Brand"
|
||||
<< std::setw(15) << "Vehicle Number"
|
||||
<< std::setw(15) << "Vehicle Model"
|
||||
<< std::setw(10) << "Discount %"
|
||||
<< std::setw(12) << "Status"
|
||||
<< std::setw(20) << "Vehicle Brand"
|
||||
<< std::setw(20) << "Vehicle Number"
|
||||
<< std::setw(20) << "Vehicle Model"
|
||||
<< std::setw(20) << "Discount %"
|
||||
<< std::setw(20) << "Status"
|
||||
<< std::endl;
|
||||
for (int iterator = 0; iterator < serviceBookingsByCurrentUser.getSize(); iterator++)
|
||||
{
|
||||
@@ -278,13 +280,13 @@ void CustomerMenu::viewServiceHistory()
|
||||
? "Not Assigned"
|
||||
: currentBooking->getAssignedTechnician()->getName();
|
||||
std::cout << std::left
|
||||
<< std::setw(12) << currentBooking->getId()
|
||||
<< std::setw(15) << currentBooking->getId()
|
||||
<< std::setw(20) << technicianName
|
||||
<< std::setw(15) << currentBooking->getVehicleBrand()
|
||||
<< std::setw(15) << currentBooking->getVehicleNumber()
|
||||
<< std::setw(15) << currentBooking->getVehicleModel()
|
||||
<< std::setw(10) << currentBooking->getDiscountPercentage()
|
||||
<< std::setw(12) << util::getServiceJobStatusString(currentBooking->getStatus())
|
||||
<< std::setw(20) << currentBooking->getVehicleBrand()
|
||||
<< std::setw(20) << currentBooking->getVehicleNumber()
|
||||
<< std::setw(20) << currentBooking->getVehicleModel()
|
||||
<< std::setw(20) << currentBooking->getDiscountPercentage()
|
||||
<< std::setw(20) << util::getServiceJobStatusString(currentBooking->getStatus())
|
||||
<< std::endl;
|
||||
hasServiceHistory = true;
|
||||
}
|
||||
@@ -308,11 +310,35 @@ Returns:
|
||||
void CustomerMenu::completePayments()
|
||||
{
|
||||
util::clear();
|
||||
std::cout << "Complete Payments\n";
|
||||
util::Map<std::string, const Invoice*> currentInvoices = m_controller.getInvoicesByUser();
|
||||
if (currentInvoices.isEmpty())
|
||||
{
|
||||
std::cout << "No pending invoices available for payment.\n";
|
||||
util::pressEnter();
|
||||
return;
|
||||
}
|
||||
bool hasPending = false;
|
||||
for (int index = 0; index < currentInvoices.getSize(); ++index)
|
||||
{
|
||||
const Invoice* invoice = currentInvoices.getValueAt(index);
|
||||
if (invoice && invoice->getStatus() == util::PaymentStatus::PENDING)
|
||||
{
|
||||
hasPending = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hasPending)
|
||||
{
|
||||
std::cout << "No pending invoices available for payment.\n";
|
||||
util::pressEnter();
|
||||
return;
|
||||
}
|
||||
std::string selectedID = selectInvoiceFromUserForPayment(currentInvoices);
|
||||
if (selectedID == "")
|
||||
{
|
||||
std::cout << "Payment failed.\n";
|
||||
util::pressEnter();
|
||||
return;
|
||||
}
|
||||
util::PaymentMode paymentMode = selectPaymentMode();
|
||||
|
||||
@@ -309,6 +309,7 @@ Returns:
|
||||
*/
|
||||
inline std::string selectInvoiceFromUserForPayment(const util::Map<std::string, const Invoice*>& currentInvoices)
|
||||
{
|
||||
util::clear();
|
||||
int currentIndex = 1, choice;
|
||||
util::Map<int, const Invoice*> pendingInvoicesForPayment;
|
||||
std::cout << std::left
|
||||
@@ -316,8 +317,8 @@ inline std::string selectInvoiceFromUserForPayment(const util::Map<std::string,
|
||||
<< std::setw(12) << "BookingID"
|
||||
<< std::setw(15) << "VehicleBrand"
|
||||
<< std::setw(15) << "VehicleNumber"
|
||||
<< std::setw(12) << "TechID"
|
||||
<< std::setw(20) << "TechnicianName"
|
||||
<< std::setw(12) << "Technician ID"
|
||||
<< std::setw(20) << "Technician Name"
|
||||
<< std::setw(10) << "Discount(%)"
|
||||
<< std::setw(12) << "TotalAmount"
|
||||
<< std::setw(20) << "InvoiceDate"
|
||||
@@ -528,11 +529,6 @@ Return type: const Notification* - pointer to the selected notification
|
||||
*/
|
||||
inline const Notification* selectNotification(const util::Vector<const Notification*>& notifications)
|
||||
{
|
||||
if (notifications.getSize() == 0)
|
||||
{
|
||||
std::cout << "No notifications available." << std::endl;
|
||||
return nullptr;
|
||||
}
|
||||
util::Map<int, const Notification*> indexedNotifications;
|
||||
std::cout << std::left
|
||||
<< std::setw(6) << "Index"
|
||||
@@ -549,7 +545,7 @@ inline const Notification* selectNotification(const util::Vector<const Notificat
|
||||
std::cout << std::left
|
||||
<< std::setw(6) << currentIndex
|
||||
<< std::setw(15) << currentNotification->getId()
|
||||
<< std::setw(30) << currentNotification->getTitle()
|
||||
<< std::setw(35) << currentNotification->getTitle()
|
||||
<< std::setw(25) << currentNotification->getCreatedAt().toString()
|
||||
<< std::endl;
|
||||
indexedNotifications.insert(currentIndex, currentNotification);
|
||||
@@ -602,6 +598,13 @@ inline void viewAndDeleteNotification(Controller& controller)
|
||||
{
|
||||
util::clear();
|
||||
auto notifications = controller.getNotifications();
|
||||
std::cout << "View and Delete Notification" << std::endl;
|
||||
if (notifications.getSize() == 0)
|
||||
{
|
||||
std::cout << "No notifications available." << std::endl;
|
||||
util::pressEnter();
|
||||
return;
|
||||
}
|
||||
const Notification* selectedNotification = selectNotification(notifications);
|
||||
if (!selectedNotification)
|
||||
{
|
||||
@@ -675,7 +678,7 @@ inline util::Map<std::string, const User*> filterActiveUsers(const util::Map<std
|
||||
for (int index = 0; index < inventorySize; index++)
|
||||
{
|
||||
const User* user = listOfUsers.getValueAt(index);
|
||||
if (user != nullptr && user->getState() != util::State::INACTIVE)
|
||||
if (user != nullptr && user->getState() != util::State::INACTIVE && user->getUserType() != util::UserType::ADMIN)
|
||||
{
|
||||
activeUsers.insert(user->getId(), user);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user