Compare commits

...

22 Commits

Author SHA1 Message Date
joelthomastrenser d6b4310de6 Merged PR 1142: Add display menus for users, services, combo packages and jobs
Add display menus for users, services, combo packages and jobs

Changes:
- Added display options for users, services, combo packages and technician jobs
- Updated admin and technician menu options and navigation
- Added reusable helper functions for displaying data in tabular format
- Improved user and combo package listing displays
- Fixed minor validation and error message formatting issues
2026-05-28 16:39:37 +05:30
joelthomastrenser 451085e9c2 Add display menus for users, services, combo packages and jobs
Changes:
- Added display options for users, services, combo packages and technician jobs
- Updated admin and technician menu options and navigation
- Added reusable helper functions for displaying data in tabular format
- Improved user and combo package listing displays
- Fixed minor validation and error message formatting issues
2026-05-28 16:36:34 +05:30
joelthomastrenser 4657d3e8d1 Merged PR 1139: Fix: create missing directories before file creation
Fix: create missing directories before file creation

Changes:
- Added ensureDirectoryExists() helper using _mkdir()
- Automatically create missing directories before file operations
- Added FileHelper include in FileManager
- Removed placeholder files/README.md

Fixes #1793

Related work items: #1646, #1793
2026-05-28 10:35:54 +05:30
joelthomastrenser b1b6125d88 Fix: create missing directories before file creation
Changes:
- Added ensureDirectoryExists() helper using _mkdir()
- Automatically create missing directories before file operations
- Added FileHelper include in FileManager
- Removed placeholder files/README.md

Fixes #1793
2026-05-28 10:32:36 +05:30
joelthomastrenser e739ec6ee2 Merged PR 1134: Fix Invoice Deserialization Failure for PaymentMode::NOTSET
Changes:

- Added support for PaymentMode::NOTSET in payment mode string-to-enum conversion.
- Improved invoice table column labels and spacing for better readability in invoice display screens.

Fixes #1789

Related work items: #1646, #1789
2026-05-27 19:51:59 +05:30
joelthomastrenser f78e02ed3d Fix: handle NOTSET payment mode during invoice deserialization
Changes:

- Added support for PaymentMode::NOTSET in string-to-enum conversion
  to prevent invoice deserialization failures during system startup
- Improved invoice table column labels and spacing for better readability
  and alignment in invoice display screens

Fixes #1789
2026-05-27 18:47:49 +05:30
Avinash Rajesh 807490443e Merged PR 1128: Fix customer removal flow, admin inventory UI issues, and user display improvements
Changes:

- Refactored customer and technician removal flow:
  - Ensured linked job cards and service bookings are properly cancelled.
  - Centralized cancellation logic with processBookingCancellation helper for consistent notifications, technician reassignment, and inventory restoration.
  - Prevented duplicate inventory restocking during customer removal.
  - Preserved customer references and IDs correctly during booking cancellation.

- Admin inventory management improvements:
  - Added heading output to Remove Inventory Item submenu for clearer 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)).

- User management UI fix:
  - Updated active user display in Remove User menu to include Full Name column, ensuring administrators have complete visibility when managing users.

Fixes #1788
Fixes #1778
Fixes #1781

Related work items: #1778, #1781, #1788
2026-05-27 18:38:17 +05:30
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
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
Jissin Mathew 13b7d6d40a Merged PR 1114: Fix Admin Menu, Service Management, Technician Assignment, and Invoice Display Issues
**Changes:**

- Fixed booking status flow (PENDING → STARTED) during job card creation
- Updated purchaseService to initialize bookings with PENDING status
- Filtered active users and services before listing/selection
- Improved Assign Job flow: pending bookings only, clearer technician prompts, consistent feedback
- Enhanced View Invoices: reference parameter, empty items handling, improved formatting
- Added filterActiveServices helper for consistent active service filtering
- Fixed Remove Service to also deactivate dependent combo packages
- Improved Create Combo Packages: cancel option, confirmation step, duplicate/edge case handling
- Updated Remove Combo Package to handle empty states gracefully without exceptions

Related work items: #1646, #1740, #1744, #1745, #1747, #1749, #1752
2026-05-26 21:26:37 +05:30
17 changed files with 822 additions and 281 deletions
@@ -409,10 +409,8 @@ void Controller::removeUser(const std::string& userID)
User* user = m_userManagementService.getUser(userID);
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);
}
@@ -1 +0,0 @@
Place files here.
@@ -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),
@@ -294,7 +294,7 @@ void PaymentManagementService::generateInvoice(ServiceBooking* booking)
{
throw std::runtime_error("Invoice generation failed: booking is null.");
}
double totalLabourCost = 0, totalPartsCost = 0, totalServiceCost = 0;
double totalLaborCost = 0, totalPartsCost = 0, totalServiceCost = 0;
double discountPercentage = booking->getDiscountPercentage();
std::string bookingID = booking->getId();
util::Map<std::string, Service*> servicesInTheBookedService = booking->getServices();
@@ -303,9 +303,10 @@ void PaymentManagementService::generateInvoice(ServiceBooking* booking)
for (int iterator = 0; iterator < currentJobCards.getSize(); iterator++)
{
JobCard* currentJobCard = currentJobCards.getValueAt(iterator);
if (currentJobCard->getBookingId() == bookingID && currentJobCard->getStatus() != util::ServiceJobStatus::COMPLETED)
util::ServiceJobStatus currentJobCardStatus = currentJobCard->getStatus();
if (currentJobCard->getBookingId() == bookingID && currentJobCardStatus != util::ServiceJobStatus::CANCELLED && currentJobCardStatus != util::ServiceJobStatus::COMPLETED)
{
throw std::runtime_error("Invoice generation failed: not all job cards are completed for booking '" + bookingID + "'.");
throw std::runtime_error("Invoice generation failed: Not all job cards are completed for booking '" + bookingID + "'.");
}
}
for (int iterator = 0; iterator < servicesInTheBookedService.getSize(); iterator++)
@@ -314,13 +315,13 @@ void PaymentManagementService::generateInvoice(ServiceBooking* booking)
if (currentService)
{
createInventoryItemsMap(completeInventoryItemMapOfBooking, currentService);
totalLabourCost += currentService->getLaborCost();
totalLaborCost += currentService->getLaborCost();
totalPartsCost += util::calculatePartsCost(currentService);
}
}
totalServiceCost = totalLabourCost + totalPartsCost;
totalServiceCost = totalLaborCost + totalPartsCost;
totalServiceCost -= (totalServiceCost * (discountPercentage / 100));
Invoice* invoice = Factory::getObject<Invoice>(bookingID, booking, util::Timestamp(), totalLabourCost, completeInventoryItemMapOfBooking, totalPartsCost, discountPercentage, totalServiceCost, util::Timestamp(), util::PaymentMode::NOTSET, util::PaymentStatus::PENDING);
Invoice* invoice = Factory::getObject<Invoice>(bookingID, booking, util::Timestamp(), totalLaborCost, completeInventoryItemMapOfBooking, totalPartsCost, discountPercentage, totalServiceCost, util::Timestamp(), util::PaymentMode::NOTSET, util::PaymentStatus::PENDING);
util::Map<std::string, Invoice*>& currentInvoices = m_dataStore.getInvoices();
currentInvoices.insert(invoice->getId(), invoice);
}
@@ -375,7 +376,7 @@ void PaymentManagementService::completePayment(const std::string& invoiceID, uti
invoice->setStatus(util::PaymentStatus::COMPLETED);
std::string title, message;
title = "Payment successful";
message = "Payment successful for invoice ID " + invoiceID;
message = "Payment successful for Invoice ID " + invoiceID;
sendNotification(currentUser, title, message);
}
}
@@ -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");
@@ -508,6 +507,89 @@ void ServiceManagementService::saveObservers()
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
Description: Cancels all service bookings associated with a given customer or technician.
@@ -516,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
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();
int userIndex = users.find(userID);
int userIndex = users.find(customerID);
if (userIndex == -1)
{
throw std::runtime_error("User not found: " + userID);
throw std::runtime_error("User not found: " + customerID);
}
User* user = users.getValueAt(userIndex);
if (user == nullptr)
User* customer = users.getValueAt(userIndex);
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 = DataStore::getInstance().getServiceBookings();
for (int bookingIterator = 0; bookingIterator < bookings.getSize(); bookingIterator++)
auto& bookings = m_dataStore.getServiceBookings();
auto& jobs = m_dataStore.getJobCards();
for (int iteratorOne = 0; iteratorOne < bookings.getSize(); iteratorOne++)
{
ServiceBooking* booking = bookings.getValueAt(bookingIterator);
if (booking != nullptr &&
(booking->getCustomerId() == userID || booking->getAssignedTechnicianId() == userID))
ServiceBooking* booking = bookings.getValueAt(iteratorOne);
if (!booking)
{
if (booking->getStatus() == util::ServiceJobStatus::PENDING ||
booking->getStatus() == util::ServiceJobStatus::STARTED)
continue;
}
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);
booking->setCustomer(nullptr);
booking->setCustomerId("");
continue;
}
User* assignedTechnician = booking->getAssignedTechnician();
std::string title = "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.";
sendNotification(assignedTechnician, title, message);
}
else if (type == util::UserType::TECHNICIAN)
{
booking->setStatus(util::ServiceJobStatus::PENDING);
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.";
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);
}
}
}
}
}
}
std::string titleToTechnician = "Customer Service Cancelled";
std::string messageToTechnician = "The customer has cancelled their service booking. Your assigned job card has been cancelled and the inventory has been restocked.";
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.";
processBookingCancellation(booking,
util::ServiceJobStatus::CANCELLED,
titleToTechnician, messageToTechnician, assignedTechnician,
util::ServiceJobStatus::CANCELLED,
jobTitle, jobMessage, jobs, *this
);
}
}
@@ -591,34 +651,48 @@ Return type: void
*/
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();
for (int jobIterator = 0; jobIterator < jobs.getSize(); jobIterator++)
for (int iteratorOne = 0; iteratorOne < bookings.getSize(); iteratorOne++)
{
JobCard* job = jobs.getValueAt(jobIterator);
if (job != nullptr && job->getTechnicianId() == technicianID)
ServiceBooking* booking = bookings.getValueAt(iteratorOne);
if (!booking)
{
if (job->getStatus() == util::ServiceJobStatus::PENDING || job->getStatus() == util::ServiceJobStatus::STARTED)
{
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);
}
continue;
}
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
);
}
}
@@ -833,6 +907,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 +1132,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 +1144,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,13 +297,27 @@ 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)
{
User* user = m_dataStore.getUsers().getValueAt(index);
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);
inventoryManagementService.detach(user);
paymentManagementService.detach(user);
serviceManagementService.detach(user);
}
}
}
@@ -137,6 +137,10 @@ namespace util
{
return PaymentMode::OFFLINE;
}
if (value == "NOTSET")
{
return PaymentMode::NOTSET;
}
throw std::invalid_argument("Invalid PaymentMode string");
}
@@ -11,10 +11,39 @@ Date: 22-May-2026
#include <fstream>
#include <string>
#include <stdexcept>
#include <direct.h>
#include "Vector.h"
namespace util
{
/*
Function: ensureDirectoryExists
Description: Creates all missing directories present in the given file path.
Iteratively parses the path and creates each directory level
using _mkdir() before file operations are performed.
Parameters:
- filePath: const std::string&, relative or absolute file path
Returns:
- void
Throws:
- None (_mkdir failures are intentionally ignored if directory already exists)
*/
inline void ensureDirectoryExists(const std::string& filePath)
{
size_t position = 0;
while ((position = filePath.find('/', position)) != std::string::npos)
{
std::string directory = filePath.substr(0, position);
if (!directory.empty())
{
(void)_mkdir(directory.c_str());
}
position++;
}
}
/*
Function: loadRecords
Description: Loads records from a given file path into a vector of strings.
@@ -32,6 +61,7 @@ namespace util
std::ifstream file(filePath);
if (!file.is_open())
{
ensureDirectoryExists(filePath);
std::ofstream newFile(filePath);
newFile.close();
file.open(filePath);
@@ -15,6 +15,7 @@ Date: 22-May-2026
#include <fstream>
#include "Vector.h"
#include "Map.h"
#include "FileHelper.h"
namespace util
{
@@ -51,6 +52,7 @@ namespace util
std::ifstream file(m_filePath);
if (!file.is_open())
{
ensureDirectoryExists(m_filePath);
std::ofstream newFile(m_filePath);
newFile.close();
file.open(m_filePath);
@@ -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) + "...";
}
}
@@ -42,15 +42,18 @@ void AdminMenu::showMenu()
<< "\n3. Remove Inventory Item"
<< "\n4. Check Stock Availability"
<< "\n5. Assign Job to Technician"
<< "\n6. Add Technician"
<< "\n7. Remove Customer/Technician"
<< "\n8. Create Service"
<< "\n9. Remove Service"
<< "\n10. Create Combo Package"
<< "\n11. Remove Combo Package"
<< "\n12. View Notifications"
<< "\n13. Change Password"
<< "\n14. Logout"
<< "\n6. Display Users"
<< "\n7. Add Technician"
<< "\n8. Remove Customer/Technician"
<< "\n9. Display Services"
<< "\n10. Create Service"
<< "\n11. Remove Service"
<< "\n12. Display Combo Packages"
<< "\n13. Create Combo Package"
<< "\n14. Remove Combo Package"
<< "\n15. View Notifications"
<< "\n16. Change Password"
<< "\n17. Logout"
<< "\nEnter a choice: ";
util::read(choice);
if (!handleOperation(choice))
@@ -92,30 +95,39 @@ bool AdminMenu::handleOperation(int choice)
assignJob();
break;
case 6:
addTechnician();
displayUsers();
break;
case 7:
removeUser();
addTechnician();
break;
case 8:
createService();
removeUser();
break;
case 9:
removeService();
displayServices();
break;
case 10:
createComboPackages();
createService();
break;
case 11:
removeComboPackage();
removeService();
break;
case 12:
viewNotifications();
displayComboPackages();
break;
case 13:
changePassword();
createComboPackages();
break;
case 14:
removeComboPackage();
break;
case 15:
viewNotifications();
break;
case 16:
changePassword();
break;
case 17:
logout();
return false;
default:
@@ -263,6 +275,7 @@ Return type: void
void AdminMenu::removeInventoryItem()
{
util::clear();
std::cout << "Remove Inventory Item\n";
auto inventoryItems = m_controller.getInventoryItems();
auto activeItems = filterActiveItems(inventoryItems);
int activeItemsSize = activeItems.getSize();
@@ -382,6 +395,24 @@ void AdminMenu::assignJob()
util::pressEnter();
}
/*
Function: displayServices()
Description: Display all active services
Parameters:
- None
Returns:
- void
*/
void AdminMenu::displayServices()
{
util::clear();
std::cout << "List of all Services\n";
util::Map<std::string, const Service*> currentServices = m_controller.getServices();
util::Map<std::string, const Service*> currentActiveServices = filterActiveServices(currentServices);
displayAllServices(currentActiveServices);
util::pressEnter();
}
/*
Function: createService
Description: Allows the admin to create a new service by selecting inventory items and specifying labor cost.
@@ -402,6 +433,11 @@ void AdminMenu::createService()
util::Map<std::string, const InventoryItem*> activeInventoryItems = filterActiveItems(currentInventoryItems);
util::Vector<std::string> selectedInventoryItems;
selectInventoryItems(activeInventoryItems,selectedInventoryItems);
if (selectedInventoryItems.isEmpty())
{
util::pressEnter();
return;
}
std::cout << "\nEnter the labour cost: ";
util::read(labourCost);
m_controller.createService(serviceName, selectedInventoryItems, labourCost);
@@ -428,7 +464,7 @@ void AdminMenu::removeService()
if (selectedServiceID != "")
{
m_controller.removeService(selectedServiceID);
std::cout << "Service removed sucessfully.\n\n";
std::cout << "Service removed successfully.\n\n";
}
else
{
@@ -437,6 +473,29 @@ void AdminMenu::removeService()
util::pressEnter();
}
/*
Function: displayUsers
Description: Displays all users.
Parameter: None
Return type: void
*/
void AdminMenu::displayUsers()
{
util::clear();
auto listOfUsers = m_controller.getUsers();
auto listOfActiveUsers = filterActiveUsers(listOfUsers);
int activeUserCount = listOfActiveUsers.getSize();
std::cout << "List of all Users\n";
if (activeUserCount < 1)
{
std::cout << "No Active users." << std::endl;
util::pressEnter();
return;
}
displayAllUsers(listOfActiveUsers);
util::pressEnter();
}
/*
Function: addTechnician
Description: Adds a new technician after validating username, password, email, and phone number.
@@ -501,12 +560,12 @@ void AdminMenu::removeUser()
util::pressEnter();
return;
}
displayAllActiveUsers(listOfActiveUsers, activeUserCount);
displayAllUsers(listOfActiveUsers);
std::cout << "Enter the index of the user to delete : ";
util::read(indexChoice);
if (indexChoice < 1 || indexChoice > activeUserCount)
{
std::cout << "Error Invaild index.\n" << std::endl;
std::cout << "Error invalid index.\n" << std::endl;
util::pressEnter();
return;
}
@@ -520,6 +579,24 @@ void AdminMenu::removeUser()
util::pressEnter();
}
/*
Function: displayComboPackages()
Description: Display all active combo packages
Parameters:
- None
Returns:
- void
*/
void AdminMenu::displayComboPackages()
{
util::clear();
std::cout << "List of all Combo Packages\n";
util::Map<std::string, const ComboPackage*> currentComboPackages = m_controller.getComboPackages();
util::Map<std::string, const ComboPackage*> currentActiveComboPackages = filterComboPackages(currentComboPackages);
displayAllComboPackages(currentActiveComboPackages);
util::pressEnter();
}
/*
Function: createComboPackages
Description: Creates a new combo package by selecting two active services and applying a discount.
@@ -541,7 +618,7 @@ void AdminMenu::createComboPackages()
while (true)
{
chosenService = selectServiceFromServices(activeServices);
if (chosenService == nullptr)
if (!chosenService)
{
std::cout << "Failed to create combo package!\n\n";
util::pressEnter();
@@ -24,10 +24,13 @@ public:
void removeInventoryItem();
void checkStockAvailability();
void assignJob();
void displayServices();
void createService();
void removeService();
void displayUsers();
void addTechnician();
void removeUser();
void displayComboPackages();
void createComboPackages();
void removeComboPackage();
void viewNotifications();
@@ -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();
}
@@ -29,6 +29,41 @@ Date: 21-May-2026
#include "Validator.h"
#include "Vector.h"
/*
Function: displayAllServices
Description: Displays all active services
Parameters:
- currentServices: util::Map<std::string, const Service*>, available services
Returns:
- void;
*/
inline void displayAllServices(util::Map<std::string, const Service*> currentServices)
{
if (currentServices.getSize() == 0)
{
std::cout << "No Services Currently Available.\n";
return;
}
std::cout << std::left
<< std::setw(12) << "Service ID"
<< std::setw(35) << "Name"
<< std::setw(10) << "Labor Cost"
<< std::endl;
for (int iterator = 0; iterator < currentServices.getSize(); iterator++)
{
const Service* currentService = currentServices.getValueAt(iterator);
if (currentService == nullptr || currentService->getState() == util::State::INACTIVE)
{
continue;
}
std::cout << std::left
<< std::setw(12) << currentService->getId()
<< std::setw(35) << util::truncateString(currentService->getName(), 30)
<< std::setw(10) << currentService->getLaborCost()
<< std::endl;
}
}
/*
Function: selectServicesToRemove
Description: Allows selection of a service to remove by index.
@@ -49,7 +84,7 @@ inline std::string selectServicesToRemove(util::Map<std::string, const Service*>
std::cout << std::left
<< std::setw(6) << "Index"
<< std::setw(12) << "Service ID"
<< std::setw(20) << "Name"
<< std::setw(35) << "Name"
<< std::setw(10) << "Labor Cost"
<< std::endl;
for (int iterator = 0; iterator < currentServices.getSize(); iterator++)
@@ -62,7 +97,7 @@ inline std::string selectServicesToRemove(util::Map<std::string, const Service*>
std::cout << std::left
<< std::setw(6) << currentIndex
<< std::setw(12) << currentService->getId()
<< std::setw(20) << currentService->getName()
<< std::setw(35) << util::truncateString(currentService->getName(), 30)
<< std::setw(10) << currentService->getLaborCost()
<< std::endl;
currentServicesMap.insert(currentIndex++, currentService);
@@ -94,13 +129,15 @@ inline void selectInventoryItems(util::Map<std::string, const InventoryItem*>& c
bool doRun = true;
util::Map<int, const InventoryItem*> currentInventoryMap;
int choice;
if (currentInventoryItems.getSize() == 0)
if (currentInventoryItems.isEmpty())
{
std::cout << "No Items Present, Inventory empty.\n";
return;
}
while (doRun)
{
util::clear();
std::cout << "Create Service\n";
std::cout << "\nSelect Required Items\n";
bool hasInventoryItems = false;
int currentIndex = 1;
@@ -119,6 +156,19 @@ inline void selectInventoryItems(util::Map<std::string, const InventoryItem*>& c
{
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::setw(6) << currentIndex
<< std::setw(12) << currentInventoryItem->getId()
@@ -133,10 +183,9 @@ inline void selectInventoryItems(util::Map<std::string, const InventoryItem*>& c
{
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);
if (choice == -1)
if (choice == 0)
{
doRun = false;
}
@@ -144,6 +193,7 @@ inline void selectInventoryItems(util::Map<std::string, const InventoryItem*>& c
{
selectedInventoryItems.push_back(currentInventoryMap.getValueAt(currentInventoryMap.find(choice))->getId());
std::cout << "Item added successfully.\n" << std::endl;
util::pressEnter();
}
else
{
@@ -160,7 +210,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 +251,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 +267,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 +285,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 +350,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,19 +373,18 @@ 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::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++)
{
@@ -348,17 +393,17 @@ inline std::string selectInvoiceFromUserForPayment(const util::Map<std::string,
{
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() != "") ?
<< 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(20) << ((currentTechnician != nullptr && currentTechnician->getName() != "") ?
<< std::setw(25) << ((currentTechnician != nullptr && currentTechnician->getName() != "") ?
currentTechnician->getName() : "Null")
<< std::setw(10) << currentInvoice->getDiscountPercentage()
<< std::setw(12) << currentInvoice->getTotalAmount()
<< std::setw(20) << currentInvoice->getInvoiceDate().toString()
<< std::setw(15) << currentInvoice->getDiscountPercentage()
<< std::setw(15) << currentInvoice->getTotalAmount()
<< std::setw(22) << currentInvoice->getInvoiceDate().toString()
<< std::endl;
pendingInvoicesForPayment.insert(currentIndex++, currentInvoice);
}
@@ -394,6 +439,9 @@ Returns:
inline util::PaymentMode selectPaymentMode()
{
int choice;
while (true)
{
util::clear();
std::cout << "Enter the payment Mode\n1.OFFLINE\n2.ONLINE\nChoice: ";
util::read(choice);
if (choice == 1)
@@ -408,8 +456,76 @@ inline util::PaymentMode selectPaymentMode()
}
else
{
std::cout << "Invalid choice. Offline mode selected.\n";
return util::PaymentMode::OFFLINE;
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(20) << "Vehicle Number"
<< std::setw(20) << "Technician Name"
<< std::setw(15) << "Total Amount"
<< std::setw(25) << "Invoice Date"
<< std::setw(20) << "Payment Status"
<< std::setw(15) << "Payment Mode"
<< 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(20) << 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(20) << 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 +542,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 +605,89 @@ 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
{
throw std::runtime_error("Null invoice encountered while displaying invoices.");
std::cout << "Invalid choice\n";
doRun = false;
}
}
else
{
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);
}
}
return startedJobCards;
}
/*
Function: displayAllJobs
Description: Displays all Jobs assigned to a Technician
Parameters:
- assignedJobCards: util::Map<std::string, const JobCard*>&, job cards assigned to the technician
Returns:
- std::string: ID of the selected job card, or empty string if none selected
*/
inline void displayAllJobs(util::Map<std::string, const JobCard*>& assignedJobCards)
{
if (assignedJobCards.getSize() == 0)
{
std::cout << "No active jobs assigned.\n";
return;
}
std::cout << std::endl;
std::cout << std::left
<< 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))
{
std::cout << std::left << std::setw(12) << currentJobCard->getBookingId()
<< std::setw(12) << currentJobCard->getId()
<< std::setw(20) << currentJobCard->getService()->getName()
<< std::setw(12) << currentJobCard->getServiceId()
<< std::endl;
}
}
util::pressEnter();
}
/*
@@ -490,15 +695,20 @@ Function: selectJobCardToComplete
Description: Lists all incomplete job cards assigned to the technician and allows selection by index.
Parameters:
- assignedJobCards: util::Map<std::string, const JobCard*>&, job cards assigned to the technician
- incompleteJobCards: util::Map<int, const JobCard*>&, map of incomplete job cards indexed for selection
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)
{
std::cout << "No started jobs available to complete.\n";
return "";
}
int currentIndex = 1;
int choice;
bool hasIncompleteJobCard = false;
std::cout << std::endl;
std::cout << std::left
<< std::setw(6) << "Index"
<< std::setw(12) << "BookingID"
@@ -517,14 +727,8 @@ inline std::string selectJobCardToComplete(util::Map<std::string, const JobCard*
<< std::setw(20) << currentJobCard->getService()->getName()
<< std::setw(12) << currentJobCard->getServiceId()
<< std::endl;
hasIncompleteJobCard = true;
incompleteJobCards.insert(currentIndex++, currentJobCard);
}
}
if (!hasIncompleteJobCard)
{
std::cout << "No pending jobs are present.\n";
return "";
}
std::cout << "Select the Job Card to complete (Index): ";
util::read(choice);
@@ -537,6 +741,7 @@ inline std::string selectJobCardToComplete(util::Map<std::string, const JobCard*
else
{
std::cout << "Invalid index.\n";
std::cout << "Failed to complete jobs.\n\n";
return "";
}
}
@@ -553,9 +758,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 +770,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);
@@ -709,17 +914,18 @@ inline util::Map<std::string, const User*> filterActiveUsers(const util::Map<std
}
/*
Function: displayAllActiveUsers
Function: displayAllUsers
Description: Displays all active users in a tabular format with index, ID, username, and type.
Parameter: util::Map<std::string, const User*>& activeUsers - active users list
int activeUserCount - number of active users
Return type: void
*/
inline void displayAllActiveUsers(util::Map<std::string, const User*>& activeUsers, int activeUserCount)
inline void displayAllUsers(util::Map<std::string, const User*>& activeUsers)
{
int activeUserCount = activeUsers.getSize();
std::cout << std::left << std::setw(10) << "Index"
<< std::setw(15) << "User ID"
<< std::setw(25) << "Username"
<< std::setw(25) << "Full Name"
<< std::setw(25) << "User Type"
<< std::endl;
for (int iterator = 0; iterator < activeUserCount; iterator++)
@@ -730,6 +936,7 @@ inline void displayAllActiveUsers(util::Map<std::string, const User*>& activeUse
std::cout << std::left << std::setw(10) << (iterator + 1)
<< std::setw(15) << user->getId()
<< std::setw(25) << user->getUserName()
<< std::setw(25) << user->getName()
<< std::setw(25) << util::getUserTypeString(user->getUserType())
<< std::endl;
}
@@ -750,7 +957,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 +984,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 +1031,68 @@ 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: displayAllComboPackages
Description: Displays all active combo packages
Parameters:
- currentComboPackages: util::Map<std::string, const ComboPackage*>, available combo packages
Returns:
- void;
*/
inline void displayAllComboPackages(util::Map<std::string, const ComboPackage*> comboPackages)
{
std::cout << std::endl;
if (comboPackages.getSize() == 0)
{
std::cout << "No active combo packages available." << std::endl;
return;
}
std::cout << std::left
<< std::setw(15) << "Combo ID"
<< std::setw(35) << "Combo Name"
<< std::setw(15) << "Estimate Cost"
<< std::endl;
for (int index = 0; index < comboPackages.getSize(); index++)
{
const ComboPackage* currentComboPackage = comboPackages.getValueAt(index);
if (currentComboPackage && currentComboPackage->getState() != util::State::ACTIVE)
{
continue;
}
std::cout << std::left
<< std::setw(15) << currentComboPackage->getId()
<< std::setw(35) << util::truncateString(currentComboPackage->getPackageName(), 30)
<< std::setw(15) << util::calculateComboServiceEstimatedCost(currentComboPackage)
<< std::endl;
}
}
/*
Function: selectComboPackageFromPackages
Description: Displays active combo packages and allows the customer to select one by index.
@@ -835,16 +1104,17 @@ 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++)
{
const ComboPackage* currentComboPackage = comboPackages.getValueAt(index);
if (currentComboPackage->getState() != util::State::ACTIVE)
if (currentComboPackage && currentComboPackage->getState() != util::State::ACTIVE)
{
continue;
}
@@ -852,7 +1122,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 +1156,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";
@@ -920,7 +1190,7 @@ inline util::Map<std::string, const InventoryItem*> filterActiveItems(const util
for (int index = 0; index < inventorySize; 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);
}
@@ -1025,21 +1295,21 @@ inline void displayComboPackagesWithIndex(util::Map<int, const ComboPackage*>& c
const ComboPackage* currentComboPackage = currentComboPackageIndexMap.getValueAt(iterator);
if (currentComboPackage == nullptr)
{
throw std::runtime_error("Error accessing the combopackage.\n");
throw std::runtime_error("Error accessing the combo package.\n");
}
if (iterator == 0)
{
std::cout << std::left
<< std::setw(8) << "Index"
<< std::setw(10) << "ID"
<< std::setw(20) << "Package Name"
<< std::setw(35) << "Package Name"
<< std::setw(15) << "Discount (%)"
<< "\n";
}
std::cout << std::left
<< std::setw(8) << currentComboPackageIndexMap.getKeyAt(iterator)
<< std::setw(10) << currentComboPackage->getId()
<< std::setw(20) << currentComboPackage->getPackageName()
<< std::setw(35) << util::truncateString(currentComboPackage->getPackageName(), 30)
<< std::setw(15) << currentComboPackage->getDiscountPercentage()
<< "\n";
}
@@ -34,10 +34,11 @@ void TechnicianMenu::showMenu()
int choice;
util::clear();
std::cout << "Technician Menu"
<< "\n1. Mark Job as Completed"
<< "\n2. View Notifications"
<< "\n3. Change Password"
<< "\n4. Logout"
<< "\n1. Display My Jobs"
<< "\n2. Mark Job as Completed"
<< "\n3. View Notifications"
<< "\n4. Change Password"
<< "\n5. Logout"
<< "\nEnter a choice: ";
util::read(choice);
if (!handleOperation(choice))
@@ -64,15 +65,18 @@ bool TechnicianMenu::handleOperation(int choice)
switch (choice)
{
case 1:
completeJob();
displayJobs();
break;
case 2:
viewNotifications();
completeJob();
break;
case 3:
changePassword();
viewNotifications();
break;
case 4:
changePassword();
break;
case 5:
logout();
return false;
default:
@@ -82,6 +86,24 @@ bool TechnicianMenu::handleOperation(int choice)
return true;
}
/*
Function: displayJobs
Description: Displays all Jobs assigned to a Technician
Parameters:
- None
Returns:
- void
*/
void TechnicianMenu::displayJobs()
{
util::clear();
std::cout << "My Jobs\n";
util::Map<std::string, const JobCard*> assignedJobCards = m_controller.getJobCardsByUser();
util::Map<std::string, const JobCard*> startedJobCards = filterStartedJobCards(assignedJobCards);
displayAllJobs(startedJobCards);
util::pressEnter();
}
/*
Function: completeJob
Description: Allows the technician to mark a selected job card as completed.
@@ -93,18 +115,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();
}
@@ -17,6 +17,7 @@ private:
bool handleOperation(int choice);
public:
void showMenu();
void displayJobs();
void completeJob();
void viewNotifications();
void logout();
@@ -7,6 +7,8 @@ Author: Trenser
Date:19-May-2026
*/
#include <iostream>
#include <stdexcept>
#include "Enums.h"
#include "InputHelper.h"
#include "OutputHelper.h"
@@ -23,6 +25,8 @@ Return type: void
*/
void UserInterface::run()
{
try
{
m_controller.loadSystemData();
m_controller.runSystemChecks();
bool isMenuActive = true;
@@ -46,6 +50,19 @@ void UserInterface::run()
}
}
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;
}
}
/*