Compare commits

...

9 Commits

Author SHA1 Message Date
Jissin Mathew 62cd7dcc89 Implement Review Fixes 2026-05-27 17:09:03 +05:30
Jissin Mathew c67bf962c1 Fix Customer Menu View Invoices UI and Navigation Issues
- Added tabular invoice list for better navigation
- Enabled user to select an invoice before viewing full details
- Displayed Payment Mode in detailed invoice view
- Removed duplicate pressEnter() calls
- Improved console messages and formatting for clarity

Fixes #1784
2026-05-27 16:55:14 +05:30
Jissin Mathew efd8b32a5f Fix Combo Package Status and Creation Flow Issues
- 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

Fixes #1779
2026-05-27 16:53:30 +05:30
Jissin Mathew 01596e825e Fix Technician Menu Complete Job UI Issues
- 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 #1782
2026-05-27 16:53:29 +05:30
joelthomastrenser fb50aeba47 Merged PR 1127: Fix notification UI formatting, payment flow issues, and observer cleanup
Changes:

- Fix notification table alignment issues in admin notification screens
- Add proper spacing for notification title column display
- Remove unnecessary tab spacing from Configure Notification Preferences heading
- Change ServiceBooking ID prefix to avoid conflict with Service IDs
- Remove unnecessary newline characters from service booking completion notifications
- Detach removed users from all service observer lists during user removal
- Fix Complete Payments screen clearing immediately after heading display
- Improve table spacing in Complete Payments screen
- Prevent invalid payment mode selection from defaulting to OFFLINE mode

Fixes #1780
Fixes #1783
Fixes #1777
Fixes #1786

Related work items: #1777, #1780, #1783, #1786
2026-05-27 16:49:20 +05:30
joelthomastrenser defee9aa15 Fix ServiceBooking ID prefix and notification messages
- change ServiceBooking ID prefix from `SRV` to `SBK`
- add customer notification when technician is assigned to booking
- fix typo in job completion exception message
- remove unnecessary newline characters from completion notifications

Fixes #1780
2026-05-27 16:02:23 +05:30
joelthomastrenser 67e5917a57 Fix: improve complete payments table formatting and payment mode validation
- Increased spacing between invoice table columns for better readability
- Updated invoice table headers with clearer labels
- Prevented screen clear before invoice listing display
- Changed payment mode selection to re-prompt on invalid input instead of defaulting to offline mode
- Removed unnecessary blank line before service listing display

Fixes #1786
2026-05-27 13:02:40 +05:30
joelthomastrenser 83e2bed432 Fix: improve notification display formatting and add global exception handling
- add truncateString utility for long notification titles
- improve notification table alignment
- remove unnecessary heading spacing in notification preferences
- add application-level exception handling in UserInterface::run()

Fixes #1777
2026-05-27 12:19:34 +05:30
joelthomastrenser a9c8ec93b7 Fix: detach removed users from service observers
- detach users from InventoryManagementService observers during removal
- detach users from PaymentManagementService observers during removal
- detach users from ServiceManagementService observers during removal

Fixes #1783
2026-05-27 11:10:59 +05:30
8 changed files with 346 additions and 149 deletions
@@ -24,9 +24,10 @@ Parameters: None
Returns: A new ServiceBooking object.
*/
ServiceBooking::ServiceBooking()
: m_id("SRV" + std::to_string(++m_uid)),
: m_id("SBK" + std::to_string(++m_uid)),
m_customer(nullptr),
m_assignedTechnician(nullptr),
m_status(util::ServiceJobStatus::PENDING),
m_discountPercentage(0.0) {}
/*
@@ -56,7 +57,7 @@ ServiceBooking::ServiceBooking(
const std::string& vehicleModel,
double discountPercentage
)
: m_id("SRV" + std::to_string(++m_uid)),
: m_id("SBK" + std::to_string(++m_uid)),
m_status(status),
m_services(services),
m_customerId(customerId),
@@ -26,7 +26,6 @@ Date:19-May-2026
#include "User.h"
#include "UserManagementService.h"
#include "Utility.h"
#include "ComboPackage.h"
/*
Function: purchaseService
@@ -100,7 +99,7 @@ void ServiceManagementService::purchaseComboPackage(const std::string& comboPack
}
const ComboPackage* comboPackage = comboPackagesMap[comboPackageID];
util::Map<std::string, Service*> selectedServices = comboPackage->getServices();
ServiceBooking* serviceBooking = Factory::getObject<ServiceBooking>(util::ServiceJobStatus::STARTED, selectedServices, authenticatedUser->getId(), authenticatedUser, vehicleNumber, vehicleBrand, vehicleModel, comboPackage->getDiscountPercentage());
ServiceBooking* serviceBooking = Factory::getObject<ServiceBooking>(util::ServiceJobStatus::PENDING, selectedServices, authenticatedUser->getId(), authenticatedUser, vehicleNumber, vehicleBrand, vehicleModel, comboPackage->getDiscountPercentage());
if (serviceBooking == nullptr)
{
throw std::runtime_error("Failed to create combo package service booking");
@@ -833,6 +832,9 @@ void ServiceManagementService::createJobCard(const std::string& bookingID, const
{
throw std::runtime_error("Failed to create job card.");
}
title = "Technician assigned";
message = "A technician has been assigned to your Service Booking with ID " + bookingID;
sendNotification(currentBooking->getCustomer(), title, message);
}
/*
@@ -1055,11 +1057,11 @@ void ServiceManagementService::completeJob(const std::string& jobID)
}
else
{
throw std::runtime_error("Failed to complete the job, some error occured or job already completed.");
throw std::runtime_error("Failed to complete the job, some error occurred or job already completed.");
}
if (!jobStatusUpdated)
{
throw std::runtime_error("Failed to complete the job, some error occured or job already completed.");
throw std::runtime_error("Failed to complete the job, some error occurred or job already completed.");
}
serviceBookingCompleted = hasCompletedAllJobs(currentJob->getBookingId(), currentAssignedJobs);
@@ -1067,8 +1069,8 @@ void ServiceManagementService::completeJob(const std::string& jobID)
{
currentJob->getBooking()->setStatus(util::ServiceJobStatus::COMPLETED);
paymentManagementService.generateInvoice(currentJob->getBooking());
std::string title = "Service Booking completed,Invoice Generated.\n";
std::string message = "Services completed for the booking and invoice generated.\n";
std::string title = "Service Booking completed,Invoice Generated.";
std::string message = "Services completed for the booking and invoice generated.";
sendNotification(currentJob->getBooking()->getCustomer(), title, message);
}
}
@@ -297,6 +297,9 @@ Return type: void
*/
void UserManagementService::removeUser(const std::string& userID)
{
InventoryManagementService inventoryManagementService;
PaymentManagementService paymentManagementService;
ServiceManagementService serviceManagementService;
int index = m_dataStore.getUsers().find(userID);
if (index != -1)
{
@@ -304,6 +307,9 @@ void UserManagementService::removeUser(const std::string& userID)
if (user != nullptr)
{
user->setState(util::State::INACTIVE);
inventoryManagementService.detach(user);
paymentManagementService.detach(user);
serviceManagementService.detach(user);
}
}
}
@@ -21,4 +21,28 @@ namespace util
{
std::cout << "\x1B[2J\x1B[H" << std::flush;
}
/*
Function: truncateString
Description:
Truncates a string if its length exceeds the given maximum length.
The truncated string ends with "..." to indicate omitted characters.
Parameters:
- text: const std::string&, input string to truncate
- maxLength: size_t, maximum allowed length of the returned string
Returns:
- std::string: Original string if within limit, otherwise truncated string with "..."
*/
inline std::string truncateString(const std::string& text, size_t maxLength)
{
if (text.length() <= maxLength)
{
return text;
}
if (maxLength <= 3)
{
return std::string(maxLength, '.');
}
return text.substr(0, maxLength - 3) + "...";
}
}
@@ -220,20 +220,24 @@ void CustomerMenu::selectComboPackage()
util::clear();
std::cout << "Select a Combo Package\n";
auto comboPackages = m_controller.getComboPackages();
if (comboPackages.isEmpty())
util::Map<std::string, const ComboPackage*> activeComboPackages = filterComboPackages(comboPackages);
if (activeComboPackages.isEmpty())
{
std::cout << "No combo packages available!";
std::cout << "No combo packages available!\n\n";
util::pressEnter();
return;
}
const ComboPackage* selectedComboPackage = selectComboPackageFromPackages(comboPackages);
const ComboPackage* selectedComboPackage = selectComboPackageFromPackages(activeComboPackages);
if (selectedComboPackage == nullptr)
{
std::cout << "Failed to book combo package!";
std::cout << "Failed to book combo package!\n\n";
util::pressEnter();
return;
}
std::cout << "Combo Package selected\n";
util::pressEnter();
util::clear();
std::cout << "Enter the vehicle details\n";
std::cout << "Enter vehicle number: ";
util::read(vehicleNumber);
std::cout << "Enter vehicle brand: ";
@@ -241,7 +245,7 @@ void CustomerMenu::selectComboPackage()
std::cout << "Enter vehicle model: ";
util::read(vehicleModel);
m_controller.purchaseComboPackage(selectedComboPackage->getId(), vehicleNumber, vehicleBrand, vehicleModel);
std::cout << "Combo Package has been booked successfully";
std::cout << "Combo Package has been booked successfully\n\n";
util::pressEnter();
}
@@ -160,7 +160,7 @@ Parameters:
Returns:
- util::Map<std::string, const ServiceBooking*>: map containing only active (PENDING) service bookings
*/
inline util::Map<std::string, const ServiceBooking*> filterActiveServiceBookings(util::Map<std::string, const ServiceBooking*> currentBookings)
inline util::Map<std::string, const ServiceBooking*> filterActiveServiceBookings(util::Map<std::string, const ServiceBooking*>& currentBookings)
{
util::Map<std::string, const ServiceBooking*> activeServiceBookings;
for (int iterator = 0; iterator < currentBookings.getSize(); iterator++)
@@ -201,8 +201,6 @@ inline bool listServiceBookings(util::Map<std::string, const ServiceBooking*>& c
<< std::setw(15) << "VehicleNo"
<< std::setw(15) << "Brand"
<< std::setw(15) << "Model"
<< std::setw(20) << "Technician"
<< std::setw(15) << "TechnicianID"
<< std::endl;
for (int iterator = 0; iterator < bookingsSize; iterator++)
{
@@ -219,8 +217,6 @@ inline bool listServiceBookings(util::Map<std::string, const ServiceBooking*>& c
<< std::setw(15) << currentBooking->getVehicleNumber()
<< std::setw(15) << currentBooking->getVehicleBrand()
<< std::setw(15) << currentBooking->getVehicleModel()
<< std::setw(20) << ((currentAssignedTechnician == nullptr || currentAssignedTechnician->getName().empty()) ? "NULL" : currentAssignedTechnician->getName())
<< std::setw(15) << ((currentAssignedTechnician == nullptr || currentAssignedTechnician->getId().empty()) ? "NULL" : currentAssignedTechnician->getId())
<< std::endl;
serviceBookingsMap.insert(currentIndex++, currentBooking);
}
@@ -239,7 +235,7 @@ Returns:
inline const ServiceBooking* selectPendingServiceBookings(util::Map<int, const ServiceBooking*>& serviceBookingsMap)
{
int userInputIndex;
std::cout << "\nEnter a valid service index: ";
std::cout << "\nEnter a service index: ";
util::read(userInputIndex);
if (serviceBookingsMap.find(userInputIndex) != -1)
{
@@ -304,7 +300,7 @@ Returns:
inline const User* selectTechnician(util::Map<int, const User*>& currentAvailableTechniciansMap)
{
int userInputIndex;
std::cout << "\nEnter valid technician index: ";
std::cout << "\nEnter technician index: ";
util::read(userInputIndex);
if (currentAvailableTechniciansMap.find(userInputIndex) != -1)
{
@@ -327,39 +323,38 @@ 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
<< std::setw(6) << "Index"
<< std::setw(12) << "BookingID"
<< std::setw(15) << "VehicleBrand"
<< std::setw(15) << "VehicleNumber"
<< std::setw(12) << "Technician ID"
<< std::setw(20) << "Technician Name"
<< std::setw(10) << "Discount(%)"
<< std::setw(12) << "TotalAmount"
<< std::setw(20) << "InvoiceDate"
<< std::endl;
std::cout << std::left
<< std::setw(8) << "Index"
<< std::setw(15) << "Booking ID"
<< std::setw(20) << "Vehicle Brand"
<< std::setw(20) << "Vehicle Number"
<< std::setw(18) << "Technician ID"
<< std::setw(25) << "Technician Name"
<< std::setw(15) << "Discount(%)"
<< std::setw(15) << "TotalAmount"
<< std::setw(22) << "Invoice Timestamp"
<< std::endl;
for (int iterator = 0; iterator < currentInvoices.getSize(); iterator++)
{
const Invoice* currentInvoice = currentInvoices.getValueAt(iterator);
if (currentInvoice && currentInvoice->getStatus() == util::PaymentStatus::PENDING)
{
const User* currentTechnician = currentInvoice->getBooking()->getAssignedTechnician();
std::cout << std::left
<< std::setw(6) << currentIndex
<< std::setw(12) << currentInvoice->getBookingId()
<< std::setw(15) << currentInvoice->getBooking()->getVehicleBrand()
<< std::setw(15) << currentInvoice->getBooking()->getVehicleNumber()
<< std::setw(12) << ((currentTechnician != nullptr && currentTechnician->getId() != "") ?
currentTechnician->getId() : "Null")
<< std::setw(20) << ((currentTechnician != nullptr && currentTechnician->getName() != "") ?
currentTechnician->getName() : "Null")
<< std::setw(10) << currentInvoice->getDiscountPercentage()
<< std::setw(12) << currentInvoice->getTotalAmount()
<< std::setw(20) << currentInvoice->getInvoiceDate().toString()
<< std::endl;
std::cout << std::left
<< std::setw(8) << currentIndex
<< std::setw(15) << currentInvoice->getBookingId()
<< std::setw(20) << currentInvoice->getBooking()->getVehicleBrand()
<< std::setw(20) << currentInvoice->getBooking()->getVehicleNumber()
<< std::setw(18) << ((currentTechnician != nullptr && currentTechnician->getId() != "") ?
currentTechnician->getId() : "Null")
<< std::setw(25) << ((currentTechnician != nullptr && currentTechnician->getName() != "") ?
currentTechnician->getName() : "Null")
<< std::setw(15) << currentInvoice->getDiscountPercentage()
<< std::setw(15) << currentInvoice->getTotalAmount()
<< std::setw(22) << currentInvoice->getInvoiceDate().toString()
<< std::endl;
pendingInvoicesForPayment.insert(currentIndex++, currentInvoice);
}
}
@@ -393,23 +388,94 @@ Returns:
*/
inline util::PaymentMode selectPaymentMode()
{
int choice;
std::cout << "Enter the payment Mode\n1.OFFLINE\n2.ONLINE\nChoice: ";
util::read(choice);
if (choice == 1)
int choice;
while (true)
{
std::cout << "Offline mode selected.\n";
return util::PaymentMode::OFFLINE;
}
else if (choice == 2)
util::clear();
std::cout << "Enter the payment Mode\n1.OFFLINE\n2.ONLINE\nChoice: ";
util::read(choice);
if (choice == 1)
{
std::cout << "Online mode selected.\n";
return util::PaymentMode::ONLINE;
std::cout << "Offline mode selected.\n";
return util::PaymentMode::OFFLINE;
}
else
{
std::cout << "Invalid choice. Offline mode selected.\n";
return util::PaymentMode::OFFLINE;
else if (choice == 2)
{
std::cout << "Online mode selected.\n";
return util::PaymentMode::ONLINE;
}
else
{
std::cout << "Invalid choice. Try again.\n";
util::pressEnter();
}
}
}
/*
Function: displayInvoicesInTabularForm
Description:
Displays all invoices in a tabular format. Each row shows booking details,
vehicle info, technician details, discount, total amount, invoice date,
and payment status. If inventory items exist for an invoice, they are
displayed in a separate table below the invoice row.
Parameters:
- currentInvoices: util::Map<std::string, const Invoice*>
Map of invoice IDs to Invoice pointers.
Returns:
- void
*/
inline const Invoice* selectInvoiceToDisplay(util::Map<std::string, const Invoice*>& currentInvoices)
{
int currentIndex = 1, choice;
util::Map<int, const Invoice*> currentInvoicesIndexMap;
if (currentInvoices.isEmpty())
{
std::cout << "No invoices available.\n\n";
return nullptr;
}
std::cout
<< std::left
<< std::setw(10) << "Index"
<< std::setw(12) << "BookingID"
<< std::setw(15) << "VehicleNumber"
<< std::setw(20) << "TechnicianName"
<< std::setw(15) << "TotalAmount"
<< std::setw(25) << "InvoiceDate"
<< std::setw(15) << "PaymentStatus"
<< std::setw(15) << "PaymentMode"
<< std::endl;
for (int iterator = 0; iterator < currentInvoices.getSize(); iterator++)
{
const Invoice* currentInvoice = currentInvoices.getValueAt(iterator);
if (!currentInvoice)
{
continue;
}
const User* currentTechnician = currentInvoice->getBooking()->getAssignedTechnician();
std::cout << std::left
<< std::setw(10) << currentIndex
<< std::setw(12) << currentInvoice->getBookingId()
<< std::setw(15) << currentInvoice->getBooking()->getVehicleNumber()
<< std::setw(20) << ((currentTechnician && !currentTechnician->getName().empty()) ? currentTechnician->getName() : "NULL")
<< std::setw(15) << currentInvoice->getTotalAmount()
<< std::setw(25) << currentInvoice->getInvoiceDate().toString()
<< std::setw(15) << util::getPaymentStatusString(currentInvoice->getStatus())
<< std::setw(15) << util::getPaymentModeString(currentInvoice->getPaymentMethod())
<< std::endl;
currentInvoicesIndexMap.insert(currentIndex++, currentInvoice);
}
std::cout << "Enter an index: ";
util::read(choice);
int currentSelectedIndex = currentInvoicesIndexMap.find(choice);
if (currentSelectedIndex != -1)
{
return currentInvoicesIndexMap.getValueAt(currentSelectedIndex);
}
else
{
std::cout << "Enter a valid index.\n";
return nullptr;
}
}
@@ -426,34 +492,48 @@ Throws:
*/
inline void displayInvoices(util::Map<std::string, const Invoice*> currentUserInvoices)
{
std::cout << std::endl;
if (currentUserInvoices.getSize() == 0)
{
std::cout << "No invoices found for this account." << std::endl << std::endl;
std::cout << "No invoices found for this account.\n\n";
return;
}
else
{
for (int index = 0; index < currentUserInvoices.getSize(); index++)
bool doRun = true;
do
{
const Invoice* currentInvoice = currentUserInvoices.getValueAt(index);
if (currentInvoice)
const Invoice* selectedInvoice;
int choice;
selectedInvoice = selectInvoiceToDisplay(currentUserInvoices);
if (selectedInvoice)
{
const User* currentTechnician = currentInvoice->getBooking()->getAssignedTechnician();
std::cout << "\nInvoice Details\n";
std::cout << "Booking ID: " << currentInvoice->getBookingId() << std::endl;
std::cout << "Vehicle Brand: " << currentInvoice->getBooking()->getVehicleBrand() << std::endl;
std::cout << "Vehicle Number: " << currentInvoice->getBooking()->getVehicleNumber() << std::endl;
std::cout << "Technician ID: " <<
((currentTechnician != nullptr && currentTechnician->getId() != "") ?
currentTechnician->getId() : "Null") << std::endl;
std::cout << "Technician Name: " <<
((currentTechnician != nullptr && currentTechnician->getName() != "") ?
currentTechnician->getName() : "Null") << std::endl;
std::cout << "Discount(%): " << currentInvoice->getDiscountPercentage() << std::endl;
std::cout << "Total Amount: " << currentInvoice->getTotalAmount() << std::endl;
std::cout << "Invoice Date: " << currentInvoice->getInvoiceDate().toString() << std::endl;
std::cout << "Payment Status: " << util::getPaymentStatusString(currentInvoice->getStatus()) << std::endl;
auto inventoryItemsInInvoice = currentInvoice->getParts();
const User* currentTechnician = selectedInvoice->getBooking()->getAssignedTechnician();
util::clear();
std::cout << "Invoice Details\n";
std::cout << std::left << std::setw(20) << "Booking ID:"
<< selectedInvoice->getBookingId() << std::endl;
std::cout << std::left << std::setw(20) << "Vehicle Brand:"
<< selectedInvoice->getBooking()->getVehicleBrand() << std::endl;
std::cout << std::left << std::setw(20) << "Vehicle Number:"
<< selectedInvoice->getBooking()->getVehicleNumber() << std::endl;
std::cout << std::left << std::setw(20) << "Technician ID:"
<< ((currentTechnician != nullptr && !currentTechnician->getId().empty())
? currentTechnician->getId() : "NULL") << std::endl;
std::cout << std::left << std::setw(20) << "Technician Name:"
<< ((currentTechnician != nullptr && !currentTechnician->getName().empty())
? currentTechnician->getName() : "NULL") << std::endl;
std::cout << std::left << std::setw(20) << "Discount(%):"
<< selectedInvoice->getDiscountPercentage() << std::endl;
std::cout << std::left << std::setw(20) << "Total Amount:"
<< selectedInvoice->getTotalAmount() << std::endl;
std::cout << std::left << std::setw(20) << "Invoice Date:"
<< selectedInvoice->getInvoiceDate().toString() << std::endl;
std::cout << std::left << std::setw(20) << "Payment Status:"
<< util::getPaymentStatusString(selectedInvoice->getStatus()) << std::endl;
std::cout << std::left << std::setw(20) << "Payment Mode:"
<< util::getPaymentModeString(selectedInvoice->getPaymentMethod()) << std::endl;
auto inventoryItemsInInvoice = selectedInvoice->getParts();
if (inventoryItemsInInvoice.isEmpty())
{
std::cout << "No inventory items used.\n\n";
@@ -475,14 +555,53 @@ inline void displayInvoices(util::Map<std::string, const Invoice*> currentUserIn
<< std::setw(10) << currentItem->getPrice()
<< std::endl;
}
std::cout << "\n\nDo you want to display another Invoice (1-Yes, 2-No): ";
util::read(choice);
if (choice == 1)
{
doRun = true;
util::clear();
}
else if (choice == 2)
{
doRun = false;
}
else
{
std::cout << "Invalid choice\n";
doRun = false;
}
}
else
{
throw std::runtime_error("Null invoice encountered while displaying invoices.");
std::cout << "Unable to fetch the selected invoice\n";
doRun = false;
}
} while (doRun);
}
}
/*
Function: filterStartedJobCards
Description: Filters the given list of job cards and returns only those with status STARTED.
Parameters:
- assignedJobCards: Map of job card IDs to JobCard pointers.
Returns:
- util::Map<std::string, const JobCard*> containing only job cards with status STARTED.
*/
inline util::Map<std::string, const JobCard*> filterStartedJobCards(util::Map<std::string, const JobCard*>& assignedJobCards)
{
util::Map<std::string, const JobCard*> startedJobCards;
for (int iterator = 0; iterator < assignedJobCards.getSize(); iterator++)
{
const JobCard* currentJobCard = assignedJobCards.getValueAt(iterator);
if (currentJobCard && currentJobCard->getStatus() == util::ServiceJobStatus::STARTED)
{
startedJobCards.insert(currentJobCard->getId(), currentJobCard);
}
}
util::pressEnter();
return startedJobCards;
}
/*
@@ -494,49 +613,50 @@ Parameters:
Returns:
- std::string: ID of the selected job card, or empty string if none selected
*/
inline std::string selectJobCardToComplete(util::Map<std::string, const JobCard*>& assignedJobCards, util::Map<int, const JobCard*>& incompleteJobCards)
inline std::string selectJobCardToComplete(util::Map<std::string, const JobCard*>& assignedJobCards)
{
util::Map<int, const JobCard* > incompleteJobCards;
if (assignedJobCards.getSize() == 0)
{
int currentIndex = 1;
int choice;
bool hasIncompleteJobCard = false;
std::cout << std::left
<< std::setw(6) << "Index"
<< std::setw(12) << "BookingID"
<< std::setw(12) << "JobID"
<< std::setw(20) << "ServiceName"
<< std::setw(12) << "ServiceID"
<< std::endl;
for (int iterator = 0; iterator < assignedJobCards.getSize(); iterator++)
std::cout << "No started jobs available to complete.\n";
return "";
}
int currentIndex = 1;
int choice;
std::cout << std::endl;
std::cout << std::left
<< std::setw(6) << "Index"
<< std::setw(12) << "BookingID"
<< std::setw(12) << "JobID"
<< std::setw(20) << "ServiceName"
<< std::setw(12) << "ServiceID"
<< std::endl;
for (int iterator = 0; iterator < assignedJobCards.getSize(); iterator++)
{
const JobCard* currentJobCard = assignedJobCards.getValueAt(iterator);
if (currentJobCard && (currentJobCard->getStatus() == util::ServiceJobStatus::STARTED))
const JobCard* currentJobCard = assignedJobCards.getValueAt(iterator);
if (currentJobCard && (currentJobCard->getStatus() == util::ServiceJobStatus::STARTED))
{
std::cout << std::left << std::setw(6) << currentIndex
<< std::setw(12) << currentJobCard->getBookingId()
<< std::setw(12) << currentJobCard->getId()
<< std::setw(20) << currentJobCard->getService()->getName()
<< std::setw(12) << currentJobCard->getServiceId()
<< std::endl;
hasIncompleteJobCard = true;
incompleteJobCards.insert(currentIndex++, currentJobCard);
std::cout << std::left << std::setw(6) << currentIndex
<< std::setw(12) << currentJobCard->getBookingId()
<< std::setw(12) << currentJobCard->getId()
<< std::setw(20) << currentJobCard->getService()->getName()
<< std::setw(12) << currentJobCard->getServiceId()
<< std::endl;
incompleteJobCards.insert(currentIndex++, currentJobCard);
}
}
if (!hasIncompleteJobCard)
{
std::cout << "No pending jobs are present.\n";
return "";
}
std::cout << "Select the Job Card to complete (Index): ";
std::cout << "Select the Job Card to complete (Index): ";
util::read(choice);
int selectedJobCardIndex = incompleteJobCards.find(choice);
if (selectedJobCardIndex != -1)
int selectedJobCardIndex = incompleteJobCards.find(choice);
if (selectedJobCardIndex != -1)
{
const JobCard* selectedJobCard = incompleteJobCards.getValueAt(selectedJobCardIndex);
return selectedJobCard->getId();
const JobCard* selectedJobCard = incompleteJobCards.getValueAt(selectedJobCardIndex);
return selectedJobCard->getId();
}
else
{
std::cout << "Invalid index.\n";
std::cout << "Invalid index.\n";
std::cout << "Failed to complete jobs.\n\n";
return "";
}
}
@@ -553,9 +673,9 @@ inline const Notification* selectNotification(const util::Vector<const Notificat
{
util::Map<int, const Notification*> indexedNotifications;
std::cout << std::left
<< std::setw(6) << "Index"
<< std::setw(10) << "Index"
<< std::setw(15) << "ID"
<< std::setw(30) << "Title"
<< std::setw(35) << "Title"
<< std::setw(25) << "Timestamp"
<< std::endl;
int currentIndex = 1;
@@ -565,9 +685,9 @@ inline const Notification* selectNotification(const util::Vector<const Notificat
if (currentNotification)
{
std::cout << std::left
<< std::setw(6) << currentIndex
<< std::setw(10) << currentIndex
<< std::setw(15) << currentNotification->getId()
<< std::setw(35) << currentNotification->getTitle()
<< std::setw(35) << util::truncateString(currentNotification->getTitle(), 30)
<< std::setw(25) << currentNotification->getCreatedAt().toString()
<< std::endl;
indexedNotifications.insert(currentIndex, currentNotification);
@@ -750,7 +870,7 @@ Parameters:
Returns:
- util::Map<std::string, const Service*> containing only active services.
*/
inline util::Map<std::string, const Service*> filterActiveServices(util::Map<std::string, const Service*> serviceList)
inline util::Map<std::string, const Service*> filterActiveServices(util::Map<std::string, const Service*>& serviceList)
{
util::Map<std::string, const Service*> activeServices;
for (int iterator = 0; iterator < serviceList.getSize(); iterator++)
@@ -777,10 +897,10 @@ inline const Service* selectServiceFromServices(const util::Map<std::string, con
std::cout << "No active services available." << std::endl;
return nullptr;
}
std::cout << std::endl;
util::Map<int, const Service*> activeServicesMap;
int currentIndex = 1;
int userInputIndex;
std::cout << std::endl;
std::cout << std::left
<< std::setw(10) << "Index"
<< std::setw(15) << "Service ID"
@@ -824,6 +944,31 @@ inline const Service* selectServiceFromServices(const util::Map<std::string, con
return activeServicesMap[userInputIndex];
}
/*
Function: filterComboPackages
Description:
Filters the given list of combo packages and returns only those that are ACTIVE.
Parameters:
- comboPackages: util::Map<std::string, const ComboPackage*>&
Map of combo package IDs to ComboPackage pointers.
Returns:
- util::Map<std::string, const ComboPackage*>
Map containing only active combo packages.
*/
inline util::Map<std::string, const ComboPackage*> filterComboPackages(util::Map<std::string, const ComboPackage*>& comboPackages)
{
util::Map<std::string, const ComboPackage*> activeComboPackages;
for (int iterator = 0; iterator < comboPackages.getSize(); iterator++)
{
const ComboPackage* currentComboPackage = comboPackages.getValueAt(iterator);
if (currentComboPackage && currentComboPackage->getState() == util::State::ACTIVE)
{
activeComboPackages.insert(currentComboPackage->getId(), currentComboPackage);
}
}
return activeComboPackages;
}
/*
Function: selectComboPackageFromPackages
Description: Displays active combo packages and allows the customer to select one by index.
@@ -835,10 +980,11 @@ inline const ComboPackage* selectComboPackageFromPackages(const util::Map<std::s
util::Map<int, const ComboPackage*> activeComboPackages;
int currentIndex = 1;
int userInputIndex;
std::cout << std::endl;
std::cout << std::left
<< std::setw(10) << "Index"
<< std::setw(15) << "Combo ID"
<< std::setw(15) << "Combo Name"
<< std::setw(35) << "Combo Name"
<< std::setw(15) << "Estimate Cost"
<< std::endl;
for (int index = 0; index < comboPackages.getSize(); index++)
@@ -852,7 +998,7 @@ inline const ComboPackage* selectComboPackageFromPackages(const util::Map<std::s
std::cout << std::left
<< std::setw(10) << currentIndex
<< std::setw(15) << currentComboPackage->getId()
<< std::setw(25) << currentComboPackage->getPackageName()
<< std::setw(35) << util::truncateString(currentComboPackage->getPackageName(), 30)
<< std::setw(15) << util::calculateComboServiceEstimatedCost(currentComboPackage)
<< std::endl;
currentIndex++;
@@ -886,7 +1032,7 @@ inline bool getNotificationPreference(const std::string& serviceName)
while (true)
{
util::clear();
std::cout << " Configure Notification Preferences\n";
std::cout << "Configure Notification Preferences\n";
std::cout << "\n" << serviceName << " Notifications\n";
std::cout << "1. Enable Notifications\n";
std::cout << "2. Disable Notifications\n";
@@ -93,18 +93,15 @@ Returns:
*/
void TechnicianMenu::completeJob()
{
util::clear();
std::cout << "Complete Job\n";
util::Map<std::string, const JobCard*> assignedJobCards = m_controller.getJobCardsByUser();
util::Map<int, const JobCard*> incompleteJobCards;
std::cout << "Jobs to be completed.\n";
std::string selectedJobID = selectJobCardToComplete(assignedJobCards, incompleteJobCards);
if (selectedJobID == "")
{
std::cout << "Failed to complete the job.\n";
}
else
util::Map<std::string, const JobCard*> startedJobCards = filterStartedJobCards(assignedJobCards);
std::string selectedJobID = selectJobCardToComplete(startedJobCards);
if (!selectedJobID.empty())
{
m_controller.completeJob(selectedJobID);
std::cout << "Job marked as completed.\n";
std::cout << "\nJob marked as completed.\n\n";
}
util::pressEnter();
}
@@ -7,6 +7,8 @@ Author: Trenser
Date:19-May-2026
*/
#include <iostream>
#include <stdexcept>
#include "Enums.h"
#include "InputHelper.h"
#include "OutputHelper.h"
@@ -23,29 +25,44 @@ Return type: void
*/
void UserInterface::run()
{
m_controller.loadSystemData();
m_controller.runSystemChecks();
bool isMenuActive = true;
while (isMenuActive)
try
{
try
m_controller.loadSystemData();
m_controller.runSystemChecks();
bool isMenuActive = true;
while (isMenuActive)
{
int choice;
util::clear();
std::cout << "Vehicle Service System\n1. Login\n2. Register Customer\n3. Exit\nEnter your Choice: ";
util::read(choice);
if (!handleOperation(choice))
try
{
isMenuActive = false;
int choice;
util::clear();
std::cout << "Vehicle Service System\n1. Login\n2. Register Customer\n3. Exit\nEnter your Choice: ";
util::read(choice);
if (!handleOperation(choice))
{
isMenuActive = false;
}
}
catch (const std::exception& e)
{
std::cout << "Exception: " << e.what() << std::endl;
util::pressEnter();
}
}
catch (const std::exception& e)
{
std::cout << "Exception: " << e.what() << std::endl;
util::pressEnter();
}
m_controller.saveSystemData();
}
catch (const std::invalid_argument& exception)
{
std::cout << "Exception: Invalid Argument: " << exception.what() << std::endl;
}
catch (const std::exception& exception)
{
std::cout << "Exception: " << exception.what() << std::endl;
}
catch (...)
{
std::cout << "Unknown error occurred." << std::endl;
}
m_controller.saveSystemData();
}
/*