Compare commits

..

8 Commits

Author SHA1 Message Date
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
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
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
Jissin Mathew f3e42a8b17 Merged PR 1172: Payment Management Refactoring - 1930
User Story #1949
User Story #1950

**Changes**

* Added `DataStoreLockGuard` header to project configuration and implemented the class for scoped mutex management.
* Refactored `Invoice` model to replace CSV-based serialization with `SerializedInvoice` struct, removing legacy `getHeaders()` and string parsing logic.
* Updated `DataStore::getInvoices()` to load records via `loadRecords`, refresh cache, and automatically enrich `Invoice` objects with linked `ServiceBooking` and `InventoryItem` entities.
* Implemented `DataStore::saveInvoices()` using the `saveRecords<Invoice, SerializedInvoice>` template for direct shared memory persistence.
* Integrated `DataStoreLockGuard` across all critical methods in `PaymentManagementService`: `sendPaymentReminders`, `generateInvoice`, `getInvoices`, `completePayment`, `getAllInvoices`, and `confirmPayment`.
* Refactored invoice creation and modification flows to use `createNewRecord` for insertion and explicitly set `RecordState::MODIFIED` before triggering `saveInvoices()`.
* Updated data access patterns to extract `.data` pointers from `TrackedRecord` wrappers instead of accessing raw map values directly.
* Added validation logic in `getInvoices()` to throw `runtime_error` if referenced ServiceBookings or InventoryItems are missing.
* Added necessary header dependencies (`Invoice.h`, `DataStoreLockGuard.h`) in service and store layers.

Related work items: #1930, #1949, #1950
2026-06-15 15:27:59 +05:30
29 changed files with 795 additions and 61 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>
@@ -622,3 +622,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"
@@ -72,4 +73,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);
};
@@ -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);
@@ -27,6 +27,32 @@ Date:19-May-2026
#include "DataStoreLockGuard.h" #include "DataStoreLockGuard.h"
#include "Utility.h" #include "Utility.h"
#include "DataStoreLockGuard.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 +67,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 +98,7 @@ 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);
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,7 @@ 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);
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 +217,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 +227,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 +462,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 +532,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 +547,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 +580,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 +597,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,6 +624,7 @@ 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);
@@ -665,6 +702,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 +749,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 +768,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();
@@ -776,6 +816,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 +843,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,6 +898,7 @@ 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;
@@ -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,19 @@ 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();
displayNewNotification(notifications);
}
/* /*
Function: logout Function: logout
Description: Logs out the currently authenticated admin user. Description: Logs out the currently authenticated admin user.
@@ -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"
@@ -62,6 +69,7 @@ void CustomerMenu::showMenu()
util::pressEnter(); util::pressEnter();
} }
} }
stopEventListener();
} }
/* /*
@@ -72,6 +80,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:
@@ -111,6 +124,19 @@ 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();
displayNewNotification(notifications);
}
/* /*
Function: logout Function: logout
Description: Logs out the currently authenticated customer user. Description: Logs out the currently authenticated customer user.
@@ -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();
@@ -0,0 +1,144 @@
/*
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);
MessageBoxA(
GetConsoleWindow(),
"Your account has been disabled.",
"Account Disabled",
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();
};
@@ -1407,4 +1407,38 @@ 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
Return type: void
*/
inline void displayNewNotification(util::Vector<const Notification*> notifications)
{
const Notification* notification = nullptr;
size_t numberOfNotifications = notifications.getSize();
for (int index = 0; index < numberOfNotifications; index++)
{
if (!notification)
{
notification = notifications[index];
}
else
{
if (notification->getId() < notifications[index]->getId())
{
notification = notifications[index];
}
}
}
MessageBoxA(
GetConsoleWindow(),
notification->getMessage().c_str(),
notification->getTitle().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,19 @@ 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();
displayNewNotification(notifications);
}
/* /*
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();