Compare commits

...

4 Commits

Author SHA1 Message Date
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
8 changed files with 60 additions and 24 deletions
@@ -1 +0,0 @@
Place files here.
@@ -294,7 +294,7 @@ void PaymentManagementService::generateInvoice(ServiceBooking* booking)
{ {
throw std::runtime_error("Invoice generation failed: booking is null."); 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(); double discountPercentage = booking->getDiscountPercentage();
std::string bookingID = booking->getId(); std::string bookingID = booking->getId();
util::Map<std::string, Service*> servicesInTheBookedService = booking->getServices(); 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++) for (int iterator = 0; iterator < currentJobCards.getSize(); iterator++)
{ {
JobCard* currentJobCard = currentJobCards.getValueAt(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++) for (int iterator = 0; iterator < servicesInTheBookedService.getSize(); iterator++)
@@ -314,13 +315,13 @@ void PaymentManagementService::generateInvoice(ServiceBooking* booking)
if (currentService) if (currentService)
{ {
createInventoryItemsMap(completeInventoryItemMapOfBooking, currentService); createInventoryItemsMap(completeInventoryItemMapOfBooking, currentService);
totalLabourCost += currentService->getLaborCost(); totalLaborCost += currentService->getLaborCost();
totalPartsCost += util::calculatePartsCost(currentService); totalPartsCost += util::calculatePartsCost(currentService);
} }
} }
totalServiceCost = totalLabourCost + totalPartsCost; totalServiceCost = totalLaborCost + totalPartsCost;
totalServiceCost -= (totalServiceCost * (discountPercentage / 100)); 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(); util::Map<std::string, Invoice*>& currentInvoices = m_dataStore.getInvoices();
currentInvoices.insert(invoice->getId(), invoice); currentInvoices.insert(invoice->getId(), invoice);
} }
@@ -375,7 +376,7 @@ void PaymentManagementService::completePayment(const std::string& invoiceID, uti
invoice->setStatus(util::PaymentStatus::COMPLETED); invoice->setStatus(util::PaymentStatus::COMPLETED);
std::string title, message; std::string title, message;
title = "Payment successful"; title = "Payment successful";
message = "Payment successful for invoice ID " + invoiceID; message = "Payment successful for Invoice ID " + invoiceID;
sendNotification(currentUser, title, message); sendNotification(currentUser, title, message);
} }
} }
@@ -686,7 +686,7 @@ void ServiceManagementService::cancelTechnicianJobs(const std::string& technicia
continue; continue;
} }
std::string title = "Technician Unavailable"; 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."; 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, processBookingCancellation(booking,
util::ServiceJobStatus::PENDING, util::ServiceJobStatus::PENDING,
title, message, customer, title, message, customer,
@@ -1144,7 +1144,7 @@ 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."; std::string title = "Service Booking completed. Invoice Generated.";
std::string message = "Services completed for the booking and invoice generated."; std::string message = "Services completed for the booking and invoice generated.";
sendNotification(currentJob->getBooking()->getCustomer(), title, message); sendNotification(currentJob->getBooking()->getCustomer(), title, message);
} }
@@ -137,6 +137,10 @@ namespace util
{ {
return PaymentMode::OFFLINE; return PaymentMode::OFFLINE;
} }
if (value == "NOTSET")
{
return PaymentMode::NOTSET;
}
throw std::invalid_argument("Invalid PaymentMode string"); throw std::invalid_argument("Invalid PaymentMode string");
} }
@@ -11,10 +11,39 @@ Date: 22-May-2026
#include <fstream> #include <fstream>
#include <string> #include <string>
#include <stdexcept> #include <stdexcept>
#include <direct.h>
#include "Vector.h" #include "Vector.h"
namespace util 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 Function: loadRecords
Description: Loads records from a given file path into a vector of strings. Description: Loads records from a given file path into a vector of strings.
@@ -32,6 +61,7 @@ namespace util
std::ifstream file(filePath); std::ifstream file(filePath);
if (!file.is_open()) if (!file.is_open())
{ {
ensureDirectoryExists(filePath);
std::ofstream newFile(filePath); std::ofstream newFile(filePath);
newFile.close(); newFile.close();
file.open(filePath); file.open(filePath);
@@ -15,6 +15,7 @@ Date: 22-May-2026
#include <fstream> #include <fstream>
#include "Vector.h" #include "Vector.h"
#include "Map.h" #include "Map.h"
#include "FileHelper.h"
namespace util namespace util
{ {
@@ -51,6 +52,7 @@ namespace util
std::ifstream file(m_filePath); std::ifstream file(m_filePath);
if (!file.is_open()) if (!file.is_open())
{ {
ensureDirectoryExists(m_filePath);
std::ofstream newFile(m_filePath); std::ofstream newFile(m_filePath);
newFile.close(); newFile.close();
file.open(m_filePath); file.open(m_filePath);
@@ -434,7 +434,7 @@ void AdminMenu::removeService()
if (selectedServiceID != "") if (selectedServiceID != "")
{ {
m_controller.removeService(selectedServiceID); m_controller.removeService(selectedServiceID);
std::cout << "Service removed sucessfully.\n\n"; std::cout << "Service removed successfully.\n\n";
} }
else else
{ {
@@ -49,7 +49,7 @@ inline std::string selectServicesToRemove(util::Map<std::string, const Service*>
std::cout << std::left std::cout << std::left
<< std::setw(6) << "Index" << std::setw(6) << "Index"
<< std::setw(12) << "Service ID" << std::setw(12) << "Service ID"
<< std::setw(20) << "Name" << std::setw(35) << "Name"
<< std::setw(10) << "Labor Cost" << std::setw(10) << "Labor Cost"
<< std::endl; << std::endl;
for (int iterator = 0; iterator < currentServices.getSize(); iterator++) for (int iterator = 0; iterator < currentServices.getSize(); iterator++)
@@ -62,7 +62,7 @@ inline std::string selectServicesToRemove(util::Map<std::string, const Service*>
std::cout << std::left std::cout << std::left
<< std::setw(6) << currentIndex << std::setw(6) << currentIndex
<< std::setw(12) << currentService->getId() << std::setw(12) << currentService->getId()
<< std::setw(20) << currentService->getName() << std::setw(35) << util::truncateString(currentService->getName(), 30)
<< std::setw(10) << currentService->getLaborCost() << std::setw(10) << currentService->getLaborCost()
<< std::endl; << std::endl;
currentServicesMap.insert(currentIndex++, currentService); currentServicesMap.insert(currentIndex++, currentService);
@@ -453,12 +453,12 @@ inline const Invoice* selectInvoiceToDisplay(util::Map<std::string, const Invoic
<< std::left << std::left
<< std::setw(10) << "Index" << std::setw(10) << "Index"
<< std::setw(12) << "BookingID" << std::setw(12) << "BookingID"
<< std::setw(15) << "VehicleNumber" << std::setw(20) << "Vehicle Number"
<< std::setw(20) << "TechnicianName" << std::setw(20) << "Technician Name"
<< std::setw(15) << "TotalAmount" << std::setw(15) << "Total Amount"
<< std::setw(25) << "InvoiceDate" << std::setw(25) << "Invoice Date"
<< std::setw(15) << "PaymentStatus" << std::setw(20) << "Payment Status"
<< std::setw(15) << "PaymentMode" << std::setw(15) << "Payment Mode"
<< std::endl; << std::endl;
for (int iterator = 0; iterator < currentInvoices.getSize(); iterator++) for (int iterator = 0; iterator < currentInvoices.getSize(); iterator++)
{ {
@@ -471,11 +471,11 @@ inline const Invoice* selectInvoiceToDisplay(util::Map<std::string, const Invoic
std::cout << std::left std::cout << std::left
<< std::setw(10) << currentIndex << std::setw(10) << currentIndex
<< std::setw(12) << currentInvoice->getBookingId() << std::setw(12) << currentInvoice->getBookingId()
<< std::setw(15) << currentInvoice->getBooking()->getVehicleNumber() << std::setw(20) << currentInvoice->getBooking()->getVehicleNumber()
<< std::setw(20) << ((currentTechnician && !currentTechnician->getName().empty()) ? currentTechnician->getName() : "NULL") << std::setw(20) << ((currentTechnician && !currentTechnician->getName().empty()) ? currentTechnician->getName() : "NULL")
<< std::setw(15) << currentInvoice->getTotalAmount() << std::setw(15) << currentInvoice->getTotalAmount()
<< std::setw(25) << currentInvoice->getInvoiceDate().toString() << std::setw(25) << currentInvoice->getInvoiceDate().toString()
<< std::setw(15) << util::getPaymentStatusString(currentInvoice->getStatus()) << std::setw(20) << util::getPaymentStatusString(currentInvoice->getStatus())
<< std::setw(15) << util::getPaymentModeString(currentInvoice->getPaymentMethod()) << std::setw(15) << util::getPaymentModeString(currentInvoice->getPaymentMethod())
<< std::endl; << std::endl;
currentInvoicesIndexMap.insert(currentIndex++, currentInvoice); currentInvoicesIndexMap.insert(currentIndex++, currentInvoice);
@@ -1188,21 +1188,21 @@ inline void displayComboPackagesWithIndex(util::Map<int, const ComboPackage*>& c
const ComboPackage* currentComboPackage = currentComboPackageIndexMap.getValueAt(iterator); const ComboPackage* currentComboPackage = currentComboPackageIndexMap.getValueAt(iterator);
if (currentComboPackage == nullptr) 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) if (iterator == 0)
{ {
std::cout << std::left std::cout << std::left
<< std::setw(8) << "Index" << std::setw(8) << "Index"
<< std::setw(10) << "ID" << std::setw(10) << "ID"
<< std::setw(20) << "Package Name" << std::setw(35) << "Package Name"
<< std::setw(15) << "Discount (%)" << std::setw(15) << "Discount (%)"
<< "\n"; << "\n";
} }
std::cout << std::left std::cout << std::left
<< std::setw(8) << currentComboPackageIndexMap.getKeyAt(iterator) << std::setw(8) << currentComboPackageIndexMap.getKeyAt(iterator)
<< std::setw(10) << currentComboPackage->getId() << std::setw(10) << currentComboPackage->getId()
<< std::setw(20) << currentComboPackage->getPackageName() << std::setw(35) << util::truncateString(currentComboPackage->getPackageName(), 30)
<< std::setw(15) << currentComboPackage->getDiscountPercentage() << std::setw(15) << currentComboPackage->getDiscountPercentage()
<< "\n"; << "\n";
} }