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
This commit is contained in:
2026-05-27 16:49:20 +05:30
6 changed files with 121 additions and 68 deletions
@@ -24,9 +24,10 @@ Parameters: None
Returns: A new ServiceBooking object. Returns: A new ServiceBooking object.
*/ */
ServiceBooking::ServiceBooking() ServiceBooking::ServiceBooking()
: m_id("SRV" + std::to_string(++m_uid)), : m_id("SBK" + std::to_string(++m_uid)),
m_customer(nullptr), m_customer(nullptr),
m_assignedTechnician(nullptr), m_assignedTechnician(nullptr),
m_status(util::ServiceJobStatus::PENDING),
m_discountPercentage(0.0) {} m_discountPercentage(0.0) {}
/* /*
@@ -56,7 +57,7 @@ ServiceBooking::ServiceBooking(
const std::string& vehicleModel, const std::string& vehicleModel,
double discountPercentage double discountPercentage
) )
: m_id("SRV" + std::to_string(++m_uid)), : m_id("SBK" + std::to_string(++m_uid)),
m_status(status), m_status(status),
m_services(services), m_services(services),
m_customerId(customerId), m_customerId(customerId),
@@ -833,6 +833,9 @@ void ServiceManagementService::createJobCard(const std::string& bookingID, const
{ {
throw std::runtime_error("Failed to create job card."); 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 +1058,11 @@ void ServiceManagementService::completeJob(const std::string& jobID)
} }
else 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) 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); serviceBookingCompleted = hasCompletedAllJobs(currentJob->getBookingId(), currentAssignedJobs);
@@ -1067,8 +1070,8 @@ void ServiceManagementService::completeJob(const std::string& jobID)
{ {
currentJob->getBooking()->setStatus(util::ServiceJobStatus::COMPLETED); currentJob->getBooking()->setStatus(util::ServiceJobStatus::COMPLETED);
paymentManagementService.generateInvoice(currentJob->getBooking()); paymentManagementService.generateInvoice(currentJob->getBooking());
std::string title = "Service Booking completed,Invoice Generated.\n"; std::string title = "Service Booking completed,Invoice Generated.";
std::string message = "Services completed for the booking and invoice generated.\n"; std::string message = "Services completed for the booking and invoice generated.";
sendNotification(currentJob->getBooking()->getCustomer(), title, message); sendNotification(currentJob->getBooking()->getCustomer(), title, message);
} }
} }
@@ -297,6 +297,9 @@ Return type: void
*/ */
void UserManagementService::removeUser(const std::string& userID) void UserManagementService::removeUser(const std::string& userID)
{ {
InventoryManagementService inventoryManagementService;
PaymentManagementService paymentManagementService;
ServiceManagementService serviceManagementService;
int index = m_dataStore.getUsers().find(userID); int index = m_dataStore.getUsers().find(userID);
if (index != -1) if (index != -1)
{ {
@@ -304,6 +307,9 @@ void UserManagementService::removeUser(const std::string& userID)
if (user != nullptr) if (user != nullptr)
{ {
user->setState(util::State::INACTIVE); 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; 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) + "...";
}
} }
@@ -327,19 +327,18 @@ Returns:
*/ */
inline std::string selectInvoiceFromUserForPayment(const util::Map<std::string, const Invoice*>& currentInvoices) inline std::string selectInvoiceFromUserForPayment(const util::Map<std::string, const Invoice*>& currentInvoices)
{ {
util::clear();
int currentIndex = 1, choice; int currentIndex = 1, choice;
util::Map<int, const Invoice*> pendingInvoicesForPayment; util::Map<int, const Invoice*> pendingInvoicesForPayment;
std::cout << std::left std::cout << std::left
<< std::setw(6) << "Index" << std::setw(8) << "Index"
<< std::setw(12) << "BookingID" << std::setw(15) << "Booking ID"
<< std::setw(15) << "VehicleBrand" << std::setw(20) << "Vehicle Brand"
<< std::setw(15) << "VehicleNumber" << std::setw(20) << "Vehicle Number"
<< std::setw(12) << "Technician ID" << std::setw(18) << "Technician ID"
<< std::setw(20) << "Technician Name" << std::setw(25) << "Technician Name"
<< std::setw(10) << "Discount(%)" << std::setw(15) << "Discount(%)"
<< std::setw(12) << "TotalAmount" << std::setw(15) << "TotalAmount"
<< std::setw(20) << "InvoiceDate" << std::setw(22) << "Invoice Timestamp"
<< std::endl; << std::endl;
for (int iterator = 0; iterator < currentInvoices.getSize(); iterator++) for (int iterator = 0; iterator < currentInvoices.getSize(); iterator++)
{ {
@@ -348,17 +347,17 @@ inline std::string selectInvoiceFromUserForPayment(const util::Map<std::string,
{ {
const User* currentTechnician = currentInvoice->getBooking()->getAssignedTechnician(); const User* currentTechnician = currentInvoice->getBooking()->getAssignedTechnician();
std::cout << std::left std::cout << std::left
<< std::setw(6) << currentIndex << std::setw(8) << currentIndex
<< std::setw(12) << currentInvoice->getBookingId() << std::setw(15) << currentInvoice->getBookingId()
<< std::setw(15) << currentInvoice->getBooking()->getVehicleBrand() << std::setw(20) << currentInvoice->getBooking()->getVehicleBrand()
<< std::setw(15) << currentInvoice->getBooking()->getVehicleNumber() << std::setw(20) << currentInvoice->getBooking()->getVehicleNumber()
<< std::setw(12) << ((currentTechnician != nullptr && currentTechnician->getId() != "") ? << std::setw(18) << ((currentTechnician != nullptr && currentTechnician->getId() != "") ?
currentTechnician->getId() : "Null") currentTechnician->getId() : "Null")
<< std::setw(20) << ((currentTechnician != nullptr && currentTechnician->getName() != "") ? << std::setw(25) << ((currentTechnician != nullptr && currentTechnician->getName() != "") ?
currentTechnician->getName() : "Null") currentTechnician->getName() : "Null")
<< std::setw(10) << currentInvoice->getDiscountPercentage() << std::setw(15) << currentInvoice->getDiscountPercentage()
<< std::setw(12) << currentInvoice->getTotalAmount() << std::setw(15) << currentInvoice->getTotalAmount()
<< std::setw(20) << currentInvoice->getInvoiceDate().toString() << std::setw(22) << currentInvoice->getInvoiceDate().toString()
<< std::endl; << std::endl;
pendingInvoicesForPayment.insert(currentIndex++, currentInvoice); pendingInvoicesForPayment.insert(currentIndex++, currentInvoice);
} }
@@ -394,6 +393,9 @@ Returns:
inline util::PaymentMode selectPaymentMode() inline util::PaymentMode selectPaymentMode()
{ {
int choice; int choice;
while (true)
{
util::clear();
std::cout << "Enter the payment Mode\n1.OFFLINE\n2.ONLINE\nChoice: "; std::cout << "Enter the payment Mode\n1.OFFLINE\n2.ONLINE\nChoice: ";
util::read(choice); util::read(choice);
if (choice == 1) if (choice == 1)
@@ -408,8 +410,9 @@ inline util::PaymentMode selectPaymentMode()
} }
else else
{ {
std::cout << "Invalid choice. Offline mode selected.\n"; std::cout << "Invalid choice. Try again.\n";
return util::PaymentMode::OFFLINE; util::pressEnter();
}
} }
} }
@@ -553,9 +556,9 @@ inline const Notification* selectNotification(const util::Vector<const Notificat
{ {
util::Map<int, const Notification*> indexedNotifications; util::Map<int, const Notification*> indexedNotifications;
std::cout << std::left std::cout << std::left
<< std::setw(6) << "Index" << std::setw(10) << "Index"
<< std::setw(15) << "ID" << std::setw(15) << "ID"
<< std::setw(30) << "Title" << std::setw(35) << "Title"
<< std::setw(25) << "Timestamp" << std::setw(25) << "Timestamp"
<< std::endl; << std::endl;
int currentIndex = 1; int currentIndex = 1;
@@ -565,9 +568,9 @@ inline const Notification* selectNotification(const util::Vector<const Notificat
if (currentNotification) if (currentNotification)
{ {
std::cout << std::left std::cout << std::left
<< std::setw(6) << currentIndex << std::setw(10) << currentIndex
<< std::setw(15) << currentNotification->getId() << 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::setw(25) << currentNotification->getCreatedAt().toString()
<< std::endl; << std::endl;
indexedNotifications.insert(currentIndex, currentNotification); indexedNotifications.insert(currentIndex, currentNotification);
@@ -780,7 +783,6 @@ inline const Service* selectServiceFromServices(const util::Map<std::string, con
util::Map<int, const Service*> activeServicesMap; util::Map<int, const Service*> activeServicesMap;
int currentIndex = 1; int currentIndex = 1;
int userInputIndex; int userInputIndex;
std::cout << std::endl;
std::cout << std::left std::cout << std::left
<< std::setw(10) << "Index" << std::setw(10) << "Index"
<< std::setw(15) << "Service ID" << std::setw(15) << "Service ID"
@@ -886,7 +888,7 @@ inline bool getNotificationPreference(const std::string& serviceName)
while (true) while (true)
{ {
util::clear(); util::clear();
std::cout << " Configure Notification Preferences\n"; std::cout << "Configure Notification Preferences\n";
std::cout << "\n" << serviceName << " Notifications\n"; std::cout << "\n" << serviceName << " Notifications\n";
std::cout << "1. Enable Notifications\n"; std::cout << "1. Enable Notifications\n";
std::cout << "2. Disable Notifications\n"; std::cout << "2. Disable Notifications\n";
@@ -7,6 +7,8 @@ Author: Trenser
Date:19-May-2026 Date:19-May-2026
*/ */
#include <iostream>
#include <stdexcept>
#include "Enums.h" #include "Enums.h"
#include "InputHelper.h" #include "InputHelper.h"
#include "OutputHelper.h" #include "OutputHelper.h"
@@ -23,6 +25,8 @@ Return type: void
*/ */
void UserInterface::run() void UserInterface::run()
{ {
try
{
m_controller.loadSystemData(); m_controller.loadSystemData();
m_controller.runSystemChecks(); m_controller.runSystemChecks();
bool isMenuActive = true; bool isMenuActive = true;
@@ -46,6 +50,19 @@ void UserInterface::run()
} }
} }
m_controller.saveSystemData(); 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;
}
} }
/* /*