Compare commits

...

22 Commits

Author SHA1 Message Date
Jissin Mathew 684d6d3860 Implement review fixes 2026-06-18 19:43:41 +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
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
joelthomastrenser 9fa58b030e Merge branch 'develop' into develop-sm-bugfix-3 2026-06-18 11:17:01 +05:30
joelthomastrenser ac552d669a Merged PR 1194: Fix shared memory notification and authorization handling issues
Changes:
- Fixed DataStore cache refresh from overwriting records with pending local modifications.
- Fixed notification ID collisions by refreshing the notification cache before creating new notifications.
- Added authorization state tracking to authenticated sessions.
- Revoked authorization immediately when account-disabled events are received.
- Added authorization validation for protected service operations.
- Cleared authorization state during logout.
- Added admin notifications for newly created service bookings.
- Added admin notifications for newly created combo package bookings.
- Included Service Booking IDs in admin notification messages.

#2077 #2078 #2078 #2100

Related work items: #2077, #2078, #2079, #2100
2026-06-18 11:05:57 +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
Avinash Rajesh 1651dfeafe Service Can Still Be Purchased After Required Inventory Item Is Removed
Changes:

- Added m_dataStore.saveServiceBookings() in ServiceManagementService::purchaseService() to persist bookings immediately after successful purchase.

- Enhanced AdminMenu::removeInventoryItem() to also remove all services that depend on the removed inventory item, ensuring consistency between inventory and service availability.

- Updated selectServiceFromServices() in MenuHelper.h to skip services if any of their required inventory items have a quantity less than 1, preventing users from selecting unavailable services.

- Introduced inventory depletion checks before inserting services into the active services map, improving reliability of service selection.

Fixes  #2082
2026-06-17 18:10:12 +05:30
joelthomastrenser 468106952f Notify admins when new service bookings are created
Changes:
- Added notifyAllAdmins() helper in ServiceManagementService.
- Sent notifications to all administrators when a customer places a service booking.
- Sent notifications to all administrators when a customer purchases a combo package.
- Included the generated Service Booking ID in admin notifications.

Fixes #2100
2026-06-17 17:39:45 +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
joelthomastrenser 82164e42c3 Stop disabled users from performing operations after account deactivation
Changes:
- Added authorization state tracking to authenticated sessions.
- Revoked authorization immediately when an account-disabled event is received.
- Added authorization validation before protected service operations.
- Cleared authorization state on logout.

Fixes #2078
2026-06-17 16:47:15 +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
Avinash Rajesh 9d166362a7 Latest Notification Is Determined Using String Comparison
Changes:

- Added #include "StringHelper.h" to MenuHelper.h for utility functions.

- Updated displayNewNotification() to compare notification IDs using util::extractNumber() instead of raw string comparison.

- Ensured numeric ordering of notifications by extracting and comparing integer values from IDs.

- Preserved existing notification selection logic while improving accuracy of ID comparisons.

Fixes #2081
2026-06-17 15:17:55 +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
Avinash Rajesh 1179f92849 Notification Dialog Does Not Indicate Intended Recipient
Changes:

- Updated AdminMenu::handleNotificationEvent, CustomerMenu::handleNotificationEvent, and TechnicianMenu::handleNotificationEvent to retrieve the authenticated user and pass their name to displayNewNotification().

- Modified Menu::handleAccountDisabledEvent to append the authenticated user’s name to the message box title when available.

- Refactored displayNewNotification() in MenuHelper.h to accept the authenticated user’s name as an additional parameter.

- Enhanced notification display logic to append the user’s name to the notification title when present.

- Added null checks to ensure safe handling when no authenticated user exists.

FIxes #2080
2026-06-17 14:20:20 +05:30
joelthomastrenser b011f600cf Fix notification overwrite caused by stale notification cache
Changes:
- Retrieved the notifications map before creating a new notification in sendNotification().
- Ensured the notification cache is refreshed before generating a notification ID.
- Prevented newly created notifications from overwriting notifications added by other operations prior to save.

Fixes #2079
2026-06-17 14:07:38 +05:30
joelthomastrenser 78e6ae2fec Fix cache refresh from overwriting modified records
Changes:
- Added a check for RecordState::MODIFIED in DataStore::refreshCache().
- Preserved modified cache records during refresh instead of overwriting them with datastore values.
- Discarded refreshed records when a corresponding cached record has pending changes.

Fixes #2077
2026-06-17 11:30:15 +05:30
joelthomastrenser 528075e0cd Merged PR 1178: Implement interprocess event handling for notifications and account disable
Implement interprocess event handling for notifications and account disable

Changes:
- Implements #2061
- Introduce EventManager for user-specific Windows event publishing/listening
- Add real-time notification and account-disabled event propagation
- Register authentication events through Controller and AuthenticationManagementService
- Trigger notification events from Inventory, Payment, and Service Management modules
- Trigger account-disabled events when users are deactivated
- Extract common menu event listener logic into Menu base class
- Add notification popup handling for Admin, Customer, and Technician menus
- Refactor shared memory components into core/sharedmemory
- Update project structure and include paths for events and shared memory modules

Related work items: #1928, #2061
2026-06-16 11:29:17 +05:30
joelthomastrenser ae37b83cbc Implement review fixes 2026-06-16 11:29:00 +05:30
joelthomastrenser 8aaa4eeec0 Implement interprocess event handling for notifications and account disable
Changes:
- Implements #2061
- Introduce EventManager for user-specific Windows event publishing/listening
- Add real-time notification and account-disabled event propagation
- Register authentication events through Controller and AuthenticationManagementService
- Trigger notification events from Inventory, Payment, and Service Management modules
- Trigger account-disabled events when users are deactivated
- Extract common menu event listener logic into Menu base class
- Add notification popup handling for Admin, Customer, and Technician menus
- Refactor shared memory components into core/sharedmemory
- Update project structure and include paths for events and shared memory modules
2026-06-16 11:10:13 +05:30
31 changed files with 1072 additions and 124 deletions
@@ -102,7 +102,7 @@
<SDLCheck>true</SDLCheck> <SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> <PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode> <ConformanceMode>true</ConformanceMode>
<AdditionalIncludeDirectories>$(ProjectDir)models;$(ProjectDir)controllers;$(ProjectDir)factories;$(ProjectDir)views;$(ProjectDir)services;$(ProjectDir)utilities;$(ProjectDir)core\patterns;$(ProjectDir)datastores;$(ProjectDir)datastores\sharedmemory;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> <AdditionalIncludeDirectories>$(ProjectDir)models;$(ProjectDir)controllers;$(ProjectDir)factories;$(ProjectDir)views;$(ProjectDir)services;$(ProjectDir)utilities;$(ProjectDir)core\patterns;$(ProjectDir)datastores;$(ProjectDir)core\sharedmemory;$(ProjectDir)core\events;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile> </ClCompile>
<Link> <Link>
<SubSystem>Console</SubSystem> <SubSystem>Console</SubSystem>
@@ -117,7 +117,7 @@
<SDLCheck>true</SDLCheck> <SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> <PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode> <ConformanceMode>true</ConformanceMode>
<AdditionalIncludeDirectories>$(ProjectDir)models;$(ProjectDir)controllers;$(ProjectDir)factories;$(ProjectDir)views;$(ProjectDir)services;$(ProjectDir)utilities;$(ProjectDir)core\patterns;$(ProjectDir)datastores;$(ProjectDir)datastores\sharedmemory;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> <AdditionalIncludeDirectories>$(ProjectDir)models;$(ProjectDir)controllers;$(ProjectDir)factories;$(ProjectDir)views;$(ProjectDir)services;$(ProjectDir)utilities;$(ProjectDir)core\patterns;$(ProjectDir)datastores;$(ProjectDir)core\sharedmemory;$(ProjectDir)core\events;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile> </ClCompile>
<Link> <Link>
<SubSystem>Console</SubSystem> <SubSystem>Console</SubSystem>
@@ -126,10 +126,11 @@
</ItemDefinitionGroup> </ItemDefinitionGroup>
<ItemGroup> <ItemGroup>
<ClCompile Include="controllers\Controller.cpp" /> <ClCompile Include="controllers\Controller.cpp" />
<ClCompile Include="core\events\EventManager.cpp" />
<ClCompile Include="core\patterns\Observer.cpp" /> <ClCompile Include="core\patterns\Observer.cpp" />
<ClCompile Include="core\patterns\Subject.cpp" /> <ClCompile Include="core\patterns\Subject.cpp" />
<ClCompile Include="core\sharedmemory\SharedMemory.cpp" />
<ClCompile Include="datastores\DataStore.cpp" /> <ClCompile Include="datastores\DataStore.cpp" />
<ClCompile Include="datastores\sharedmemory\SharedMemory.cpp" />
<ClCompile Include="models\ComboPackage.cpp" /> <ClCompile Include="models\ComboPackage.cpp" />
<ClCompile Include="models\InventoryItem.cpp" /> <ClCompile Include="models\InventoryItem.cpp" />
<ClCompile Include="models\Invoice.cpp" /> <ClCompile Include="models\Invoice.cpp" />
@@ -148,21 +149,23 @@
<ClCompile Include="utilities\Validator.cpp" /> <ClCompile Include="utilities\Validator.cpp" />
<ClCompile Include="views\AdminMenu.cpp" /> <ClCompile Include="views\AdminMenu.cpp" />
<ClCompile Include="views\CustomerMenu.cpp" /> <ClCompile Include="views\CustomerMenu.cpp" />
<ClCompile Include="views\Menu.cpp" />
<ClCompile Include="views\TechnicianMenu.cpp" /> <ClCompile Include="views\TechnicianMenu.cpp" />
<ClCompile Include="views\UserInterface.cpp" /> <ClCompile Include="views\UserInterface.cpp" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClInclude Include="controllers\Controller.h" /> <ClInclude Include="controllers\Controller.h" />
<ClInclude Include="core\events\EventManager.h" />
<ClInclude Include="core\patterns\Observer.h" /> <ClInclude Include="core\patterns\Observer.h" />
<ClInclude Include="core\patterns\Subject.h" /> <ClInclude Include="core\patterns\Subject.h" />
<ClInclude Include="core\sharedmemory\FileHeader.h" />
<ClInclude Include="core\sharedmemory\MappingInfo.h" />
<ClInclude Include="core\sharedmemory\RecordState.h" />
<ClInclude Include="core\sharedmemory\SerializedRecords.h" />
<ClInclude Include="core\sharedmemory\SharedMemory.h" />
<ClInclude Include="core\sharedmemory\TrackedRecord.h" />
<ClInclude Include="datastores\DataStore.h" /> <ClInclude Include="datastores\DataStore.h" />
<ClInclude Include="datastores\DataStoreLockGuard.h" /> <ClInclude Include="datastores\DataStoreLockGuard.h" />
<ClInclude Include="datastores\sharedmemory\FileHeader.h" />
<ClInclude Include="datastores\sharedmemory\MappingInfo.h" />
<ClInclude Include="datastores\sharedmemory\RecordState.h" />
<ClInclude Include="datastores\sharedmemory\SerializedRecords.h" />
<ClInclude Include="datastores\sharedmemory\SharedMemory.h" />
<ClInclude Include="datastores\sharedmemory\TrackedRecord.h" />
<ClInclude Include="factories\Factory.h" /> <ClInclude Include="factories\Factory.h" />
<ClInclude Include="models\ComboPackage.h" /> <ClInclude Include="models\ComboPackage.h" />
<ClInclude Include="models\InventoryItem.h" /> <ClInclude Include="models\InventoryItem.h" />
@@ -191,6 +194,7 @@
<ClInclude Include="utilities\Vector.h" /> <ClInclude Include="utilities\Vector.h" />
<ClInclude Include="views\AdminMenu.h" /> <ClInclude Include="views\AdminMenu.h" />
<ClInclude Include="views\CustomerMenu.h" /> <ClInclude Include="views\CustomerMenu.h" />
<ClInclude Include="views\Menu.h" />
<ClInclude Include="views\MenuHelper.h" /> <ClInclude Include="views\MenuHelper.h" />
<ClInclude Include="views\TechnicianMenu.h" /> <ClInclude Include="views\TechnicianMenu.h" />
<ClInclude Include="views\UserInterface.h" /> <ClInclude Include="views\UserInterface.h" />
@@ -64,11 +64,17 @@
<Filter Include="Source Files\Core\Patterns"> <Filter Include="Source Files\Core\Patterns">
<UniqueIdentifier>{8057b93d-51a9-42df-b06e-01ce395f6308}</UniqueIdentifier> <UniqueIdentifier>{8057b93d-51a9-42df-b06e-01ce395f6308}</UniqueIdentifier>
</Filter> </Filter>
<Filter Include="Header Files\DataStores\SharedMemory"> <Filter Include="Header Files\Core\SharedMemory">
<UniqueIdentifier>{ec639004-44c6-4bd6-9963-077adde82b5f}</UniqueIdentifier> <UniqueIdentifier>{d9da9793-fe6f-4914-bee3-99d5934da228}</UniqueIdentifier>
</Filter> </Filter>
<Filter Include="Source Files\DataStores\SharedMemory"> <Filter Include="Source Files\Core\SharedMemory">
<UniqueIdentifier>{7aa8722e-adfa-466e-8211-de63f3b7892b}</UniqueIdentifier> <UniqueIdentifier>{0769afb6-f57d-4ae3-a1cf-ceca6e606af0}</UniqueIdentifier>
</Filter>
<Filter Include="Header Files\Core\Events">
<UniqueIdentifier>{85029bdb-6941-41dc-a3a7-9e5841671d8c}</UniqueIdentifier>
</Filter>
<Filter Include="Source Files\Core\Events">
<UniqueIdentifier>{1050aca7-6f2c-4ccb-a446-db9c898c3599}</UniqueIdentifier>
</Filter> </Filter>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -144,8 +150,14 @@
<ClCompile Include="models\ComboPackage.cpp"> <ClCompile Include="models\ComboPackage.cpp">
<Filter>Source Files\Models</Filter> <Filter>Source Files\Models</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="datastores\sharedmemory\SharedMemory.cpp"> <ClCompile Include="core\sharedmemory\SharedMemory.cpp">
<Filter>Source Files\DataStores\SharedMemory</Filter> <Filter>Source Files\Core\SharedMemory</Filter>
</ClCompile>
<ClCompile Include="core\events\EventManager.cpp">
<Filter>Source Files\Core\Events</Filter>
</ClCompile>
<ClCompile Include="views\Menu.cpp">
<Filter>Source Files\Views</Filter>
</ClCompile> </ClCompile>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -254,26 +266,32 @@
<ClInclude Include="views\MenuHelper.h"> <ClInclude Include="views\MenuHelper.h">
<Filter>Header Files\Views</Filter> <Filter>Header Files\Views</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="datastores\sharedmemory\FileHeader.h">
<Filter>Header Files\DataStores\SharedMemory</Filter>
</ClInclude>
<ClInclude Include="datastores\sharedmemory\MappingInfo.h">
<Filter>Header Files\DataStores\SharedMemory</Filter>
</ClInclude>
<ClInclude Include="datastores\sharedmemory\RecordState.h">
<Filter>Header Files\DataStores\SharedMemory</Filter>
</ClInclude>
<ClInclude Include="datastores\sharedmemory\TrackedRecord.h">
<Filter>Header Files\DataStores\SharedMemory</Filter>
</ClInclude>
<ClInclude Include="datastores\sharedmemory\SerializedRecords.h">
<Filter>Header Files\DataStores\SharedMemory</Filter>
</ClInclude>
<ClInclude Include="datastores\sharedmemory\SharedMemory.h">
<Filter>Header Files\DataStores\SharedMemory</Filter>
</ClInclude>
<ClInclude Include="datastores\DataStoreLockGuard.h"> <ClInclude Include="datastores\DataStoreLockGuard.h">
<Filter>Header Files\DataStores</Filter> <Filter>Header Files\DataStores</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="core\sharedmemory\FileHeader.h">
<Filter>Header Files\Core\SharedMemory</Filter>
</ClInclude>
<ClInclude Include="core\sharedmemory\MappingInfo.h">
<Filter>Header Files\Core\SharedMemory</Filter>
</ClInclude>
<ClInclude Include="core\sharedmemory\RecordState.h">
<Filter>Header Files\Core\SharedMemory</Filter>
</ClInclude>
<ClInclude Include="core\sharedmemory\SerializedRecords.h">
<Filter>Header Files\Core\SharedMemory</Filter>
</ClInclude>
<ClInclude Include="core\sharedmemory\SharedMemory.h">
<Filter>Header Files\Core\SharedMemory</Filter>
</ClInclude>
<ClInclude Include="core\sharedmemory\TrackedRecord.h">
<Filter>Header Files\Core\SharedMemory</Filter>
</ClInclude>
<ClInclude Include="core\events\EventManager.h">
<Filter>Header Files\Core\Events</Filter>
</ClInclude>
<ClInclude Include="views\Menu.h">
<Filter>Header Files\Views</Filter>
</ClInclude>
</ItemGroup> </ItemGroup>
</Project> </Project>
@@ -234,6 +234,17 @@ void Controller::removeInventoryItem(const std::string& inventoryItemID)
m_inventoryManagementService.removeInventoryItem(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 Function: addInventoryItemStock
Description: Adds stock to an existing inventory item in the inventory management service. 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; return readOnlyServiceBookings;
} }
/* /*
Function: getServiceBookingsByUser Function: getServiceBookingsByUser
Description: Retrieves all service bookings for a specific user. Description: Retrieves all service bookings for a specific user.
@@ -622,3 +634,16 @@ void Controller::shutdown()
auto& dataStore = DataStore::getInstance(); auto& dataStore = DataStore::getInstance();
dataStore.shutdown(); dataStore.shutdown();
} }
/*
Function: registerEvents
Description: Registers menu event handles with the authentication
service.
Parameter: HANDLE accountDisabledEvent - account disabled event handle
HANDLE notificationAvailableEvent - notification event handle
Return type: void
*/
void Controller::registerEvents(HANDLE accountDisabledEvent, HANDLE notificationAvailableEvent)
{
m_authenticationManagementService.registerEvents(accountDisabledEvent, notificationAvailableEvent);
}
@@ -8,6 +8,7 @@ Date:19-May-2026
*/ */
#pragma once #pragma once
#include <windows.h>
#include <string> #include <string>
#include "AuthenticationManagementService.h" #include "AuthenticationManagementService.h"
#include "Enums.h" #include "Enums.h"
@@ -57,6 +58,7 @@ public:
util::Map<std::string, const User*> getUsers(util::UserType userType); 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 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 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); void removeService(const std::string& serviceID);
util::Map<std::string, const JobCard*> getJobCardsByUser(); util::Map<std::string, const JobCard*> getJobCardsByUser();
void updateJobStatus(const std::string& jobID); void updateJobStatus(const std::string& jobID);
@@ -72,4 +74,5 @@ public:
void configureNotifications(bool paymentNotifications, bool serviceNotifications); void configureNotifications(bool paymentNotifications, bool serviceNotifications);
bool initialize(); bool initialize();
void shutdown(); void shutdown();
void registerEvents(HANDLE accountDisabledEvent, HANDLE notificationAvailableEvent);
}; };
@@ -0,0 +1,225 @@
/*
File: EventManager.cpp
Description: Implementation file containing the method definitions of the
EventManager class, including listener management and
interprocess event publishing.
Author: Trenser
Date:15-Jun-2026
*/
#include <iostream>
#include <stdexcept>
#include "EventManager.h"
namespace
{
const std::string USER_DISABLED_EVENT = "userDisabled_";
const std::string NOTIFICATION_AVAILABLE_EVENT = "notificationAvailable_";
}
/*
Function: EventManager
Description: Constructs an EventManager instance with default values.
Parameter: None
Return type: None
*/
EventManager::EventManager()
:
m_userDisabledEvent(NULL),
m_notificationAvailableEvent(NULL),
m_shutdownEvent(NULL),
m_running(false) {}
/*
Function: ~EventManager
Description: Destroys the EventManager and performs final cleanup.
Parameter: None
Return type: None
*/
EventManager::~EventManager()
{
shutdown();
if (m_listenerThread.joinable())
{
m_listenerThread.join();
}
}
/*
Function: initialize
Description: Creates the user-specific events and starts the listener
thread.
Parameter: const std::string& userId - unique identifier of the user
std::function<void()> userDisabledCallback - callback for
user disable events
std::function<void()> notificationCallback - callback for
notification events
Return type: bool - true if initialization succeeds, false otherwise
*/
bool EventManager::initialize(const std::string& userId, std::function<void()> userDisabledCallback, std::function<void()> notificationCallback)
{
if (m_running.load())
{
return false;
}
m_userDisabledCallback = userDisabledCallback;
m_notificationCallback = notificationCallback;
m_userDisabledEvent = CreateEventA(NULL, FALSE, FALSE, (USER_DISABLED_EVENT + userId).c_str());
if (!m_userDisabledEvent)
{
return false;
}
if (GetLastError() == ERROR_ALREADY_EXISTS)
{
CloseHandle(m_userDisabledEvent);
m_userDisabledEvent = NULL;
throw std::runtime_error("Only one session allowed per user.");
}
m_notificationAvailableEvent = CreateEventA(NULL, FALSE, FALSE, (NOTIFICATION_AVAILABLE_EVENT + userId).c_str());
if (!m_notificationAvailableEvent)
{
CloseHandle(m_userDisabledEvent);
m_userDisabledEvent = NULL;
return false;
}
m_shutdownEvent = CreateEventA(NULL, FALSE, FALSE, NULL);
if (!m_shutdownEvent)
{
CloseHandle(m_userDisabledEvent);
CloseHandle(m_notificationAvailableEvent);
m_userDisabledEvent = NULL;
m_notificationAvailableEvent = NULL;
return false;
}
m_running.store(true);
m_listenerThread = std::thread(&EventManager::run, this);
return true;
}
/*
Function: shutdown
Description: Stops the listener thread and releases event resources.
Parameter: None
Return type: None
*/
void EventManager::shutdown()
{
if (!m_running.load())
{
return;
}
m_running.store(false);
if (m_shutdownEvent)
{
SetEvent(m_shutdownEvent);
}
if (m_listenerThread.joinable())
{
if (std::this_thread::get_id() != m_listenerThread.get_id())
{
m_listenerThread.join();
}
}
if (m_userDisabledEvent)
{
CloseHandle(m_userDisabledEvent);
m_userDisabledEvent = NULL;
}
if (m_notificationAvailableEvent)
{
CloseHandle(m_notificationAvailableEvent);
m_notificationAvailableEvent = NULL;
}
if (m_shutdownEvent)
{
CloseHandle(m_shutdownEvent);
m_shutdownEvent = NULL;
}
}
/*
Function: run
Description: Waits for and dispatches user-related events.
Parameter: None
Return type: void
*/
void EventManager::run()
{
HANDLE handles[3];
handles[0] = m_userDisabledEvent;
handles[1] = m_notificationAvailableEvent;
handles[2] = m_shutdownEvent;
while (m_running.load())
{
DWORD result = WaitForMultipleObjects(3, handles, FALSE, INFINITE);
switch (result)
{
case WAIT_OBJECT_0:
try
{
if (m_userDisabledCallback)
{
m_userDisabledCallback();
}
}
catch (const std::exception& exception)
{
std::cout << exception.what() << std::endl;
}
break;
case WAIT_OBJECT_0 + 1:
try
{
if (m_notificationCallback)
{
m_notificationCallback();
}
}
catch (const std::exception& exception)
{
std::cout << exception.what() << std::endl;
}
break;
case WAIT_OBJECT_0 + 2:
return;
default:
break;
}
}
}
/*
Function: sendUserDisabledEvent
Description: Publishes a user disabled event for a specific user.
Parameter: const std::string& userId - target user identifier
Return type: void
*/
void EventManager::sendUserDisabledEvent(const std::string& userId)
{
HANDLE eventHandle = CreateEventA(NULL, FALSE, FALSE, (USER_DISABLED_EVENT + userId).c_str());
if (!eventHandle)
{
return;
}
SetEvent(eventHandle);
CloseHandle(eventHandle);
}
/*
Function: sendNotificationAvailableEvent
Description: Publishes a notification available event for a specific
user.
Parameter: const std::string& userId - target user identifier
Return type: void
*/
void EventManager::sendNotificationAvailableEvent(const std::string& userId)
{
HANDLE eventHandle = CreateEventA(NULL, FALSE, FALSE, (NOTIFICATION_AVAILABLE_EVENT + userId).c_str());
if (!eventHandle)
{
return;
}
SetEvent(eventHandle);
CloseHandle(eventHandle);
}
@@ -0,0 +1,37 @@
/*
File: EventManager.h
Description: Header file declaring the EventManager class, which manages
user-specific interprocess events for user disable and
notification availability updates.
Author: Trenser
Date:15-Jun-2026
*/
#pragma once
#include <windows.h>
#include <atomic>
#include <functional>
#include <string>
#include <thread>
class EventManager
{
private:
HANDLE m_userDisabledEvent;
HANDLE m_notificationAvailableEvent;
HANDLE m_shutdownEvent;
std::atomic<bool> m_running;
std::thread m_listenerThread;
std::function<void()> m_userDisabledCallback;
std::function<void()> m_notificationCallback;
void run();
public:
EventManager();
~EventManager();
bool initialize(const std::string& userId, std::function<void()> userDisabledCallback, std::function<void()> notificationCallback);
void shutdown();
static void sendUserDisabledEvent(const std::string& userId);
static void sendNotificationAvailableEvent(const std::string& userId);
};
@@ -415,7 +415,7 @@ util::Map<std::string, TrackedRecord<JobCard>>& DataStore::getJobCards()
{ {
throw std::runtime_error("Invalid booking ID: " + bookingId); throw std::runtime_error("Invalid booking ID: " + bookingId);
} }
auto trackedBooking = serviceBookings.getValueAt(bookingIndex); auto& trackedBooking = serviceBookings.getValueAt(bookingIndex);
jobCard->setBooking(trackedBooking.data); jobCard->setBooking(trackedBooking.data);
const std::string& serviceId = jobCard->getServiceId(); const std::string& serviceId = jobCard->getServiceId();
int serviceIndex = services.find(serviceId); int serviceIndex = services.find(serviceId);
@@ -220,6 +220,12 @@ void DataStore::refreshCache(util::Map<std::string, TrackedRecord<TObject>>& cac
if (oldIndex != -1) if (oldIndex != -1)
{ {
TrackedRecord<TObject>& oldRecord = oldCache.getValueAt(oldIndex); TrackedRecord<TObject>& oldRecord = oldCache.getValueAt(oldIndex);
if (oldRecord.state == RecordState::MODIFIED)
{
delete refreshedRecord.data;
cache.insert(id, oldRecord);
continue;
}
*oldRecord.data = *refreshedRecord.data; *oldRecord.data = *refreshedRecord.data;
oldRecord.slotIndex = refreshedRecord.slotIndex; oldRecord.slotIndex = refreshedRecord.slotIndex;
oldRecord.state = refreshedRecord.state; oldRecord.state = refreshedRecord.state;
@@ -14,6 +14,28 @@ Date:19-May-2026
#include "DataStoreLockGuard.h" #include "DataStoreLockGuard.h"
User* AuthenticationManagementService::m_authenticatedUser = nullptr; User* AuthenticationManagementService::m_authenticatedUser = nullptr;
bool AuthenticationManagementService::m_isAuthorized = false;
EventManager AuthenticationManagementService::m_eventManager;
HANDLE AuthenticationManagementService::m_accountDisabledEvent = NULL;
HANDLE AuthenticationManagementService::m_notificationsAvailableEvent = NULL;
/*
Function: ensureAuthorization
Description: Verifies that a user is currently authenticated before allowing
access to a protected operation. Throws an exception if no
authorized user session exists.
Parameter: None
Return type: void
Throws: std::runtime_error - if the user is not authorized
*/
void AuthenticationManagementService::ensureAuthorization()
{
if (!m_authenticatedUser || !m_isAuthorized)
{
throw std::runtime_error("You are not authorized to do this operation!");
}
}
/* /*
Function: login Function: login
@@ -37,6 +59,24 @@ bool AuthenticationManagementService::login(const std::string& username, const s
if (password == user->getPassword()) if (password == user->getPassword())
{ {
m_authenticatedUser = user; m_authenticatedUser = user;
m_isAuthorized = true;
m_eventManager.initialize(
user->getId(),
[]()
{
if (m_accountDisabledEvent)
{
AuthenticationManagementService::m_isAuthorized = false;
SetEvent(m_accountDisabledEvent);
}
},
[]()
{
if (m_notificationsAvailableEvent)
{
SetEvent(m_notificationsAvailableEvent);
}
});
return true; return true;
} }
return false; return false;
@@ -65,7 +105,11 @@ Return type: void
*/ */
void AuthenticationManagementService::logout() void AuthenticationManagementService::logout()
{ {
m_eventManager.shutdown();
m_authenticatedUser = nullptr; m_authenticatedUser = nullptr;
m_isAuthorized = false;
m_accountDisabledEvent = NULL;
m_notificationsAvailableEvent = NULL;
} }
/* /*
@@ -77,6 +121,7 @@ Return type: void
*/ */
void AuthenticationManagementService::changePassword(const std::string& newPassword) void AuthenticationManagementService::changePassword(const std::string& newPassword)
{ {
AuthenticationManagementService::ensureAuthorization();
DataStoreLockGuard lock(m_dataStore); DataStoreLockGuard lock(m_dataStore);
auto& trackedUsersMap = m_dataStore.getUsers(); auto& trackedUsersMap = m_dataStore.getUsers();
if (m_authenticatedUser == nullptr) if (m_authenticatedUser == nullptr)
@@ -92,3 +137,17 @@ void AuthenticationManagementService::changePassword(const std::string& newPassw
trackedUsersMap.getValueAt(index).state = RecordState::MODIFIED; trackedUsersMap.getValueAt(index).state = RecordState::MODIFIED;
m_dataStore.saveUsers(); m_dataStore.saveUsers();
} }
/*
Function: registerEvents
Description: Registers menu event handles used to notify the active
menu of account disable and notification events.
Parameter: HANDLE accountDisabledEvent - account disabled event handle
HANDLE notificationAvailableEvent - notification event handle
Return type: void
*/
void AuthenticationManagementService::registerEvents(HANDLE accountDisabledEvent, HANDLE notificationAvailableEvent)
{
m_accountDisabledEvent = accountDisabledEvent;
m_notificationsAvailableEvent = notificationAvailableEvent;
}
@@ -9,6 +9,8 @@ Date:19-May-2026
#pragma once #pragma once
#include <string> #include <string>
#include <windows.h>
#include "EventManager.h"
#include "DataStore.h" #include "DataStore.h"
class User; class User;
@@ -17,11 +19,17 @@ class AuthenticationManagementService
{ {
private: private:
static User* m_authenticatedUser; static User* m_authenticatedUser;
static bool m_isAuthorized;
static EventManager m_eventManager;
static HANDLE m_accountDisabledEvent;
static HANDLE m_notificationsAvailableEvent;
DataStore& m_dataStore; DataStore& m_dataStore;
public: public:
AuthenticationManagementService() : m_dataStore(DataStore::getInstance()) {} AuthenticationManagementService() : m_dataStore(DataStore::getInstance()) {}
static void ensureAuthorization();
bool login(const std::string& username, const std::string& password); bool login(const std::string& username, const std::string& password);
void logout(); void logout();
void changePassword(const std::string& newPassword); void changePassword(const std::string& newPassword);
User* getAuthenticatedUser(); User* getAuthenticatedUser();
void registerEvents(HANDLE accountDisabledEvent, HANDLE notificationAvailableEvent);
}; };
@@ -14,11 +14,13 @@ Date: 22-May-2026
#include "Factory.h" #include "Factory.h"
#include "InventoryItem.h" #include "InventoryItem.h"
#include "InventoryManagementService.h" #include "InventoryManagementService.h"
#include "AuthenticationManagementService.h"
#include "Timestamp.h" #include "Timestamp.h"
#include "User.h" #include "User.h"
#include "Utility.h" #include "Utility.h"
#include "Vector.h" #include "Vector.h"
#include "DataStoreLockGuard.h" #include "DataStoreLockGuard.h"
#include "EventManager.h"
util::Map<std::string, User*> InventoryManagementService::m_observers{}; util::Map<std::string, User*> InventoryManagementService::m_observers{};
@@ -101,6 +103,7 @@ Return type: void
*/ */
void InventoryManagementService::addInventoryItem(const std::string& partName, int quantity, double price) void InventoryManagementService::addInventoryItem(const std::string& partName, int quantity, double price)
{ {
AuthenticationManagementService::ensureAuthorization();
DataStoreLockGuard lock(m_dataStore); DataStoreLockGuard lock(m_dataStore);
auto& trackedInventoryItemMap = m_dataStore.getInventoryItems(); auto& trackedInventoryItemMap = m_dataStore.getInventoryItems();
InventoryItem* newItem = Factory::getObject<InventoryItem>(partName, quantity, price); InventoryItem* newItem = Factory::getObject<InventoryItem>(partName, quantity, price);
@@ -117,6 +120,7 @@ Return type: void
*/ */
void InventoryManagementService::addInventoryItemStock(const std::string& selectedItemId, int quantity) void InventoryManagementService::addInventoryItemStock(const std::string& selectedItemId, int quantity)
{ {
AuthenticationManagementService::ensureAuthorization();
DataStoreLockGuard lock(m_dataStore); DataStoreLockGuard lock(m_dataStore);
auto& trackedInventoryItemMap = m_dataStore.getInventoryItems(); auto& trackedInventoryItemMap = m_dataStore.getInventoryItems();
int index = trackedInventoryItemMap.find(selectedItemId); int index = trackedInventoryItemMap.find(selectedItemId);
@@ -143,6 +147,7 @@ Return type: util::Map<std::string, InventoryItem*>
*/ */
util::Map<std::string, InventoryItem*> InventoryManagementService::getInventoryItems() util::Map<std::string, InventoryItem*> InventoryManagementService::getInventoryItems()
{ {
AuthenticationManagementService::ensureAuthorization();
DataStoreLockGuard lock(m_dataStore); DataStoreLockGuard lock(m_dataStore);
auto& trackedInventoryItemMap = m_dataStore.getInventoryItems(); auto& trackedInventoryItemMap = m_dataStore.getInventoryItems();
auto inventoryMap = util::getObjects(trackedInventoryItemMap); auto inventoryMap = util::getObjects(trackedInventoryItemMap);
@@ -157,6 +162,7 @@ Return type: void
*/ */
void InventoryManagementService::removeInventoryItem(const std::string& inventoryItemID) void InventoryManagementService::removeInventoryItem(const std::string& inventoryItemID)
{ {
AuthenticationManagementService::ensureAuthorization();
DataStoreLockGuard lock(m_dataStore); DataStoreLockGuard lock(m_dataStore);
auto& trackedInventoryItemMap = m_dataStore.getInventoryItems(); auto& trackedInventoryItemMap = m_dataStore.getInventoryItems();
int index = trackedInventoryItemMap.find(inventoryItemID); int index = trackedInventoryItemMap.find(inventoryItemID);
@@ -182,6 +188,7 @@ Return type: InventoryItem*
*/ */
InventoryItem* InventoryManagementService::getInventoryItem(const std::string& inventoryItemID) InventoryItem* InventoryManagementService::getInventoryItem(const std::string& inventoryItemID)
{ {
AuthenticationManagementService::ensureAuthorization();
DataStoreLockGuard lock(m_dataStore); DataStoreLockGuard lock(m_dataStore);
auto& trackedInventoryItemMap = m_dataStore.getInventoryItems(); auto& trackedInventoryItemMap = m_dataStore.getInventoryItems();
int index = trackedInventoryItemMap.find(inventoryItemID); int index = trackedInventoryItemMap.find(inventoryItemID);
@@ -269,6 +276,7 @@ void InventoryManagementService::sendNotification(User* user, const std::string&
{ {
return; return;
} }
auto& trackedNotificationsMap = m_dataStore.getNotifications();
Notification* notification = Factory::getObject<Notification>( Notification* notification = Factory::getObject<Notification>(
user->getId(), user->getId(),
title, title,
@@ -278,8 +286,8 @@ void InventoryManagementService::sendNotification(User* user, const std::string&
{ {
throw std::runtime_error("Failed to create notification"); throw std::runtime_error("Failed to create notification");
} }
auto& trackedNotificationsMap = m_dataStore.getNotifications();
trackedNotificationsMap.insert(notification->getId(), util::createNewRecord(notification)); trackedNotificationsMap.insert(notification->getId(), util::createNewRecord(notification));
m_dataStore.saveNotifications(); m_dataStore.saveNotifications();
EventManager::sendNotificationAvailableEvent(user->getId());
} }
@@ -15,6 +15,7 @@ Date: 20-May-2026
#include "Invoice.h" #include "Invoice.h"
#include "JobCard.h" #include "JobCard.h"
#include "PaymentManagementService.h" #include "PaymentManagementService.h"
#include "AuthenticationManagementService.h"
#include "DataStoreLockGuard.h" #include "DataStoreLockGuard.h"
#include "Service.h" #include "Service.h"
#include "ServiceBooking.h" #include "ServiceBooking.h"
@@ -22,6 +23,7 @@ Date: 20-May-2026
#include "User.h" #include "User.h"
#include "Utility.h" #include "Utility.h"
#include "DataStoreLockGuard.h" #include "DataStoreLockGuard.h"
#include "EventManager.h"
util::Map<std::string, User*> PaymentManagementService::m_observers{}; util::Map<std::string, User*> PaymentManagementService::m_observers{};
@@ -97,6 +99,7 @@ void PaymentManagementService::sendNotification(User* user, const std::string& t
{ {
return; return;
} }
auto& trackedNotificationsMap = m_dataStore.getNotifications();
Notification* notification = Factory::getObject<Notification>( Notification* notification = Factory::getObject<Notification>(
user->getId(), user->getId(),
title, title,
@@ -106,9 +109,9 @@ void PaymentManagementService::sendNotification(User* user, const std::string& t
{ {
throw std::runtime_error("Failed to create notification"); throw std::runtime_error("Failed to create notification");
} }
auto& trackedNotificationsMap = m_dataStore.getNotifications();
trackedNotificationsMap.insert(notification->getId(), util::createNewRecord(notification)); trackedNotificationsMap.insert(notification->getId(), util::createNewRecord(notification));
m_dataStore.saveNotifications(); m_dataStore.saveNotifications();
EventManager::sendNotificationAvailableEvent(user->getId());
} }
/* /*
@@ -231,6 +234,7 @@ Returns:
*/ */
util::Map<std::string, Invoice*> PaymentManagementService::getInvoices(const std::string& customerID) util::Map<std::string, Invoice*> PaymentManagementService::getInvoices(const std::string& customerID)
{ {
AuthenticationManagementService::ensureAuthorization();
DataStoreLockGuard lock(m_dataStore); DataStoreLockGuard lock(m_dataStore);
auto& currentTrackedInvoices = m_dataStore.getInvoices(); auto& currentTrackedInvoices = m_dataStore.getInvoices();
util::Map<std::string, Invoice*> currentUserInvoices; util::Map<std::string, Invoice*> currentUserInvoices;
@@ -259,6 +263,7 @@ Throws:
*/ */
void PaymentManagementService::completePayment(const std::string& invoiceID, util::PaymentMode paymentMode) void PaymentManagementService::completePayment(const std::string& invoiceID, util::PaymentMode paymentMode)
{ {
AuthenticationManagementService::ensureAuthorization();
DataStoreLockGuard lock(m_dataStore); DataStoreLockGuard lock(m_dataStore);
auto& currentTrackedInvoices = m_dataStore.getInvoices(); auto& currentTrackedInvoices = m_dataStore.getInvoices();
int invoiceIndex = currentTrackedInvoices.find(invoiceID); int invoiceIndex = currentTrackedInvoices.find(invoiceID);
@@ -287,7 +292,7 @@ void PaymentManagementService::completePayment(const std::string& invoiceID, uti
} }
/* /*
Function: getAllInvoice Function: getAllInvoices
Description: Provides access to all invoices stored in the data store. Description: Provides access to all invoices stored in the data store.
Parameters: Parameters:
- none - none
@@ -296,6 +301,7 @@ Returns:
*/ */
util::Map<std::string, Invoice*> PaymentManagementService::getAllInvoices() util::Map<std::string, Invoice*> PaymentManagementService::getAllInvoices()
{ {
AuthenticationManagementService::ensureAuthorization();
DataStoreLockGuard lock(m_dataStore); DataStoreLockGuard lock(m_dataStore);
util::Map<std::string, Invoice*> invoices; util::Map<std::string, Invoice*> invoices;
invoices = util::getObjects(m_dataStore.getInvoices()); invoices = util::getObjects(m_dataStore.getInvoices());
@@ -315,6 +321,7 @@ Throws:
*/ */
void PaymentManagementService::confirmPayment(const std::string& invoiceID) void PaymentManagementService::confirmPayment(const std::string& invoiceID)
{ {
AuthenticationManagementService::ensureAuthorization();
DataStoreLockGuard lock(m_dataStore); DataStoreLockGuard lock(m_dataStore);
auto& currentTrackedInvoices = m_dataStore.getInvoices(); auto& currentTrackedInvoices = m_dataStore.getInvoices();
int invoiceIndex = currentTrackedInvoices.find(invoiceID); int invoiceIndex = currentTrackedInvoices.find(invoiceID);
@@ -26,7 +26,32 @@ Date:19-May-2026
#include "UserManagementService.h" #include "UserManagementService.h"
#include "DataStoreLockGuard.h" #include "DataStoreLockGuard.h"
#include "Utility.h" #include "Utility.h"
#include "DataStoreLockGuard.h" #include "EventManager.h"
/*
Function: notifyAllAdmins
Description: Sends a notification to all users with the ADMIN role.
Iterates through the provided user collection and delivers
the specified notification to each administrator using the
ServiceManagementService notification mechanism.
Parameter: const std::string& title - notification title
Parameter: const std::string& message - notification message
Parameter: const util::Map<std::string, TrackedRecord<User>>& users - collection of tracked users
Parameter: ServiceManagementService* serviceManagementService - service used to dispatch notifications
Return type: void
*/
static void notifyAllAdmins(const std::string& title, const std::string& message, const util::Map<std::string, TrackedRecord<User>>& users, ServiceManagementService* serviceManagementService)
{
int numberOfUsers = users.getSize();
for (int index = 0; index < numberOfUsers; index++)
{
User* user = users.getValueAt(index).data;
if (user->getUserType() == util::UserType::ADMIN)
{
serviceManagementService->sendNotification(user, title, message);
}
}
}
/* /*
Function: purchaseService Function: purchaseService
@@ -41,6 +66,7 @@ Return type: void
*/ */
void ServiceManagementService::purchaseService(const util::Vector<std::string>& serviceIDs, const std::string& vehicleNumber, const std::string& vehicleBrand, const std::string& vehicleModel) void ServiceManagementService::purchaseService(const util::Vector<std::string>& serviceIDs, const std::string& vehicleNumber, const std::string& vehicleBrand, const std::string& vehicleModel)
{ {
AuthenticationManagementService::ensureAuthorization();
AuthenticationManagementService m_authenticationManagementService; AuthenticationManagementService m_authenticationManagementService;
auto authenticatedUser = m_authenticationManagementService.getAuthenticatedUser(); auto authenticatedUser = m_authenticationManagementService.getAuthenticatedUser();
if (authenticatedUser == nullptr) if (authenticatedUser == nullptr)
@@ -71,6 +97,8 @@ void ServiceManagementService::purchaseService(const util::Vector<std::string>&
std::string title = "Service Booking succeeded"; std::string title = "Service Booking succeeded";
std::string message = "Your service booking has been successfully placed with ID " + serviceBooking->getId(); std::string message = "Your service booking has been successfully placed with ID " + serviceBooking->getId();
sendNotification(authenticatedUser, title, message); sendNotification(authenticatedUser, title, message);
m_dataStore.saveServiceBookings();
notifyAllAdmins("New Service Order Available", "A new service order has been placed with Service Booking ID " + serviceBooking->getId(), m_dataStore.getUsers(), this);
} }
/* /*
@@ -86,6 +114,7 @@ Return type: void
*/ */
void ServiceManagementService::purchaseComboPackage(const std::string& comboPackageID, const std::string& vehicleNumber, const std::string& vehicleBrand, const std::string& vehicleModel) void ServiceManagementService::purchaseComboPackage(const std::string& comboPackageID, const std::string& vehicleNumber, const std::string& vehicleBrand, const std::string& vehicleModel)
{ {
AuthenticationManagementService::ensureAuthorization();
AuthenticationManagementService m_authenticationManagementService; AuthenticationManagementService m_authenticationManagementService;
auto authenticatedUser = m_authenticationManagementService.getAuthenticatedUser(); auto authenticatedUser = m_authenticationManagementService.getAuthenticatedUser();
if (authenticatedUser == nullptr) if (authenticatedUser == nullptr)
@@ -111,6 +140,8 @@ void ServiceManagementService::purchaseComboPackage(const std::string& comboPack
std::string title = "Combo Package Service Booking succeeded"; 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(); std::string message = "Your service booking for the combo package has been successfully placed with ID " + serviceBooking->getId();
sendNotification(authenticatedUser, title, message); 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);
} }
util::Map<std::string, User*> ServiceManagementService::m_observers{}; util::Map<std::string, User*> ServiceManagementService::m_observers{};
@@ -187,6 +218,7 @@ void ServiceManagementService::sendNotification(User* user, const std::string& t
{ {
return; return;
} }
auto& trackedNotificationsMap = m_dataStore.getNotifications();
Notification* notification = Factory::getObject<Notification>( Notification* notification = Factory::getObject<Notification>(
user->getId(), user->getId(),
title, title,
@@ -196,9 +228,9 @@ void ServiceManagementService::sendNotification(User* user, const std::string& t
{ {
throw std::runtime_error("Failed to create notification"); throw std::runtime_error("Failed to create notification");
} }
auto& trackedNotificationsMap = m_dataStore.getNotifications();
trackedNotificationsMap.insert(notification->getId(), util::createNewRecord(notification)); trackedNotificationsMap.insert(notification->getId(), util::createNewRecord(notification));
m_dataStore.saveNotifications(); m_dataStore.saveNotifications();
EventManager::sendNotificationAvailableEvent(user->getId());
} }
/* /*
@@ -431,6 +463,7 @@ Return type: void
*/ */
void ServiceManagementService::createComboPackage(const std::string& packageName, const util::Vector<std::string>& serviceIDsInNewCombo, double discountPercentage) void ServiceManagementService::createComboPackage(const std::string& packageName, const util::Vector<std::string>& serviceIDsInNewCombo, double discountPercentage)
{ {
AuthenticationManagementService::ensureAuthorization();
DataStoreLockGuard lock(m_dataStore); DataStoreLockGuard lock(m_dataStore);
if (packageName.empty()) if (packageName.empty())
{ {
@@ -500,6 +533,7 @@ Return type: util::Map<std::string, ComboPackage*>
*/ */
util::Map<std::string, ComboPackage*> ServiceManagementService::getComboPackages() util::Map<std::string, ComboPackage*> ServiceManagementService::getComboPackages()
{ {
AuthenticationManagementService::ensureAuthorization();
DataStoreLockGuard lock(m_dataStore); DataStoreLockGuard lock(m_dataStore);
util::Map<std::string, ComboPackage*> comboPackages; util::Map<std::string, ComboPackage*> comboPackages;
comboPackages = util::getObjects(m_dataStore.getComboPackages()); comboPackages = util::getObjects(m_dataStore.getComboPackages());
@@ -514,6 +548,7 @@ Return type: void
*/ */
void ServiceManagementService::removeComboPackage(const std::string& comboPackageID) void ServiceManagementService::removeComboPackage(const std::string& comboPackageID)
{ {
AuthenticationManagementService::ensureAuthorization();
DataStoreLockGuard lock(m_dataStore); DataStoreLockGuard lock(m_dataStore);
bool removed = false; bool removed = false;
auto& trackedComboPackages = m_dataStore.getComboPackages(); auto& trackedComboPackages = m_dataStore.getComboPackages();
@@ -546,6 +581,7 @@ Returns:
*/ */
util::Map<std::string, ServiceBooking*> ServiceManagementService::getServiceBookings() util::Map<std::string, ServiceBooking*> ServiceManagementService::getServiceBookings()
{ {
AuthenticationManagementService::ensureAuthorization();
DataStoreLockGuard lock(m_dataStore); DataStoreLockGuard lock(m_dataStore);
util::Map<std::string, ServiceBooking*> serviceBookings; util::Map<std::string, ServiceBooking*> serviceBookings;
serviceBookings = util::getObjects(m_dataStore.getServiceBookings()); serviceBookings = util::getObjects(m_dataStore.getServiceBookings());
@@ -562,6 +598,7 @@ Returns:
*/ */
ServiceBooking* ServiceManagementService::getServiceBooking(const std::string& serviceID) ServiceBooking* ServiceManagementService::getServiceBooking(const std::string& serviceID)
{ {
AuthenticationManagementService::ensureAuthorization();
auto currentServiceBookings = getServiceBookings(); auto currentServiceBookings = getServiceBookings();
for (int iterator = 0; iterator < currentServiceBookings.getSize(); iterator++) for (int iterator = 0; iterator < currentServiceBookings.getSize(); iterator++)
{ {
@@ -588,14 +625,28 @@ Throws:
*/ */
void ServiceManagementService::createJobCard(const std::string& bookingID, const std::string& technicianID, const std::string& serviceID) void ServiceManagementService::createJobCard(const std::string& bookingID, const std::string& technicianID, const std::string& serviceID)
{ {
AuthenticationManagementService::ensureAuthorization();
DataStoreLockGuard lock(m_dataStore); DataStoreLockGuard lock(m_dataStore);
UserManagementService m_userManagementService; UserManagementService m_userManagementService;
ServiceBooking* currentBooking = getServiceBooking(bookingID); 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(); auto& currentTrackedJobCards = m_dataStore.getJobCards();
if (currentBooking == nullptr) auto& currentTrackedInventoryItems = m_dataStore.getInventoryItems();
{ auto& currentTrackedServiceBookings = m_dataStore.getServiceBookings();
throw std::runtime_error("Service Booking not available"); 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(); auto& currentServices = currentBooking->getServices();
if (currentServices.find(serviceID) == -1) if (currentServices.find(serviceID) == -1)
{ {
@@ -620,23 +671,44 @@ void ServiceManagementService::createJobCard(const std::string& bookingID, const
for (int iterator = 0; iterator < inventoryItems.getSize(); iterator++) for (int iterator = 0; iterator < inventoryItems.getSize(); iterator++)
{ {
InventoryItem* currentInventoryItem = inventoryItems.getValueAt(iterator); InventoryItem* currentInventoryItem = inventoryItems.getValueAt(iterator);
const std::string& currentInventoryItemId = inventoryItems.getKeyAt(iterator);
if (currentInventoryItem) 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(); int currentStockQuantity = currentInventoryItem->getQuantity();
currentInventoryItem->setQuantity(currentStockQuantity - 1); currentInventoryItem->setQuantity(currentStockQuantity - 1);
trackedCurrentInventoryItem.state = RecordState::MODIFIED;
} }
} }
currentBooking->setAssignedTechnician(selectedTechnician); const User* currentAssignedTechnician = currentBooking->getAssignedTechnician();
currentBooking->setAssignedTechnicianId(selectedTechnician->getId()); 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) if (currentBooking->getStatus() == util::ServiceJobStatus::PENDING)
{ {
currentBooking->setStatus(util::ServiceJobStatus::STARTED); currentBooking->setStatus(util::ServiceJobStatus::STARTED);
} }
std::string title = "Job card created"; currentTrackedServiceBooking.state = RecordState::MODIFIED;
std::string message = "Job card created for the service and you are assigned for that.";
JobCard* jobCard = Factory::getObject<JobCard>(bookingID, currentBooking, currentService, serviceID, technicianID, selectedTechnician, util::Timestamp(), util::ServiceJobStatus::STARTED, util::Timestamp()); JobCard* jobCard = Factory::getObject<JobCard>(bookingID, currentBooking, currentService, serviceID, technicianID, selectedTechnician, util::Timestamp(), util::ServiceJobStatus::STARTED, util::Timestamp());
if (jobCard) 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)); currentTrackedJobCards.insert(jobCard->getId(), util::createNewRecord(jobCard));
sendNotification(selectedTechnician, title, message); sendNotification(selectedTechnician, title, message);
} }
@@ -644,10 +716,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);
m_dataStore.saveJobCards(); m_dataStore.saveJobCards();
m_dataStore.saveServiceBookings();
m_dataStore.saveInventoryItems();
} }
/* /*
@@ -665,6 +736,7 @@ Throws:
*/ */
void ServiceManagementService::createService(const std::string& name, const util::Vector<std::string>& inventoryItemIDs, double laborCost) void ServiceManagementService::createService(const std::string& name, const util::Vector<std::string>& inventoryItemIDs, double laborCost)
{ {
AuthenticationManagementService::ensureAuthorization();
DataStoreLockGuard lock(m_dataStore); DataStoreLockGuard lock(m_dataStore);
util::Map<std::string, InventoryItem*> currentServiceInventoryItems; util::Map<std::string, InventoryItem*> currentServiceInventoryItems;
auto& trackedInventoryItems = m_dataStore.getInventoryItems(); auto& trackedInventoryItems = m_dataStore.getInventoryItems();
@@ -711,6 +783,7 @@ Returns:
*/ */
util::Map<std::string, Service*> ServiceManagementService::getServices() util::Map<std::string, Service*> ServiceManagementService::getServices()
{ {
AuthenticationManagementService::ensureAuthorization();
DataStoreLockGuard lock(m_dataStore); DataStoreLockGuard lock(m_dataStore);
util::Map<std::string, Service*> services; util::Map<std::string, Service*> services;
services = util::getObjects(m_dataStore.getServices()); services = util::getObjects(m_dataStore.getServices());
@@ -729,6 +802,7 @@ Throws:
*/ */
void ServiceManagementService::removeService(const std::string& serviceID) void ServiceManagementService::removeService(const std::string& serviceID)
{ {
AuthenticationManagementService::ensureAuthorization();
DataStoreLockGuard lock(m_dataStore); DataStoreLockGuard lock(m_dataStore);
auto& currentTrackedServices = m_dataStore.getServices(); auto& currentTrackedServices = m_dataStore.getServices();
auto& currentTrackedComboPackages = m_dataStore.getComboPackages(); auto& currentTrackedComboPackages = m_dataStore.getComboPackages();
@@ -766,6 +840,61 @@ void ServiceManagementService::removeService(const std::string& serviceID)
m_dataStore.saveComboPackages(); 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) Function: getServiceBookings (overloaded)
Description: Retrieves all service bookings for a specific customer. Description: Retrieves all service bookings for a specific customer.
@@ -776,6 +905,7 @@ Returns:
*/ */
util::Map<std::string, ServiceBooking*> ServiceManagementService::getServiceBookings(const std::string& customerID) util::Map<std::string, ServiceBooking*> ServiceManagementService::getServiceBookings(const std::string& customerID)
{ {
AuthenticationManagementService::ensureAuthorization();
util::Map<std::string, ServiceBooking*> currentServiceBookings = getServiceBookings(); util::Map<std::string, ServiceBooking*> currentServiceBookings = getServiceBookings();
util::Map<std::string, ServiceBooking*> currentUserServiceBookings; util::Map<std::string, ServiceBooking*> currentUserServiceBookings;
if (currentServiceBookings.getSize() != 0) if (currentServiceBookings.getSize() != 0)
@@ -802,6 +932,7 @@ Returns:
*/ */
util::Map<std::string, JobCard*> ServiceManagementService::getJobCards(const std::string& technicianID) util::Map<std::string, JobCard*> ServiceManagementService::getJobCards(const std::string& technicianID)
{ {
AuthenticationManagementService::ensureAuthorization();
DataStoreLockGuard lock(m_dataStore); DataStoreLockGuard lock(m_dataStore);
auto& trackedJobCards = m_dataStore.getJobCards(); auto& trackedJobCards = m_dataStore.getJobCards();
util::Map<std::string, JobCard*> technicianJobCards; util::Map<std::string, JobCard*> technicianJobCards;
@@ -856,45 +987,46 @@ Returns:
*/ */
void ServiceManagementService::updateJobStatus(const std::string& jobID) void ServiceManagementService::updateJobStatus(const std::string& jobID)
{ {
AuthenticationManagementService::ensureAuthorization();
DataStoreLockGuard lock(m_dataStore); DataStoreLockGuard lock(m_dataStore);
AuthenticationManagementService authenticationManagementService; AuthenticationManagementService authenticationManagementService;
PaymentManagementService paymentManagementService; PaymentManagementService paymentManagementService;
bool jobStatusUpdated = false, serviceBookingCompleted; bool jobStatusUpdated = false, serviceBookingCompleted;
JobCard* currentJob; User* currentTechnician = authenticationManagementService.getAuthenticatedUser();
User* currentTechnician = authenticationManagementService.getAuthenticatedUser(); if (currentTechnician == nullptr)
if (currentTechnician == nullptr) {
{ throw std::runtime_error("Unable to fetch current technician.");
throw std::runtime_error("Unable to fetch current technician."); }
} util::Map<std::string, JobCard*> currentAssignedJobs = getJobCards(currentTechnician->getId());
util::Map<std::string, JobCard*> currentAssignedJobs = getJobCards(currentTechnician->getId()); if (currentAssignedJobs.getSize() == 0)
if (currentAssignedJobs.getSize() == 0) {
{ throw std::runtime_error("No job cards assigned to the technician.");
throw std::runtime_error("No job cards assigned to the technician."); }
}
auto& trackedJobCards = m_dataStore.getJobCards(); auto& trackedJobCards = m_dataStore.getJobCards();
auto& trackedServiceBookings = m_dataStore.getServiceBookings(); auto& trackedServiceBookings = m_dataStore.getServiceBookings();
if (currentAssignedJobs.find(jobID) != -1) if (currentAssignedJobs.find(jobID) != -1)
{ {
int jobIndex = trackedJobCards.find(jobID); int jobIndex = trackedJobCards.find(jobID);
if (jobIndex == -1) if (jobIndex == -1)
{ {
throw std::runtime_error("Unable to fetch current job."); throw std::runtime_error("Unable to fetch current job.");
} }
currentJob = currentAssignedJobs.getValueAt(currentAssignedJobs.find(jobID)); auto& trackedCurrentJob = trackedJobCards.getValueAt(jobIndex);
if (currentJob == nullptr) JobCard* currentJob = trackedCurrentJob.data;
{ if (currentJob == nullptr)
throw std::runtime_error("Unable to fetch current job."); {
} throw std::runtime_error("Unable to fetch current job.");
if (currentJob->getStatus() == util::ServiceJobStatus::STARTED) }
{ if (currentJob->getStatus() == util::ServiceJobStatus::STARTED)
currentJob->setStatus(util::ServiceJobStatus::IN_PROGRESS); {
trackedJobCards.getValueAt(jobIndex).state = RecordState::MODIFIED; currentJob->setStatus(util::ServiceJobStatus::IN_PROGRESS);
jobStatusUpdated = true; trackedCurrentJob.state = RecordState::MODIFIED;
} jobStatusUpdated = true;
}
else if (currentJob->getStatus() == util::ServiceJobStatus::IN_PROGRESS) else if (currentJob->getStatus() == util::ServiceJobStatus::IN_PROGRESS)
{ {
currentJob->setStatus(util::ServiceJobStatus::COMPLETED); currentJob->setStatus(util::ServiceJobStatus::COMPLETED);
trackedJobCards.getValueAt(jobIndex).state = RecordState::MODIFIED; trackedCurrentJob.state = RecordState::MODIFIED;
jobStatusUpdated = true; jobStatusUpdated = true;
serviceBookingCompleted = hasCompletedAllJobs(currentJob->getBookingId(), currentAssignedJobs); serviceBookingCompleted = hasCompletedAllJobs(currentJob->getBookingId(), currentAssignedJobs);
if (serviceBookingCompleted) if (serviceBookingCompleted)
@@ -903,20 +1035,20 @@ void ServiceManagementService::updateJobStatus(const std::string& jobID)
currentJob->getBooking()->setStatus(util::ServiceJobStatus::COMPLETED); currentJob->getBooking()->setStatus(util::ServiceJobStatus::COMPLETED);
trackedServiceBookings.getValueAt(trackedServiceBookings.find(bookingId)).state = RecordState::MODIFIED; trackedServiceBookings.getValueAt(trackedServiceBookings.find(bookingId)).state = RecordState::MODIFIED;
paymentManagementService.generateInvoice(currentJob->getBooking()); paymentManagementService.generateInvoice(currentJob->getBooking());
std::string title = "Service Booking completed. Invoice Generated."; std::string title = "Service Booking Completed";
std::string message = "Services completed for the booking and invoice generated."; std::string message = "Service Booking (ID: " + bookingId + ") has been completed successfully. An invoice has been generated.";
sendNotification(currentJob->getBooking()->getCustomer(), title, message); sendNotification(currentJob->getBooking()->getCustomer(), title, message);
} }
} }
} }
else else
{ {
throw std::runtime_error("Failed to update job status. Job may already be completed."); throw std::runtime_error("Failed to update job status. Job may already be completed.");
} }
if (!jobStatusUpdated) if (!jobStatusUpdated)
{ {
throw std::runtime_error("Failed to update job status. Job may already be completed."); throw std::runtime_error("Failed to update job status. Job may already be completed.");
} }
m_dataStore.saveJobCards(); m_dataStore.saveJobCards();
m_dataStore.saveServiceBookings(); m_dataStore.saveServiceBookings();
} }
@@ -35,6 +35,7 @@ public:
void createJobCard(const std::string& bookingID, const std::string& technicianID, const std::string& serviceID); 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 createService(const std::string& name, const util::Vector<std::string>& inventoryItemIDs, double laborCost);
void removeService(const std::string& serviceID); void removeService(const std::string& serviceID);
void removeServiceBooking(const std::string& bookingID);
util::Map<std::string, JobCard*> getJobCards(const std::string& technicianID); util::Map<std::string, JobCard*> getJobCards(const std::string& technicianID);
void updateJobStatus(const std::string& jobID); void updateJobStatus(const std::string& jobID);
void cancelCustomerServiceBookings(const std::string& customerID); void cancelCustomerServiceBookings(const std::string& customerID);
@@ -15,6 +15,7 @@ Date:19-May-2026
#include "Notification.h" #include "Notification.h"
#include "PaymentManagementService.h" #include "PaymentManagementService.h"
#include "ServiceManagementService.h" #include "ServiceManagementService.h"
#include "AuthenticationManagementService.h"
#include "User.h" #include "User.h"
#include "UserManagementService.h" #include "UserManagementService.h"
#include "Vector.h" #include "Vector.h"
@@ -22,6 +23,7 @@ Date:19-May-2026
#include "Utility.h" #include "Utility.h"
#include "TrackedRecord.h" #include "TrackedRecord.h"
#include "DataStoreLockGuard.h" #include "DataStoreLockGuard.h"
#include "EventManager.h"
/* /*
Function: ensureAdminExists Function: ensureAdminExists
@@ -113,6 +115,7 @@ Return type: void
*/ */
void UserManagementService::updateUserDetails(const std::string& userID, const std::string& email, const std::string& phone) void UserManagementService::updateUserDetails(const std::string& userID, const std::string& email, const std::string& phone)
{ {
AuthenticationManagementService::ensureAuthorization();
DataStoreLockGuard lock(m_dataStore); DataStoreLockGuard lock(m_dataStore);
auto& trackedUsersMap = m_dataStore.getUsers(); auto& trackedUsersMap = m_dataStore.getUsers();
auto usersMap = util::getObjects(trackedUsersMap); auto usersMap = util::getObjects(trackedUsersMap);
@@ -160,6 +163,7 @@ Throws:
*/ */
util::Vector<Notification*> UserManagementService::getUserNotifications(const std::string& userID) util::Vector<Notification*> UserManagementService::getUserNotifications(const std::string& userID)
{ {
AuthenticationManagementService::ensureAuthorization();
DataStoreLockGuard lock(m_dataStore); DataStoreLockGuard lock(m_dataStore);
auto& trackedUsersMap = m_dataStore.getUsers(); auto& trackedUsersMap = m_dataStore.getUsers();
if (trackedUsersMap.find(userID) == -1) if (trackedUsersMap.find(userID) == -1)
@@ -203,6 +207,7 @@ Throws:
*/ */
void UserManagementService::deleteNotification(const std::string& notificationID, const std::string& userID) void UserManagementService::deleteNotification(const std::string& notificationID, const std::string& userID)
{ {
AuthenticationManagementService::ensureAuthorization();
DataStoreLockGuard lock(m_dataStore); DataStoreLockGuard lock(m_dataStore);
auto& trackedUsersMap = m_dataStore.getUsers(); auto& trackedUsersMap = m_dataStore.getUsers();
auto& trackedNotificationsMap = m_dataStore.getNotifications(); auto& trackedNotificationsMap = m_dataStore.getNotifications();
@@ -264,33 +269,42 @@ Return type: void
*/ */
void UserManagementService::removeUser(const std::string& userID) void UserManagementService::removeUser(const std::string& userID)
{ {
AuthenticationManagementService::ensureAuthorization();
InventoryManagementService inventoryManagementService; InventoryManagementService inventoryManagementService;
PaymentManagementService paymentManagementService; PaymentManagementService paymentManagementService;
ServiceManagementService serviceManagementService; ServiceManagementService serviceManagementService;
DataStoreLockGuard lock(m_dataStore); std::string removedUserID;
auto& trackedUsersMap = m_dataStore.getUsers();
int index = trackedUsersMap.find(userID);
if (index != -1)
{ {
User* user = trackedUsersMap.getValueAt(index).data; DataStoreLockGuard lock(m_dataStore);
if (user != nullptr) auto& trackedUsersMap = m_dataStore.getUsers();
int index = trackedUsersMap.find(userID);
if (index != -1)
{ {
if (user->getUserType() == util::UserType::CUSTOMER) User* user = trackedUsersMap.getValueAt(index).data;
if (user != nullptr)
{ {
serviceManagementService.cancelCustomerServiceBookings(userID); if (user->getUserType() == util::UserType::CUSTOMER)
{
serviceManagementService.cancelCustomerServiceBookings(userID);
}
if (user->getUserType() == util::UserType::TECHNICIAN)
{
serviceManagementService.cancelTechnicianJobs(userID);
}
inventoryManagementService.detach(user);
paymentManagementService.detach(user);
serviceManagementService.detach(user);
user->setState(util::State::INACTIVE);
trackedUsersMap.getValueAt(index).state = RecordState::MODIFIED;
removedUserID = user->getId();
m_dataStore.saveUsers();
} }
if (user->getUserType() == util::UserType::TECHNICIAN)
{
serviceManagementService.cancelTechnicianJobs(userID);
}
inventoryManagementService.detach(user);
paymentManagementService.detach(user);
serviceManagementService.detach(user);
user->setState(util::State::INACTIVE);
trackedUsersMap.getValueAt(index).state = RecordState::MODIFIED;
m_dataStore.saveUsers();
} }
} }
if (!removedUserID.empty())
{
EventManager::sendUserDisabledEvent(removedUserID);
}
} }
/* /*
@@ -30,10 +30,16 @@ Return type: void
*/ */
void AdminMenu::showMenu() void AdminMenu::showMenu()
{ {
startEventListener();
while (true) while (true)
{ {
try try
{ {
if (!m_isMenuActive)
{
logout();
break;
}
int choice; int choice;
util::clear(); util::clear();
std::cout << "Admin Menu" std::cout << "Admin Menu"
@@ -68,6 +74,7 @@ void AdminMenu::showMenu()
util::pressEnter(); util::pressEnter();
} }
} }
stopEventListener();
} }
/* /*
@@ -78,6 +85,11 @@ Return type: bool - true if menu continues, false if logout
*/ */
bool AdminMenu::handleOperation(int choice) bool AdminMenu::handleOperation(int choice)
{ {
if (!m_isMenuActive)
{
logout();
return false;
}
switch (choice) switch (choice)
{ {
case 1: case 1:
@@ -141,6 +153,25 @@ bool AdminMenu::handleOperation(int choice)
return true; return true;
} }
/*
Function: handleNotificationEvent
Description: Retrieves and displays the latest notification for the
currently logged in admin.
Parameter: None
Return type: void
*/
void AdminMenu::handleNotificationEvent()
{
auto notifications = m_controller.getNotifications();
const User* authenticatedUser = m_controller.getAuthenticatedUser();
std::string name;
if (authenticatedUser)
{
name = authenticatedUser->getName();
}
displayNewNotification(notifications, name);
}
/* /*
Function: logout Function: logout
Description: Logs out the currently authenticated admin user. Description: Logs out the currently authenticated admin user.
@@ -307,6 +338,23 @@ void AdminMenu::removeInventoryItem()
std::string selectedItemId = selectedItem->getId(); std::string selectedItemId = selectedItem->getId();
m_controller.removeInventoryItem(selectedItemId); m_controller.removeInventoryItem(selectedItemId);
std::cout << "Item " << selectedItem->getPartName() << " removed successfully." << std::endl; std::cout << "Item " << selectedItem->getPartName() << " removed successfully." << std::endl;
const util::Map<std::string, const Service*>& listOfService = m_controller.getServices();
for (int serviceIndex = 0; serviceIndex < listOfService.getSize(); serviceIndex++)
{
const Service* service = listOfService.getValueAt(serviceIndex);
if (!service)
{
continue;
}
const util::Map<std::string, InventoryItem*>& requiredItems = service->getRequiredInventoryItems();
if (requiredItems.find(selectedItemId) != -1)
{
m_controller.removeService(service->getId());
std::cout << "Service " << service->getName()
<< " removed as the item "
<< selectedItem->getPartName() << " required for the service has been removed." << std::endl;
}
}
} }
} }
util::pressEnter(); util::pressEnter();
@@ -9,12 +9,13 @@ Date:19-May-2026
#pragma once #pragma once
#include "Controller.h" #include "Controller.h"
#include "Menu.h"
class AdminMenu class AdminMenu : public Menu
{ {
private: private:
Controller m_controller;
bool handleOperation(int choice); bool handleOperation(int choice);
void handleNotificationEvent() override;
public: public:
void showMenu(); void showMenu();
void logout(); void logout();
@@ -32,10 +32,17 @@ Return type: void
*/ */
void CustomerMenu::showMenu() void CustomerMenu::showMenu()
{ {
startEventListener();
while (true) while (true)
{ {
try try
{ {
if (!m_isMenuActive)
{
logout();
break;
}
int choice; int choice;
util::clear(); util::clear();
std::cout << "Customer Menu" std::cout << "Customer Menu"
@@ -44,11 +51,12 @@ void CustomerMenu::showMenu()
<< "\n3. Update Profile" << "\n3. Update Profile"
<< "\n4. Change Password" << "\n4. Change Password"
<< "\n5. View Service History" << "\n5. View Service History"
<< "\n6. Complete Payments" << "\n6. Cancel Service Booking"
<< "\n7. View Invoices" << "\n7. Complete Payments"
<< "\n8. View Notifications" << "\n8. View Invoices"
<< "\n9. Configure Notifications" << "\n9. View Notifications"
<< "\n10. Logout" << "\n10. Configure Notifications"
<< "\n11. Logout"
<< "\nEnter a choice: "; << "\nEnter a choice: ";
util::read(choice); util::read(choice);
if (!handleOperation(choice)) if (!handleOperation(choice))
@@ -62,6 +70,7 @@ void CustomerMenu::showMenu()
util::pressEnter(); util::pressEnter();
} }
} }
stopEventListener();
} }
/* /*
@@ -72,6 +81,11 @@ Return type: bool - true if menu continues, false if logout
*/ */
bool CustomerMenu::handleOperation(int choice) bool CustomerMenu::handleOperation(int choice)
{ {
if (!m_isMenuActive)
{
logout();
return false;
}
switch (choice) switch (choice)
{ {
case 1: case 1:
@@ -90,18 +104,21 @@ bool CustomerMenu::handleOperation(int choice)
viewServiceHistory(); viewServiceHistory();
break; break;
case 6: case 6:
completePayments(); cancelServiceBooking();
break; break;
case 7: case 7:
viewInvoices(); completePayments();
break; break;
case 8: case 8:
viewNotifications(); viewInvoices();
break; break;
case 9: case 9:
configureNotifications(); viewNotifications();
break; break;
case 10: case 10:
configureNotifications();
break;
case 11:
logout(); logout();
return false; return false;
default: default:
@@ -111,6 +128,25 @@ bool CustomerMenu::handleOperation(int choice)
return true; return true;
} }
/*
Function: handleNotificationEvent
Description: Retrieves and displays the latest notification for the
currently logged in admin.
Parameter: None
Return type: void
*/
void CustomerMenu::handleNotificationEvent()
{
auto notifications = m_controller.getNotifications();
const User* authenticatedUser = m_controller.getAuthenticatedUser();
std::string name;
if (authenticatedUser)
{
name = authenticatedUser->getName();
}
displayNewNotification(notifications, name);
}
/* /*
Function: logout Function: logout
Description: Logs out the currently authenticated customer user. Description: Logs out the currently authenticated customer user.
@@ -301,6 +337,46 @@ void CustomerMenu::viewServiceHistory()
util::pressEnter(); 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 Function: completePayments
Description: Allows the customer to complete pending payments for invoices. Description: Allows the customer to complete pending payments for invoices.
@@ -9,13 +9,14 @@ Date:19-May-2026
*/ */
#pragma once #pragma once
#include "Menu.h"
#include "Controller.h" #include "Controller.h"
class CustomerMenu class CustomerMenu : public Menu
{ {
private: private:
Controller m_controller;
bool handleOperation(int choice); bool handleOperation(int choice);
void handleNotificationEvent();
public: public:
void showMenu(); void showMenu();
void logout(); void logout();
@@ -27,5 +28,6 @@ public:
void completePayments(); void completePayments();
void viewInvoices(); void viewInvoices();
void viewNotifications(); void viewNotifications();
void cancelServiceBooking();
void configureNotifications(); void configureNotifications();
}; };
@@ -0,0 +1,150 @@
/*
File: Menu.cpp
Description: Implementation file containing common menu event listener
functionality, account disable handling, and notification
event dispatching for all menu types.
Author: Trenser
Date:16-Jun-2026
*/
#include "Menu.h"
/*
Function: Menu
Description: Constructs a Menu object and initializes event handles
and menu state.
Parameter: None
Return type: None
*/
Menu::Menu()
:
m_isMenuActive(false),
m_accountDisabledEvent(NULL),
m_notificationAvailableEvent(NULL),
m_shutdownEvent(NULL) {}
/*
Function: ~Menu
Description: Destroys the Menu object and performs event listener
cleanup.
Parameter: None
Return type: None
*/
Menu::~Menu()
{
stopEventListener();
}
/*
Function: startEventListener
Description: Creates menu event handles, registers them with the
authentication service, and starts the event listener
thread.
Parameter: None
Return type: void
*/
void Menu::startEventListener()
{
if (m_isMenuActive.load())
{
return;
}
m_isMenuActive.store(true);
m_accountDisabledEvent = CreateEventA(NULL, FALSE, FALSE, NULL);
m_notificationAvailableEvent = CreateEventA(NULL, FALSE, FALSE, NULL);
m_shutdownEvent = CreateEventA(NULL, FALSE, FALSE, NULL);
m_controller.registerEvents(m_accountDisabledEvent, m_notificationAvailableEvent);
m_eventListenerThread = std::thread(&Menu::eventListenerLoop, this);
}
/*
Function: eventListenerLoop
Description: Waits for account disabled, notification available,
and shutdown events and dispatches them to the
appropriate handlers.
Parameter: None
Return type: void
*/
void Menu::eventListenerLoop()
{
HANDLE handles[3];
handles[0] = m_accountDisabledEvent;
handles[1] = m_notificationAvailableEvent;
handles[2] = m_shutdownEvent;
while (m_isMenuActive.load())
{
DWORD result = WaitForMultipleObjects(3, handles, FALSE, INFINITE);
switch (result)
{
case WAIT_OBJECT_0:
handleAccountDisabledEvent();
break;
case WAIT_OBJECT_0 + 1:
handleNotificationEvent();
break;
case WAIT_OBJECT_0 + 2:
return;
}
}
}
/*
Function: stopEventListener
Description: Stops the event listener thread and releases all
associated event handles.
Parameter: None
Return type: void
*/
void Menu::stopEventListener()
{
m_isMenuActive.store(false);
if (m_shutdownEvent)
{
SetEvent(m_shutdownEvent);
}
if (m_eventListenerThread.joinable())
{
m_eventListenerThread.join();
}
if (m_accountDisabledEvent)
{
CloseHandle(m_accountDisabledEvent);
}
if (m_notificationAvailableEvent)
{
CloseHandle(m_notificationAvailableEvent);
}
if (m_shutdownEvent)
{
CloseHandle(m_shutdownEvent);
}
m_accountDisabledEvent = NULL;
m_notificationAvailableEvent = NULL;
m_shutdownEvent = NULL;
}
/*
Function: handleAccountDisabledEvent
Description: Handles an account disabled event by marking the menu
inactive and notifying the user.
Parameter: None
Return type: void
*/
void Menu::handleAccountDisabledEvent()
{
m_isMenuActive.store(false);
const User* authenticatedUser = m_controller.getAuthenticatedUser();
std::string messageTitle = "Account Disabled";
if (authenticatedUser)
{
messageTitle += " - " + authenticatedUser->getName();
}
MessageBoxA(
GetConsoleWindow(),
"Your account has been disabled.",
messageTitle.c_str(),
MB_OK |
MB_ICONWARNING |
MB_SETFOREGROUND |
MB_TOPMOST);
}
@@ -0,0 +1,33 @@
/*
File: Menu.h
Description: Base class providing common event listener functionality
for all menu implementations.
Author: Trenser
Date:16-Jun-2026
*/
#pragma once
#include <windows.h>
#include <atomic>
#include <thread>
#include "Controller.h"
class Menu
{
protected:
Controller m_controller;
std::atomic<bool> m_isMenuActive;
HANDLE m_accountDisabledEvent;
HANDLE m_notificationAvailableEvent;
HANDLE m_shutdownEvent;
std::thread m_eventListenerThread;
void startEventListener();
void stopEventListener();
void eventListenerLoop();
void handleAccountDisabledEvent();
virtual void handleNotificationEvent() = 0;
public:
Menu();
virtual ~Menu();
};
@@ -28,6 +28,7 @@ Date: 21-May-2026
#include "Utility.h" #include "Utility.h"
#include "Validator.h" #include "Validator.h"
#include "Vector.h" #include "Vector.h"
#include "StringHelper.h"
/* /*
Function: displayAllServices Function: displayAllServices
@@ -1056,6 +1057,21 @@ inline const Service* selectServiceFromServices(const util::Map<std::string, con
{ {
continue; continue;
} }
bool hasDepletedItem = false;
const util::Map<std::string, InventoryItem*>& requiredItems = currentService->getRequiredInventoryItems();
for (int itemIndex = 0; itemIndex < requiredItems.getSize(); itemIndex++)
{
const InventoryItem* item = requiredItems.getValueAt(itemIndex);
if (!item || item->getQuantity() < 1)
{
hasDepletedItem = true;
break;
}
}
if (hasDepletedItem)
{
continue;
}
activeServicesMap.insert(currentIndex, currentService); activeServicesMap.insert(currentIndex, currentService);
double partsCost = util::calculatePartsCost(currentService); double partsCost = util::calculatePartsCost(currentService);
std::cout << std::left std::cout << std::left
@@ -1407,4 +1423,47 @@ inline std::string selectComboPackage(util::Map<std::string, const ComboPackage*
std::cout << "Enter a valid choice.\n"; std::cout << "Enter a valid choice.\n";
return ""; return "";
} }
} }
/*
Function: displayNewNotification
Description: Displays the most recent notification from the supplied
notification collection.
Parameter: util::Vector<const Notification*> notifications -
collection of notifications
const std::string& - The name of the user currently authenticated with the system
Return type: void
*/
inline void displayNewNotification(util::Vector<const Notification*> notifications, const std::string& name)
{
const Notification* notification = nullptr;
size_t numberOfNotifications = notifications.getSize();
for (int index = 0; index < numberOfNotifications; index++)
{
if (!notification)
{
notification = notifications[index];
}
else
{
if (util::extractNumber(notification->getId()) < util::extractNumber(notifications[index]->getId()))
{
notification = notifications[index];
}
}
}
if (notification)
{
std::string messageTitle = notification->getTitle();
if (!name.empty())
{
messageTitle += " - " + name;
}
MessageBoxA(
GetConsoleWindow(),
notification->getMessage().c_str(),
messageTitle.c_str(),
MB_OK |
MB_ICONINFORMATION);
}
}
@@ -27,10 +27,16 @@ Returns:
*/ */
void TechnicianMenu::showMenu() void TechnicianMenu::showMenu()
{ {
startEventListener();
while (true) while (true)
{ {
try try
{ {
if (!m_isMenuActive)
{
logout();
break;
}
int choice; int choice;
util::clear(); util::clear();
std::cout << "Technician Menu" std::cout << "Technician Menu"
@@ -52,6 +58,7 @@ void TechnicianMenu::showMenu()
util::pressEnter(); util::pressEnter();
} }
} }
stopEventListener();
} }
/* /*
@@ -62,6 +69,11 @@ Return type: bool - true if menu continues, false if logout
*/ */
bool TechnicianMenu::handleOperation(int choice) bool TechnicianMenu::handleOperation(int choice)
{ {
if (!m_isMenuActive)
{
logout();
return false;
}
switch (choice) switch (choice)
{ {
case 1: case 1:
@@ -86,6 +98,25 @@ bool TechnicianMenu::handleOperation(int choice)
return true; return true;
} }
/*
Function: handleNotificationEvent
Description: Retrieves and displays the latest notification for the
currently logged in admin.
Parameter: None
Return type: void
*/
void TechnicianMenu::handleNotificationEvent()
{
auto notifications = m_controller.getNotifications();
const User* authenticatedUser = m_controller.getAuthenticatedUser();
std::string name;
if (authenticatedUser)
{
name = authenticatedUser->getName();
}
displayNewNotification(notifications, name);
}
/* /*
Function: displayJobs Function: displayJobs
Description: Displays all Jobs assigned to a Technician Description: Displays all Jobs assigned to a Technician
@@ -9,12 +9,13 @@ Date:19-May-2026
#pragma once #pragma once
#include "Controller.h" #include "Controller.h"
#include "Menu.h"
class TechnicianMenu class TechnicianMenu : public Menu
{ {
private: private:
Controller m_controller;
bool handleOperation(int choice); bool handleOperation(int choice);
void handleNotificationEvent();
public: public:
void showMenu(); void showMenu();
void displayJobs(); void displayJobs();