Compare commits

...

19 Commits

Author SHA1 Message Date
joelthomastrenser 714067f309 Merged PR 1202: Vehicle Service System v1.2.0.0
## New Features

### Shared Memory DataStore

* Replaced the existing in-memory DataStore with a shared-memory implementation using Windows File Mapping.
* Added support for sharing application data between multiple running instances.
* Created separate mappings for users, notifications, services, bookings, invoices, payments, and other entities.

### Binary Data Storage

* Added binary serialization for all major entities.
* Introduced record tracking to handle new, modified, and deleted records.
* Added automatic growth and shrinking of mappings when required.

## Refactoring

### DataStore Changes

* Refactored DataStore to use shared memory instead of local process memory.
* Added generic helper functions for loading and saving records.
* Updated application startup and shutdown to initialize and clean up shared-memory resources.

### Synchronization

* Added a global mutex to synchronize datastore operations across processes.
* Added remapping logic to detect mapping size changes made by other instances.

## Bug Fixes

* Fixed issues with data not being shared correctly between multiple application instances.
* Improved reliability of record updates and deletions.
* Fixed stale mapping issues when mappings were resized by another process.

Related work items: #1925, #1926, #1927, #1928, #1929, #1930, #1949, #1950, #1951, #1952, #1953, #1954, #1955, #1956, #1957, #1958, #1959, #1960, #2061, #2074, #2075, #2076, #2077, #2078, #2079, #2080, #2081, #2082, #2100, #2105, #2114, #2115, #2116
2026-06-19 16:51:22 +05:30
Avinash Rajesh 05b9fc7962 Merged PR 1200: Fix Customer Allowed to Create Service Bookings While Having Unpaid Invoices
Changes:
- Added verifyAllPaymentsCompleted() helper in MenuHelper.h to check if the authenticated customer has completed all invoices.
- Integrated payment verification into CustomerMenu::selectService() to block new service bookings when pending payments exist.
- Integrated payment verification into CustomerMenu::selectComboPackage() to block new combo package bookings when invoices are incomplete.
- Implemented iteration over customer invoices to ensure only users with all payments marked as COMPLETED can proceed with new bookings.
- Added user-facing messages to inform customers when bookings are denied due to outstanding payments.

Related work items: #2116
2026-06-18 19:55:12 +05:30
joelthomastrenser 956ef58c79 Implement review fixes 2026-06-18 19:54:12 +05:30
Jissin Mathew 32f1fa33ce Merged PR 1199: Fix Multiple Notifications Contain Formatting Issues, Vague Messaging, and Duplicate Entries
Changes:

- Updated ServiceManagementService::createJobCard() to prevent duplicate
  "Technician assigned" notifications when multiple job cards exist for
  the same booking.
- Refined job card creation notification to include Job Card ID, Service
  ID, and Booking ID for clearer technician communication.
- Corrected service booking cancellation notification formatting by
  standardizing capitalization and improving message clarity.
- Improved service booking completion notification to explicitly mention
  the booking ID and confirm invoice generation.
- Ensured consistent notification titles and messages across the
  workflow for better user understanding.

Fixes

#2114

Related work items: #2114
2026-06-18 19:44:35 +05:30
Jissin Mathew 684d6d3860 Implement review fixes 2026-06-18 19:43:41 +05:30
joelthomastrenser 976547e7d1 Merged PR 1201: Fix invoice display issues and improve combo package handling
**Changes**:

- Removed inventory quantity from invoice item display.
- Added null checks when displaying combo packages.
- Added null checks when selecting combo packages.
- Replaced unnecessary object copies with references.
- Zero-initialized SerializedObserver.
- Initialized event handle array before use.

Fixes
#2115

Related work items: #2115
2026-06-18 19:37:31 +05:30
joelthomastrenser 4fa143946e Fix combo package null handling and cleanup initialization
- Added null checks in combo package display/selection flows
- Replaced unnecessary copies with references
- Zero-initialized SerializedObserver
- Initialized event handle array
2026-06-18 19:31:18 +05:30
Avinash Rajesh bdb8431773 Customer Allowed to Create Service Bookings While Having Unpaid Invoices
- Added verifyAllPaymentsCompleted() helper in MenuHelper.h to check if the authenticated customer has completed all invoices.

- Integrated payment verification into CustomerMenu::selectService() to block new service bookings when pending payments exist.

- Integrated payment verification into CustomerMenu::selectComboPackage() to block new combo package bookings when invoices are incomplete.

- Implemented iteration over customer invoices to ensure only users with all payments marked as COMPLETED can proceed with new bookings.

- Added user-facing messages to inform customers when bookings are denied due to outstanding payments.

Fixes #2116
2026-06-18 19:26:45 +05:30
Jissin Mathew 2eaa719aca Fix Multiple Notifications Contain Formatting Issues, Vague Messaging, and Duplicate Entries
Changes:

- Updated ServiceManagementService::createJobCard() to prevent duplicate
  "Technician assigned" notifications when multiple job cards exist for
  the same booking.
- Refined job card creation notification to include Job Card ID, Service
  ID, and Booking ID for clearer technician communication.
- Corrected service booking cancellation notification formatting by
  standardizing capitalization and improving message clarity.
- Improved service booking completion notification to explicitly mention
  the booking ID and confirm invoice generation.
- Ensured consistent notification titles and messages across the
  workflow for better user understanding.

Fixes #2114
2026-06-18 19:21:59 +05:30
joelthomastrenser 931913fa30 Fix: remove inventory quantity from invoice display
- Removed Quantity column from invoice item listing
- Removed inventory stock quantity values from invoice output
- Display only item name and price in generated invoices

Fixes #2115
2026-06-18 19:20:02 +05:30
Jissin Mathew b983337630 Merged PR 1196: Fix Service Booking Cancellation, Job Completion Exception, and Inventory Quantity Handling
Changes

- Added Controller::removeServiceBooking() with documentation and delegation to ServiceManagementService.

- Implemented ServiceManagementService::removeServiceBooking() to handle cancellation of pending bookings, enforce status validation, send notifications, and persist changes safely.

- Updated CustomerMenu to include a "Cancel Service Booking" option, wired into handleOperation, and implemented CustomerMenu::cancelServiceBooking() for user interaction.

- Ensured consistent declarations in Controller.h, ServiceManagementService.h, and CustomerMenu.h.

- Refactored ServiceManagementService::updateJobStatus() to use tracked job card references instead of raw pointers, ensuring consistent state updates.

- Added proper null checks and error handling for current job retrieval to prevent unexpected termination.

- Updated logic to mark tracked job records as MODIFIED when status transitions occur (`STARTED` → `IN_PROGRESS`, `IN_PROGRESS` → `COMPLETED`).

- Simplified control flow and indentation for better readability and maintainability.

- Added retrieval of tracked service bookings and inventory items in ServiceManagementService::createJobCard().

- Validated service booking ID and inventory item indices before proceeding with job card creation.

- Decremented inventory item quantities when job cards are created and marked corresponding tracked inventory records as `MODIFIED`.

- Updated tracked service booking state to `MODIFIED` when technician is assigned and job status changes.

- Persisted changes by saving job cards, service bookings, and inventory items to the datastore.

Fixes

#2105
#2076
#2075
#2074
2026-06-18 11:36:43 +05:30
joelthomastrenser 5155192f6d Merge branch 'develop' into develop-sm-bugfix-2 2026-06-18 11:36:25 +05:30
Jissin Mathew ca0e277586 Implement Review fixes 2026-06-18 11:33:10 +05:30
Avinash Rajesh 69b7c31100 Merged PR 1195: Fix notification display and service dependency handling issues
**Changes:**
- Persist service bookings immediately after purchase to ensure data consistency.
- Enhanced notification display by appending authenticated user names to notification titles.
- Updated account disabled event handling to include authenticated user names in warning messages.
- Modified notification event handling in Admin, Customer, and Technician menus to pass authenticated user names.
- Improved service selection logic to skip services with depleted inventory items.
- Added dependency cleanup: services requiring a removed inventory item are automatically removed.
- Updated displayNewNotification to extract numeric IDs correctly and append authenticated user names to notification titles.
- Included StringHelper for improved string operations in notification handling.

#2080
#2081
#2082

Related work items: #2080, #2081, #2082
2026-06-18 11:17:11 +05:30
Jissin Mathew b45463a66d Fix Cancel Service Booking Functionality
Changes:

- Added Controller::removeServiceBooking() with proper documentation and
  delegation to ServiceManagementService.
- Implemented ServiceManagementService::removeServiceBooking() to handle
  cancellation of pending bookings, enforce status validation, send
  notifications, and persist changes safely.
- Updated CustomerMenu to include a "Cancel Service Booking" option in
  the menu, wired it into handleOperation, and implemented
  CustomerMenu::cancelServiceBooking() for user interaction.
- Ensured consistent declarations in Controller.h,
  ServiceManagementService.h, and CustomerMenu.h.

Fixes #2105
2026-06-18 10:14:51 +05:30
Jissin Mathew bb0d186b62 Fix Completing a Job Throws an Exception
Changes:

- Refactored ServiceManagementService::updateJobStatus() to use tracked
  job card references instead of raw pointers, ensuring consistent state
  updates.
- Added proper null checks and error handling for current job retrieval
  to prevent unexpected termination.
- Updated logic to mark tracked job records as MODIFIED when status
  transitions occur (STARTED → IN_PROGRESS, IN_PROGRESS → COMPLETED).
- Simplified control flow and indentation for better readability and
  maintainability.

Fixes #2076
2026-06-17 17:01:34 +05:30
Jissin Mathew 4243f4e43f Fix Inventory Quantity Is Not Decremented When Job Card Is Created
Changes:

- Added retrieval of tracked service bookings and inventory items in
  ServiceManagementService::createJobCard().
- Validated service booking ID and inventory item indices before
  proceeding with job card creation.
- Decremented inventory item quantities when job cards are created and
  marked corresponding tracked inventory records as MODIFIED.
- Updated tracked service booking state to MODIFIED when technician is
  assigned and job status changes.
- Persisted changes by saving job cards, service bookings, and inventory
  items to the datastore.

Fixes #2075
2026-06-17 15:29:46 +05:30
Jissin Mathew 974d4efe02 Fix Service Booking Creation Is Not Synchronized Between Processes
Changes:

- Updated ServiceManagementService::purchaseService() to persist service
  bookings immediately after creation using m_dataStore.saveServiceBookings().
- Updated ServiceManagementService::purchaseComboPackage() to persist
  combo package bookings immediately after creation using
  m_dataStore.saveServiceBookings().
- Changed DataStore::getJobCards() to use a reference for tracked bookings
  to ensure consistent linkage and prevent overwriting.

Fixes #2074
2026-06-17 14:47:37 +05:30
joelthomastrenser 8a3ec278ce Merged PR 1159: Vehicle Service System v1.1.0.0
**New Features**

**Payment Confirmation Workflow**
- Added support for payment confirmation by administrators.

- Introduced a new PAID payment status.

- Implemented invoice confirmation flow from PAID → COMPLETED.

- Added invoice filtering based on payment status.

- Added admin menu option to confirm customer payments.

**Technician Job Status Workflow**
- Enhanced technician job management with multi-stage status updates.

- Added support for job status transitions:

- STARTED → IN_PROGRESS

- IN_PROGRESS → COMPLETED

- Added invoice generation and customer notification upon booking completion.

- Improved job visibility by displaying current job status.

**UI Improvements**
- Improved formatting and wording in the Technician Job Status workflow.

- Standardized status labels using "In Progress".

- Improved prompts, headings, and job selection messages.

- Enhanced readability of job listings.

**Bug Fixes**
- Fixed duplicate customer notifications when an assigned technician is removed.

- Prevented creation of duplicate usernames across all user states.

- Fixed authentication conflicts caused by reuse of deleted/disabled usernames.

- Improved booking cancellation handling for customer and technician removal scenarios.

- Updated cancellation logic to correctly handle bookings in IN_PROGRESS state.

Related work items: #1797, #1798, #1807, #1808, #1809
2026-06-01 18:57:46 +05:30
9 changed files with 296 additions and 89 deletions
@@ -234,6 +234,17 @@ void Controller::removeInventoryItem(const std::string& inventoryItemID)
m_inventoryManagementService.removeInventoryItem(inventoryItemID);
}
/*
Function: removeServiceBooking
Description: Removes a service booking from the service management system by its booking ID.
Parameter: const std::string& bookingID - ID of the service booking
Return type: void
*/
void Controller::removeServiceBooking(const std::string& bookingID)
{
m_serviceManagementService.removeServiceBooking(bookingID);
}
/*
Function: addInventoryItemStock
Description: Adds stock to an existing inventory item in the inventory management service.
@@ -265,6 +276,7 @@ util::Map<std::string, const ServiceBooking*> Controller::getServiceBookings()
return readOnlyServiceBookings;
}
/*
Function: getServiceBookingsByUser
Description: Retrieves all service bookings for a specific user.
@@ -58,6 +58,7 @@ public:
util::Map<std::string, const User*> getUsers(util::UserType userType);
void createJobCard(const std::string& bookingID, const std::string& technicianID, const std::string& serviceID);
void createService(const std::string& name, const util::Vector<std::string>& inventoryItemIDs, double laborCost);
void removeServiceBooking(const std::string& bookingID);
void removeService(const std::string& serviceID);
util::Map<std::string, const JobCard*> getJobCardsByUser();
void updateJobStatus(const std::string& jobID);
@@ -358,7 +358,7 @@ util::Map<std::string, TrackedRecord<ServiceBooking>>& DataStore::getServiceBook
{
throw std::runtime_error("Invalid service index.");
}
auto currentService = services.getValueAt(serviceIndex);
auto& currentService = services.getValueAt(serviceIndex);
servicesInBooking[currentServiceId] = currentService.data;
}
serviceBooking->setServices(servicesInBooking);
@@ -369,7 +369,7 @@ util::Map<std::string, TrackedRecord<ServiceBooking>>& DataStore::getServiceBook
{
throw std::runtime_error("Invalid user index.");
}
auto customer = users.getValueAt(userIndex);
auto& customer = users.getValueAt(userIndex);
serviceBooking->setCustomer(customer.data);
}
if (!serviceBooking->getAssignedTechnicianId().empty())
@@ -379,7 +379,7 @@ util::Map<std::string, TrackedRecord<ServiceBooking>>& DataStore::getServiceBook
{
throw std::runtime_error("Invalid technician index.");
}
auto technician = users.getValueAt(technicianIndex);
auto& technician = users.getValueAt(technicianIndex);
serviceBooking->setAssignedTechnician(technician.data);
}
}
@@ -415,7 +415,7 @@ util::Map<std::string, TrackedRecord<JobCard>>& DataStore::getJobCards()
{
throw std::runtime_error("Invalid booking ID: " + bookingId);
}
auto trackedBooking = serviceBookings.getValueAt(bookingIndex);
auto& trackedBooking = serviceBookings.getValueAt(bookingIndex);
jobCard->setBooking(trackedBooking.data);
const std::string& serviceId = jobCard->getServiceId();
int serviceIndex = services.find(serviceId);
@@ -423,7 +423,7 @@ util::Map<std::string, TrackedRecord<JobCard>>& DataStore::getJobCards()
{
throw std::runtime_error("Invalid service ID: " + serviceId);
}
auto trackedService = services.getValueAt(serviceIndex);
auto& trackedService = services.getValueAt(serviceIndex);
jobCard->setService(trackedService.data);
const std::string& technicianId = jobCard->getTechnicianId();
if (!technicianId.empty())
@@ -433,7 +433,7 @@ util::Map<std::string, TrackedRecord<JobCard>>& DataStore::getJobCards()
{
throw std::runtime_error("Invalid technician ID: " + technicianId);
}
auto trackedTechnician = users.getValueAt(technicianIndex);
auto& trackedTechnician = users.getValueAt(technicianIndex);
jobCard->setTechnician(trackedTechnician.data);
}
}
@@ -691,7 +691,7 @@ void DataStore::saveObservers(MappingInfo& mapping, util::Map<std::string, User*
SharedMemory::setRecordCount(mapping, observerCount);
for (size_t index = 0; index < observerCount; index++)
{
SerializedObserver serializedObserver;
SerializedObserver serializedObserver{};
User* user = observers.getValueAt(static_cast<int>(index));
strcpy_s(serializedObserver.id, sizeof(serializedObserver.id), user->getId().c_str());
SerializedObserver* destination = static_cast<SerializedObserver*>(SharedMemory::getRecordAddress(mapping, index));
@@ -26,7 +26,6 @@ Date:19-May-2026
#include "UserManagementService.h"
#include "DataStoreLockGuard.h"
#include "Utility.h"
#include "DataStoreLockGuard.h"
#include "EventManager.h"
/*
@@ -141,6 +140,7 @@ void ServiceManagementService::purchaseComboPackage(const std::string& comboPack
std::string title = "Combo Package Service Booking succeeded";
std::string message = "Your service booking for the combo package has been successfully placed with ID " + serviceBooking->getId();
sendNotification(authenticatedUser, title, message);
m_dataStore.saveServiceBookings();
notifyAllAdmins("New Combo Package Order Available", "A new combo package order has been placed with Service Booking ID " + serviceBooking->getId(), m_dataStore.getUsers(), this);
}
@@ -629,11 +629,24 @@ void ServiceManagementService::createJobCard(const std::string& bookingID, const
DataStoreLockGuard lock(m_dataStore);
UserManagementService m_userManagementService;
ServiceBooking* currentBooking = getServiceBooking(bookingID);
std::string title, message;
if (currentBooking == nullptr)
{
throw std::runtime_error("Service Booking not available");
}
if (currentBooking->getStatus() == util::ServiceJobStatus::CANCELLED)
{
throw std::runtime_error("Cannot create job card. Service Booking was cancelled!");
}
auto& currentTrackedJobCards = m_dataStore.getJobCards();
if (currentBooking == nullptr)
{
throw std::runtime_error("Service Booking not available");
}
auto& currentTrackedInventoryItems = m_dataStore.getInventoryItems();
auto& currentTrackedServiceBookings = m_dataStore.getServiceBookings();
int currentTrackedServiceBookingIndex = currentTrackedServiceBookings.find(bookingID);
if (currentTrackedServiceBookingIndex == -1)
{
throw std::runtime_error("Invalid service booking id.");
}
auto& currentTrackedServiceBooking = currentTrackedServiceBookings.getValueAt(currentTrackedServiceBookingIndex);
auto& currentServices = currentBooking->getServices();
if (currentServices.find(serviceID) == -1)
{
@@ -658,23 +671,44 @@ void ServiceManagementService::createJobCard(const std::string& bookingID, const
for (int iterator = 0; iterator < inventoryItems.getSize(); iterator++)
{
InventoryItem* currentInventoryItem = inventoryItems.getValueAt(iterator);
const std::string& currentInventoryItemId = inventoryItems.getKeyAt(iterator);
if (currentInventoryItem)
{
int trackedCurrentInventoryItemIndex = currentTrackedInventoryItems.find(currentInventoryItemId);
if (trackedCurrentInventoryItemIndex == -1)
{
throw std::runtime_error("Invalid inventory item index.");
}
auto& trackedCurrentInventoryItem = currentTrackedInventoryItems.getValueAt(trackedCurrentInventoryItemIndex);
int currentStockQuantity = currentInventoryItem->getQuantity();
currentInventoryItem->setQuantity(currentStockQuantity - 1);
trackedCurrentInventoryItem.state = RecordState::MODIFIED;
}
}
currentBooking->setAssignedTechnician(selectedTechnician);
currentBooking->setAssignedTechnicianId(selectedTechnician->getId());
const User* currentAssignedTechnician = currentBooking->getAssignedTechnician();
const std::string& currentAssignedTechnicianId = currentBooking->getAssignedTechnicianId();
if (!currentAssignedTechnician && currentAssignedTechnicianId.empty())
{
currentBooking->setAssignedTechnician(selectedTechnician);
currentBooking->setAssignedTechnicianId(selectedTechnician->getId());
title = "Technician assigned";
message = "A technician has been assigned to your Service Booking with ID " + bookingID;
sendNotification(currentBooking->getCustomer(), title, message);
}
if (currentBooking->getStatus() == util::ServiceJobStatus::PENDING)
{
currentBooking->setStatus(util::ServiceJobStatus::STARTED);
}
std::string title = "Job card created";
std::string message = "Job card created for the service and you are assigned for that.";
currentTrackedServiceBooking.state = RecordState::MODIFIED;
JobCard* jobCard = Factory::getObject<JobCard>(bookingID, currentBooking, currentService, serviceID, technicianID, selectedTechnician, util::Timestamp(), util::ServiceJobStatus::STARTED, util::Timestamp());
if (jobCard)
{
title = "Job Card Assigned";
message = "A new Job Card (ID: " + jobCard->getId() +
") has been created for Service " + serviceID +
" in Booking " + bookingID +
". You have been assigned to this job.";
currentTrackedJobCards.insert(jobCard->getId(), util::createNewRecord(jobCard));
sendNotification(selectedTechnician, title, message);
}
@@ -682,10 +716,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);
m_dataStore.saveJobCards();
m_dataStore.saveServiceBookings();
m_dataStore.saveInventoryItems();
}
/*
@@ -807,6 +840,61 @@ void ServiceManagementService::removeService(const std::string& serviceID)
m_dataStore.saveComboPackages();
}
/*
Function: removeServiceBooking
Description: Removes a pending service booking by its ID.
Cancels only if status is PENDING, otherwise throws exceptions
for invalid states. Sends notification to the customer and
persists changes.
Parameter: const std::string& bookingID - ID of the service booking
Return type: void
*/
void ServiceManagementService::removeServiceBooking(const std::string& bookingID)
{
DataStoreLockGuard lock(m_dataStore);
auto& trackedServiceBookings = m_dataStore.getServiceBookings();
bool serviceBookingRemoved = false;
for (int iterator = 0; iterator < trackedServiceBookings.getSize(); iterator++)
{
auto& currentTrackedServiceBooking = trackedServiceBookings.getValueAt(iterator);
ServiceBooking* currentServiceBooking = currentTrackedServiceBooking.data;
if (currentServiceBooking && currentServiceBooking->getId() == bookingID)
{
if (currentServiceBooking->getStatus() == util::ServiceJobStatus::PENDING)
{
const std::string title = "Service Booking Cancelled";
const std::string message = "Service Booking (ID: " + bookingID + ") has been successfully cancelled";
currentServiceBooking->setStatus(util::ServiceJobStatus::CANCELLED);
currentTrackedServiceBooking.state = RecordState::MODIFIED;
serviceBookingRemoved = true;
sendNotification(currentServiceBooking->getCustomer(), title, message);
break;
}
else if(currentServiceBooking->getStatus() == util::ServiceJobStatus::COMPLETED)
{
throw std::runtime_error("Unable to cancel completed service booking.");
}
else if (currentServiceBooking->getStatus() == util::ServiceJobStatus::STARTED)
{
throw std::runtime_error("Unable to cancel started service booking.");
}
else if (currentServiceBooking->getStatus() == util::ServiceJobStatus::IN_PROGRESS)
{
throw std::runtime_error("Unable to cancel currently Inprogress service booking.");
}
else
{
throw std::runtime_error("Service Booking already cancelled.");
}
}
}
if (!serviceBookingRemoved)
{
throw std::runtime_error("Unable to cancel service booking.");
}
m_dataStore.saveServiceBookings();
}
/*
Function: getServiceBookings (overloaded)
Description: Retrieves all service bookings for a specific customer.
@@ -901,44 +989,44 @@ void ServiceManagementService::updateJobStatus(const std::string& jobID)
{
AuthenticationManagementService::ensureAuthorization();
DataStoreLockGuard lock(m_dataStore);
AuthenticationManagementService authenticationManagementService;
PaymentManagementService paymentManagementService;
bool jobStatusUpdated = false, serviceBookingCompleted;
JobCard* currentJob;
User* currentTechnician = authenticationManagementService.getAuthenticatedUser();
if (currentTechnician == nullptr)
{
throw std::runtime_error("Unable to fetch current technician.");
}
util::Map<std::string, JobCard*> currentAssignedJobs = getJobCards(currentTechnician->getId());
if (currentAssignedJobs.getSize() == 0)
{
throw std::runtime_error("No job cards assigned to the technician.");
}
AuthenticationManagementService authenticationManagementService;
PaymentManagementService paymentManagementService;
bool jobStatusUpdated = false, serviceBookingCompleted;
User* currentTechnician = authenticationManagementService.getAuthenticatedUser();
if (currentTechnician == nullptr)
{
throw std::runtime_error("Unable to fetch current technician.");
}
util::Map<std::string, JobCard*> currentAssignedJobs = getJobCards(currentTechnician->getId());
if (currentAssignedJobs.getSize() == 0)
{
throw std::runtime_error("No job cards assigned to the technician.");
}
auto& trackedJobCards = m_dataStore.getJobCards();
auto& trackedServiceBookings = m_dataStore.getServiceBookings();
if (currentAssignedJobs.find(jobID) != -1)
{
if (currentAssignedJobs.find(jobID) != -1)
{
int jobIndex = trackedJobCards.find(jobID);
if (jobIndex == -1)
{
throw std::runtime_error("Unable to fetch current job.");
}
currentJob = currentAssignedJobs.getValueAt(currentAssignedJobs.find(jobID));
if (currentJob == nullptr)
{
throw std::runtime_error("Unable to fetch current job.");
}
if (currentJob->getStatus() == util::ServiceJobStatus::STARTED)
{
currentJob->setStatus(util::ServiceJobStatus::IN_PROGRESS);
trackedJobCards.getValueAt(jobIndex).state = RecordState::MODIFIED;
jobStatusUpdated = true;
}
auto& trackedCurrentJob = trackedJobCards.getValueAt(jobIndex);
JobCard* currentJob = trackedCurrentJob.data;
if (currentJob == nullptr)
{
throw std::runtime_error("Unable to fetch current job.");
}
if (currentJob->getStatus() == util::ServiceJobStatus::STARTED)
{
currentJob->setStatus(util::ServiceJobStatus::IN_PROGRESS);
trackedCurrentJob.state = RecordState::MODIFIED;
jobStatusUpdated = true;
}
else if (currentJob->getStatus() == util::ServiceJobStatus::IN_PROGRESS)
{
currentJob->setStatus(util::ServiceJobStatus::COMPLETED);
trackedJobCards.getValueAt(jobIndex).state = RecordState::MODIFIED;
trackedCurrentJob.state = RecordState::MODIFIED;
jobStatusUpdated = true;
serviceBookingCompleted = hasCompletedAllJobs(currentJob->getBookingId(), currentAssignedJobs);
if (serviceBookingCompleted)
@@ -947,20 +1035,20 @@ void ServiceManagementService::updateJobStatus(const std::string& jobID)
currentJob->getBooking()->setStatus(util::ServiceJobStatus::COMPLETED);
trackedServiceBookings.getValueAt(trackedServiceBookings.find(bookingId)).state = RecordState::MODIFIED;
paymentManagementService.generateInvoice(currentJob->getBooking());
std::string title = "Service Booking completed. Invoice Generated.";
std::string message = "Services completed for the booking and invoice generated.";
std::string title = "Service Booking Completed";
std::string message = "Service Booking (ID: " + bookingId + ") has been completed successfully. An invoice has been generated.";
sendNotification(currentJob->getBooking()->getCustomer(), title, message);
}
}
}
else
{
throw std::runtime_error("Failed to update job status. Job may already be completed.");
}
if (!jobStatusUpdated)
{
throw std::runtime_error("Failed to update job status. Job may already be completed.");
}
}
else
{
throw std::runtime_error("Failed to update job status. Job may already be completed.");
}
if (!jobStatusUpdated)
{
throw std::runtime_error("Failed to update job status. Job may already be completed.");
}
m_dataStore.saveJobCards();
m_dataStore.saveServiceBookings();
}
@@ -35,6 +35,7 @@ public:
void createJobCard(const std::string& bookingID, const std::string& technicianID, const std::string& serviceID);
void createService(const std::string& name, const util::Vector<std::string>& inventoryItemIDs, double laborCost);
void removeService(const std::string& serviceID);
void removeServiceBooking(const std::string& bookingID);
util::Map<std::string, JobCard*> getJobCards(const std::string& technicianID);
void updateJobStatus(const std::string& jobID);
void cancelCustomerServiceBookings(const std::string& customerID);
@@ -51,11 +51,12 @@ void CustomerMenu::showMenu()
<< "\n3. Update Profile"
<< "\n4. Change Password"
<< "\n5. View Service History"
<< "\n6. Complete Payments"
<< "\n7. View Invoices"
<< "\n8. View Notifications"
<< "\n9. Configure Notifications"
<< "\n10. Logout"
<< "\n6. Cancel Service Booking"
<< "\n7. Complete Payments"
<< "\n8. View Invoices"
<< "\n9. View Notifications"
<< "\n10. Configure Notifications"
<< "\n11. Logout"
<< "\nEnter a choice: ";
util::read(choice);
if (!handleOperation(choice))
@@ -103,18 +104,21 @@ bool CustomerMenu::handleOperation(int choice)
viewServiceHistory();
break;
case 6:
completePayments();
cancelServiceBooking();
break;
case 7:
viewInvoices();
completePayments();
break;
case 8:
viewNotifications();
viewInvoices();
break;
case 9:
configureNotifications();
viewNotifications();
break;
case 10:
configureNotifications();
break;
case 11:
logout();
return false;
default:
@@ -216,6 +220,13 @@ void CustomerMenu::selectService()
util::pressEnter();
return;
}
if (!verifyAllPaymentsCompleted(m_controller))
{
std::cout << "Your booking cannot be processed because you have pending payments for previous services. Please complete all outstanding invoices before booking a new service."
<< std::endl;
util::pressEnter();
return;
}
util::Vector<std::string> selectedServices;
const Service* selectedService = selectServiceFromServices(services);
if (selectedService == nullptr)
@@ -258,6 +269,13 @@ void CustomerMenu::selectComboPackage()
util::pressEnter();
return;
}
if (!verifyAllPaymentsCompleted(m_controller))
{
std::cout << "Your booking cannot be processed because you have pending payments for previous services. Please complete all outstanding invoices before booking a new combo package."
<< std::endl;
util::pressEnter();
return;
}
const ComboPackage* selectedComboPackage = selectComboPackageFromPackages(activeComboPackages);
if (selectedComboPackage == nullptr)
{
@@ -333,6 +351,46 @@ void CustomerMenu::viewServiceHistory()
util::pressEnter();
}
/*
Function: cancelServiceBooking
Description: Allows the customer to cancel a pending service booking.
Displays the list of active bookings, lets the user select one,
and removes it from the system. If no bookings are available,
an appropriate message is shown.
Parameter: None
Return type: void
*/
void CustomerMenu::cancelServiceBooking()
{
util::clear();
std::cout << "Cancel Service Booking\n";
const User* currentUser = m_controller.getAuthenticatedUser();
std::string currentUserID = currentUser->getId();
util::Map<std::string, const ServiceBooking*> serviceBookingsByCurrentUser = m_controller.getServiceBookingsByUser(currentUserID);
util::Map<int, const ServiceBooking*> serviceBookingsMap;
auto currentPendingServiceBookings = filterActiveServiceBookings(serviceBookingsByCurrentUser);
int bookingsSize = currentPendingServiceBookings.getSize();
if (listServiceBookings(currentPendingServiceBookings, bookingsSize, serviceBookingsMap))
{
const ServiceBooking* selectedService = selectPendingServiceBookings(serviceBookingsMap);
if (selectedService)
{
m_controller.removeServiceBooking(selectedService->getId());
std::cout << "Cancelled Service booking of id " + selectedService->getId() << std::endl << std::endl;
}
else
{
std::cout << "Invalid service booking index.\n\n";
return;
}
}
else
{
std::cout << "No pending service bookings available.\n\n";
}
util::pressEnter();
}
/*
Function: completePayments
Description: Allows the customer to complete pending payments for invoices.
@@ -28,5 +28,6 @@ public:
void completePayments();
void viewInvoices();
void viewNotifications();
void cancelServiceBooking();
void configureNotifications();
};
};
@@ -67,7 +67,7 @@ Return type: void
*/
void Menu::eventListenerLoop()
{
HANDLE handles[3];
HANDLE handles[3] = { NULL, NULL, NULL };
handles[0] = m_accountDisabledEvent;
handles[1] = m_notificationAvailableEvent;
handles[2] = m_shutdownEvent;
@@ -588,7 +588,7 @@ inline void displayInvoices(util::Map<std::string, const Invoice*> currentUserIn
<< 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();
auto& inventoryItemsInInvoice = selectedInvoice->getParts();
if (inventoryItemsInInvoice.isEmpty())
{
std::cout << "No inventory items used.\n\n";
@@ -597,7 +597,6 @@ inline void displayInvoices(util::Map<std::string, const Invoice*> currentUserIn
std::cout << "\nItems Used:\n";
std::cout << std::left
<< std::setw(20) << "ItemName"
<< std::setw(10) << "Quantity"
<< std::setw(10) << "Price"
<< std::endl;
std::cout << std::string(40, '-') << std::endl;
@@ -606,7 +605,6 @@ inline void displayInvoices(util::Map<std::string, const Invoice*> currentUserIn
InventoryItem* currentItem = inventoryItemsInInvoice.getValueAt(iterator);
std::cout << std::left
<< std::setw(20) << currentItem->getPartName()
<< std::setw(10) << currentItem->getQuantity()
<< std::setw(10) << currentItem->getPrice()
<< std::endl;
}
@@ -1146,15 +1144,18 @@ inline void displayAllComboPackages(util::Map<std::string, const ComboPackage*>
for (int index = 0; index < comboPackages.getSize(); index++)
{
const ComboPackage* currentComboPackage = comboPackages.getValueAt(index);
if (currentComboPackage && currentComboPackage->getState() != util::State::ACTIVE)
if (currentComboPackage)
{
continue;
if (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;
}
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;
}
}
@@ -1180,18 +1181,21 @@ inline const ComboPackage* selectComboPackageFromPackages(const util::Map<std::s
for (int index = 0; index < comboPackages.getSize(); index++)
{
const ComboPackage* currentComboPackage = comboPackages.getValueAt(index);
if (currentComboPackage && currentComboPackage->getState() != util::State::ACTIVE)
if (currentComboPackage)
{
continue;
if (currentComboPackage->getState() != util::State::ACTIVE)
{
continue;
}
activeComboPackages.insert(currentIndex, currentComboPackage);
std::cout << std::left
<< std::setw(10) << currentIndex
<< std::setw(15) << currentComboPackage->getId()
<< std::setw(35) << util::truncateString(currentComboPackage->getPackageName(), 30)
<< std::setw(15) << util::calculateComboServiceEstimatedCost(currentComboPackage)
<< std::endl;
currentIndex++;
}
activeComboPackages.insert(currentIndex, currentComboPackage);
std::cout << std::left
<< std::setw(10) << currentIndex
<< std::setw(15) << currentComboPackage->getId()
<< std::setw(35) << util::truncateString(currentComboPackage->getPackageName(), 30)
<< std::setw(15) << util::calculateComboServiceEstimatedCost(currentComboPackage)
<< std::endl;
currentIndex++;
}
if (activeComboPackages.getSize() == 0)
{
@@ -1467,3 +1471,45 @@ inline void displayNewNotification(util::Vector<const Notification*> notificatio
MB_ICONINFORMATION);
}
}
/*
Function: verifyAllPaymentsCompleted
Description: Checks whether the authenticated customer has completed
all payments for their invoices. Iterates through all
invoices belonging to the customer and verifies that
each invoice has a payment status of COMPLETED.
Parameters: Controller& m_controller -
reference to the Controller object used to access
authenticated user and invoice data
Return type: bool
true if all invoices for the authenticated customer
are completed, false if any invoice is pending or not completed
Throws: std::runtime_error if no authenticated user is found
*/
inline bool verifyAllPaymentsCompleted(Controller& m_controller)
{
const User* authenticatedUser = m_controller.getAuthenticatedUser();
if (!authenticatedUser)
{
throw std::runtime_error("No authenticated user found.");
}
const std::string& authenticatedUserId = authenticatedUser->getId();
util::Map<std::string, const Invoice*> listOfInvoices = m_controller.getAllInvoices();
for (int invoiceIndex = 0; invoiceIndex < listOfInvoices.getSize(); ++invoiceIndex)
{
const Invoice* invoice = listOfInvoices.getValueAt(invoiceIndex);
if (!invoice)
{
continue;
}
const std::string& customerId = invoice->getBooking()->getCustomerId();
if (customerId == authenticatedUserId)
{
if (invoice->getStatus() != util::PaymentStatus::COMPLETED)
{
return false;
}
}
}
return true;
}