Compare commits

...

5 Commits

Author SHA1 Message Date
Avinash Rajesh b7bc1f574d Implement Review Fixes 2026-05-27 17:32:36 +05:30
Avinash Rajesh b25b3d59cf Fix Admin Menu Remove User Incomplete Data
- Updated active user display to include Full Name column.
- Ensured both header and row output show full name alongside ID, username, and user type.
- Improved clarity of Remove User submenu by presenting complete user information.

Fixes #1788
2026-05-27 17:12:23 +05:30
Avinash Rajesh c1bd2a6ef1 Fix Admin Inventory Management UI Issues and Workflow Enhancements
- Added heading output to Remove Inventory Item submenu for clearer user context.
- Prevented service creation with empty inventory selection by adding validation and early return.
- Improved combo package creation by simplifying null checks with modern idioms.
- Enhanced inventory selection flow:
  - Clear screen and context header for better UX.
  - Prevent duplicate item selection by checking already chosen items.
  - Changed exit option from -1 to 0 for consistency.
  - Added pressEnter prompt after successful item addition.
- Simplified pointer checks using concise conditions (e.g., if (item)).

Fixes #1778
2026-05-27 17:12:23 +05:30
Avinash Rajesh 859f7bbeaa Fix Customer Removal and Cancellation Handling Issues
- Refactored customer and technician removal flow to ensure linked job cards and service bookings are properly cancelled.
- Added inventory restoration logic to avoid duplicate restocking when cancelling bookings.
- Introduced processBookingCancellation helper for consistent cancellation handling, notifications, and technician reassignment.
- Updated UserManagementService::removeUser to invoke appropriate cancellation routines based on user type.
- Ensured customer references and IDs are preserved correctly during booking cancellation.

Fixes #1781
2026-05-27 17:12:20 +05:30
Jissin Mathew d6cc6fc04f Merged PR 1129: Fix view invoices, combo packages, and technician menu UI issues
Changes
- Added tabular invoice list with selection before full details
- Displayed Payment Mode in detailed invoice view
- Removed duplicate pressEnter() calls and improved console messages
- Changed combo package bookings to start with PENDING status
- Added filterComboPackages to list only active packages
- Improved CustomerMenu combo package selection with clearer messages and formatting
- Enhanced output with console clearing, spacing, and success feedback
- Added truncateString utility for consistent display of long names
- Updated filter functions to use reference parameters for efficiency
- Cleared console before displaying Complete Job screen
- Improved submenu header and formatting for clarity
- Prevented table headers from showing when no jobs exist
- Refined job completion flow with consistent messages

Fixes #1779
Fixes #1782
Fixes #1784

Related work items: #1779, #1782, #1784
2026-05-27 17:09:18 +05:30
5 changed files with 191 additions and 87 deletions
@@ -409,10 +409,8 @@ void Controller::removeUser(const std::string& userID)
User* user = m_userManagementService.getUser(userID); User* user = m_userManagementService.getUser(userID);
if (!user) if (!user)
{ {
throw std::runtime_error("Error User not Found.\n"); throw std::runtime_error("Error: User not Found.\n");
} }
m_serviceManagementService.cancelCustomerServiceBookings(userID);
m_serviceManagementService.cancelTechnicianJobs(userID);
m_userManagementService.removeUser(userID); m_userManagementService.removeUser(userID);
} }
@@ -507,6 +507,89 @@ void ServiceManagementService::saveObservers()
util::saveObservers(config::file::SERVICEMANAGEMENTOBSERVERS, this); util::saveObservers(config::file::SERVICEMANAGEMENTOBSERVERS, this);
} }
/*
Function: restoreInventory
Description: Restores inventory quantities for all required items in the services associated
with a given booking. Each item's quantity is incremented by a fixed value.
Parameter: ServiceBooking* booking - Pointer to the booking whose inventory items need to be restored
Return type: void
*/
static void restoreInventory(ServiceBooking* booking)
{
const int INCREMENT_VALUE = 1;
if (!booking)
{
return;
}
const auto& services = booking->getServices();
for (int serviceIterator = 0; serviceIterator < services.getSize(); ++serviceIterator)
{
Service* service = services.getValueAt(serviceIterator);
if (!service)
{
continue;
}
const auto& items = service->getRequiredInventoryItems();
for (int InventoryIterator = 0; InventoryIterator < items.getSize(); ++InventoryIterator)
{
InventoryItem* item = items.getValueAt(InventoryIterator);
if (item)
{
item->setQuantity(item->getQuantity() + INCREMENT_VALUE);
}
}
}
}
/*
Function: processBookingCancellation
Description: Cancels jobs and updates the status of a given booking. Sends notifications to the
specified user, resets technician assignment if needed, and restores inventory items.
Parameter: ServiceBooking* booking - Pointer to the booking being cancelled
util::ServiceJobStatus newServiceBookingStatus - New status to assign to the booking
const std::string& notificationTitle - Title of the booking cancellation notification
const std::string& notificationMessage - Message body of the booking cancellation notification
User* notifyUser - User to notify about the cancellation
util::ServiceJobStatus jobCardStatus - New status to assign to associated job cards
const std::string& jobNotificationTitle - Title of the job cancellation notification
const std::string& jobNotificationMessage - Message body of the job cancellation notification
util::Map<std::string, JobCard*>& jobs - Collection of job cards to update
ServiceManagementService& currentService - Reference to the service for sending notifications
Return type: void
*/
static void processBookingCancellation(ServiceBooking* booking,
util::ServiceJobStatus newServiceBookingStatus,
const std::string& notificationTitle,
const std::string& notificationMessage,
User* notifyUser,
util::ServiceJobStatus jobCardStatus,
const std::string& jobNotificationTitle,
const std::string& jobNotificationMessage,
util::Map<std::string, JobCard*>& jobs, ServiceManagementService& currentService)
{
if (!booking || !notifyUser)
{
return;
}
for (int jobIterator = 0; jobIterator < jobs.getSize(); ++jobIterator)
{
JobCard* jobCard = jobs.getValueAt(jobIterator);
if (jobCard && jobCard->getBookingId() == booking->getId())
{
jobCard->setStatus(jobCardStatus);
currentService.sendNotification(notifyUser, jobNotificationTitle, jobNotificationMessage);
}
}
booking->setStatus(newServiceBookingStatus);
currentService.sendNotification(notifyUser, notificationTitle, notificationMessage);
if (newServiceBookingStatus == util::ServiceJobStatus::PENDING)
{
booking->setAssignedTechnician(nullptr);
booking->setAssignedTechnicianId("");
}
restoreInventory(booking);
}
/* /*
Function: cancelCustomerServiceBookings Function: cancelCustomerServiceBookings
Description: Cancels all service bookings associated with a given customer or technician. Description: Cancels all service bookings associated with a given customer or technician.
@@ -515,69 +598,47 @@ Description: Cancels all service bookings associated with a given customer or te
Parameter: const std::string& userID - ID of the customer or technician Parameter: const std::string& userID - ID of the customer or technician
Return type: void Return type: void
*/ */
void ServiceManagementService::cancelCustomerServiceBookings(const std::string& userID) void ServiceManagementService::cancelCustomerServiceBookings(const std::string& customerID)
{ {
const int INCREMENT_VALUE = 1;
auto& users = m_dataStore.getUsers(); auto& users = m_dataStore.getUsers();
int userIndex = users.find(userID); int userIndex = users.find(customerID);
if (userIndex == -1) if (userIndex == -1)
{ {
throw std::runtime_error("User not found: " + userID); throw std::runtime_error("User not found: " + customerID);
} }
User* user = users.getValueAt(userIndex); User* customer = users.getValueAt(userIndex);
if (user == nullptr) if (!customer)
{ {
throw std::runtime_error("User not found: " + userID); throw std::runtime_error("User not found: " + customerID);
} }
util::UserType type = user->getUserType(); auto& bookings = m_dataStore.getServiceBookings();
auto& bookings = DataStore::getInstance().getServiceBookings(); auto& jobs = m_dataStore.getJobCards();
for (int bookingIterator = 0; bookingIterator < bookings.getSize(); bookingIterator++) for (int iteratorOne = 0; iteratorOne < bookings.getSize(); iteratorOne++)
{ {
ServiceBooking* booking = bookings.getValueAt(bookingIterator); ServiceBooking* booking = bookings.getValueAt(iteratorOne);
if (booking != nullptr && if (!booking)
(booking->getCustomerId() == userID || booking->getAssignedTechnicianId() == userID))
{ {
if (booking->getStatus() == util::ServiceJobStatus::PENDING || continue;
booking->getStatus() == util::ServiceJobStatus::STARTED) }
if (booking->getCustomerId() != customerID)
{ {
if (type == util::UserType::CUSTOMER) continue;
}
if (booking->getStatus() != util::ServiceJobStatus::PENDING && booking->getStatus() != util::ServiceJobStatus::STARTED)
{ {
booking->setStatus(util::ServiceJobStatus::CANCELLED); continue;
booking->setCustomer(nullptr); }
booking->setCustomerId("");
User* assignedTechnician = booking->getAssignedTechnician(); User* assignedTechnician = booking->getAssignedTechnician();
std::string title = "Customer Service Cancelled"; std::string titleToTechnician = "Customer Service Cancelled";
std::string message = "The customer has cancelled their service booking. Your assigned job card has been cancelled and the inventory has been restocked."; std::string messageToTechnician = "The customer has cancelled their service booking. Your assigned job card has been cancelled and the inventory has been restocked.";
sendNotification(assignedTechnician, title, message); std::string jobTitle = "Job Cancelled";
} std::string jobMessage = "The job has been cancelled. Your job card has been cancelled and the inventory has been restocked.";
else if (type == util::UserType::TECHNICIAN) processBookingCancellation(booking,
{ util::ServiceJobStatus::CANCELLED,
booking->setStatus(util::ServiceJobStatus::PENDING); titleToTechnician, messageToTechnician, assignedTechnician,
std::string title = "Technician Unavailable"; util::ServiceJobStatus::CANCELLED,
std::string message = "Your assigned technician is no longer available. Your booking has been reset to pending, and we will reassign a new technician shortly."; jobTitle, jobMessage, jobs, *this
sendNotification(booking->getCustomer(), title, message); );
}
booking->setAssignedTechnician(nullptr);
booking->setAssignedTechnicianId("");
const auto& ListOfServices = booking->getServices();
for (int serviceIterator = 0; serviceIterator < ListOfServices.getSize(); serviceIterator++)
{
Service* service = ListOfServices.getValueAt(serviceIterator);
if (service != nullptr)
{
const auto& items = service->getRequiredInventoryItems();
for (int itemIterator = 0; itemIterator < items.getSize(); itemIterator++)
{
InventoryItem* item = items.getValueAt(itemIterator);
if (item != nullptr)
{
item->setQuantity(item->getQuantity() + INCREMENT_VALUE);
}
}
}
}
}
}
} }
} }
@@ -590,34 +651,48 @@ Return type: void
*/ */
void ServiceManagementService::cancelTechnicianJobs(const std::string& technicianID) void ServiceManagementService::cancelTechnicianJobs(const std::string& technicianID)
{ {
const int INCREMENT_VALUE = 1; auto& users = m_dataStore.getUsers();
int userIndex = users.find(technicianID);
if (userIndex == -1)
{
throw std::runtime_error("User not found: " + technicianID);
}
User* technician = users.getValueAt(userIndex);
if (!technician)
{
throw std::runtime_error("User not found: " + technicianID);
}
auto& bookings = m_dataStore.getServiceBookings();
auto& jobs = m_dataStore.getJobCards(); auto& jobs = m_dataStore.getJobCards();
for (int jobIterator = 0; jobIterator < jobs.getSize(); jobIterator++) for (int iteratorOne = 0; iteratorOne < bookings.getSize(); iteratorOne++)
{ {
JobCard* job = jobs.getValueAt(jobIterator); ServiceBooking* booking = bookings.getValueAt(iteratorOne);
if (job != nullptr && job->getTechnicianId() == technicianID) if (!booking)
{ {
if (job->getStatus() == util::ServiceJobStatus::PENDING || job->getStatus() == util::ServiceJobStatus::STARTED) continue;
{
job->setStatus(util::ServiceJobStatus::CANCELLED);
std::string title = "Job Cancelled";
std::string message = "The Job has cancelled. Your job card has been cancelled and the inventory has been restocked.";
sendNotification(job->getTechnician(), title, message);
Service* service = job->getService();
if (service != nullptr)
{
const auto& items = service->getRequiredInventoryItems();
for (int itemIterator = 0; itemIterator < items.getSize(); itemIterator++)
{
InventoryItem* item = items.getValueAt(itemIterator);
if (item != nullptr)
{
item->setQuantity(item->getQuantity() + INCREMENT_VALUE);
}
} }
std::string technicianId = booking->getAssignedTechnicianId();
if (technicianId != technicianID)
{
continue;
} }
if (booking->getStatus() != util::ServiceJobStatus::PENDING && booking->getStatus() != util::ServiceJobStatus::STARTED)
{
continue;
} }
User* customer = booking->getCustomer();
if (!customer)
{
continue;
} }
std::string title = "Technician Unavailable";
std::string message = "Your assigned technician is no longer available. Your booking has been reset to pending, and we will reassign a new technician shortly.";
processBookingCancellation(booking,
util::ServiceJobStatus::PENDING,
title, message, customer,
util::ServiceJobStatus::CANCELLED,
title, message, jobs, *this
);
} }
} }
@@ -306,6 +306,14 @@ void UserManagementService::removeUser(const std::string& userID)
User* user = m_dataStore.getUsers().getValueAt(index); User* user = m_dataStore.getUsers().getValueAt(index);
if (user != nullptr) if (user != nullptr)
{ {
if (user->getUserType() == util::UserType::CUSTOMER)
{
serviceManagementService.cancelCustomerServiceBookings(userID);
}
if (user->getUserType() == util::UserType::TECHNICIAN)
{
serviceManagementService.cancelTechnicianJobs(userID);
}
user->setState(util::State::INACTIVE); user->setState(util::State::INACTIVE);
inventoryManagementService.detach(user); inventoryManagementService.detach(user);
paymentManagementService.detach(user); paymentManagementService.detach(user);
@@ -263,6 +263,7 @@ Return type: void
void AdminMenu::removeInventoryItem() void AdminMenu::removeInventoryItem()
{ {
util::clear(); util::clear();
std::cout << "Remove Inventory Item\n";
auto inventoryItems = m_controller.getInventoryItems(); auto inventoryItems = m_controller.getInventoryItems();
auto activeItems = filterActiveItems(inventoryItems); auto activeItems = filterActiveItems(inventoryItems);
int activeItemsSize = activeItems.getSize(); int activeItemsSize = activeItems.getSize();
@@ -402,6 +403,11 @@ void AdminMenu::createService()
util::Map<std::string, const InventoryItem*> activeInventoryItems = filterActiveItems(currentInventoryItems); util::Map<std::string, const InventoryItem*> activeInventoryItems = filterActiveItems(currentInventoryItems);
util::Vector<std::string> selectedInventoryItems; util::Vector<std::string> selectedInventoryItems;
selectInventoryItems(activeInventoryItems,selectedInventoryItems); selectInventoryItems(activeInventoryItems,selectedInventoryItems);
if (selectedInventoryItems.isEmpty())
{
util::pressEnter();
return;
}
std::cout << "\nEnter the labour cost: "; std::cout << "\nEnter the labour cost: ";
util::read(labourCost); util::read(labourCost);
m_controller.createService(serviceName, selectedInventoryItems, labourCost); m_controller.createService(serviceName, selectedInventoryItems, labourCost);
@@ -541,7 +547,7 @@ void AdminMenu::createComboPackages()
while (true) while (true)
{ {
chosenService = selectServiceFromServices(activeServices); chosenService = selectServiceFromServices(activeServices);
if (chosenService == nullptr) if (!chosenService)
{ {
std::cout << "Failed to create combo package!\n\n"; std::cout << "Failed to create combo package!\n\n";
util::pressEnter(); util::pressEnter();
@@ -94,13 +94,15 @@ inline void selectInventoryItems(util::Map<std::string, const InventoryItem*>& c
bool doRun = true; bool doRun = true;
util::Map<int, const InventoryItem*> currentInventoryMap; util::Map<int, const InventoryItem*> currentInventoryMap;
int choice; int choice;
if (currentInventoryItems.getSize() == 0) if (currentInventoryItems.isEmpty())
{ {
std::cout << "No Items Present, Inventory empty.\n"; std::cout << "No Items Present, Inventory empty.\n";
return; return;
} }
while (doRun) while (doRun)
{ {
util::clear();
std::cout << "Create Service\n";
std::cout << "\nSelect Required Items\n"; std::cout << "\nSelect Required Items\n";
bool hasInventoryItems = false; bool hasInventoryItems = false;
int currentIndex = 1; int currentIndex = 1;
@@ -119,6 +121,19 @@ inline void selectInventoryItems(util::Map<std::string, const InventoryItem*>& c
{ {
continue; continue;
} }
bool alreadySelected = false;
for (int iteratorOne = 0; iteratorOne < selectedInventoryItems.getSize(); iteratorOne++)
{
if (selectedInventoryItems[iteratorOne] == currentInventoryItem->getId())
{
alreadySelected = true;
break;
}
}
if (alreadySelected)
{
continue;
}
std::cout << std::left std::cout << std::left
<< std::setw(6) << currentIndex << std::setw(6) << currentIndex
<< std::setw(12) << currentInventoryItem->getId() << std::setw(12) << currentInventoryItem->getId()
@@ -133,10 +148,9 @@ inline void selectInventoryItems(util::Map<std::string, const InventoryItem*>& c
{ {
break; break;
} }
std::cout << "Select the item (Index) or enter -1 to exit: "; std::cout << "Select the item (Index) or enter 0 to exit: ";
util::read(choice); util::read(choice);
if (choice == 0)
if (choice == -1)
{ {
doRun = false; doRun = false;
} }
@@ -144,6 +158,7 @@ inline void selectInventoryItems(util::Map<std::string, const InventoryItem*>& c
{ {
selectedInventoryItems.push_back(currentInventoryMap.getValueAt(currentInventoryMap.find(choice))->getId()); selectedInventoryItems.push_back(currentInventoryMap.getValueAt(currentInventoryMap.find(choice))->getId());
std::cout << "Item added successfully.\n" << std::endl; std::cout << "Item added successfully.\n" << std::endl;
util::pressEnter();
} }
else else
{ {
@@ -840,6 +855,7 @@ inline void displayAllActiveUsers(util::Map<std::string, const User*>& activeUse
std::cout << std::left << std::setw(10) << "Index" std::cout << std::left << std::setw(10) << "Index"
<< std::setw(15) << "User ID" << std::setw(15) << "User ID"
<< std::setw(25) << "Username" << std::setw(25) << "Username"
<< std::setw(25) << "Full Name"
<< std::setw(25) << "User Type" << std::setw(25) << "User Type"
<< std::endl; << std::endl;
for (int iterator = 0; iterator < activeUserCount; iterator++) for (int iterator = 0; iterator < activeUserCount; iterator++)
@@ -850,6 +866,7 @@ inline void displayAllActiveUsers(util::Map<std::string, const User*>& activeUse
std::cout << std::left << std::setw(10) << (iterator + 1) std::cout << std::left << std::setw(10) << (iterator + 1)
<< std::setw(15) << user->getId() << std::setw(15) << user->getId()
<< std::setw(25) << user->getUserName() << std::setw(25) << user->getUserName()
<< std::setw(25) << user->getName()
<< std::setw(25) << util::getUserTypeString(user->getUserType()) << std::setw(25) << util::getUserTypeString(user->getUserType())
<< std::endl; << std::endl;
} }
@@ -1066,7 +1083,7 @@ inline util::Map<std::string, const InventoryItem*> filterActiveItems(const util
for (int index = 0; index < inventorySize; index++) for (int index = 0; index < inventorySize; index++)
{ {
const InventoryItem* item = inventoryItems.getValueAt(index); const InventoryItem* item = inventoryItems.getValueAt(index);
if (item != nullptr && item->getState() != util::State::INACTIVE) if (item && item->getState() != util::State::INACTIVE)
{ {
activeItems.insert(item->getId(), item); activeItems.insert(item->getId(), item);
} }