Compare commits

...

12 Commits

Author SHA1 Message Date
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
joelthomastrenser d8ef54ddd0 Merge branch 'develop' into develop-sm-payment-management 2026-06-15 15:27:45 +05:30
Avinash Rajesh 5fb92fba0e Merged PR 1170: Inventory Management Refactoring - 1926
User Story #1957
User Story #1958
**Changes**
- Added DataStoreLockGuard for scoped datastore locking/unlocking.
- Refactored InventoryItem serialization from CSV strings to SerializedInventoryItem struct.
- Removed old serialize, deserialize, and getHeaders methods from InventoryItem.
- Updated DataStore to load, refresh, and save inventory items using typed records.
- Applied lock guard in InventoryManagementService for stock alerts, add/remove/update operations.
- Enhanced error handling for invalid item IDs and missing inventory records.
- Simplified getInventoryItems to return object maps from tracked records.
- Removed redundant persistence methods (loadInventoryItems, saveInventoryItems) from service layer.

Related work items: #1926, #1957, #1958
2026-06-15 15:26:00 +05:30
joelthomastrenser 95d1c9a04e Merge branch 'develop' into develop-sm-inventory-management 2026-06-15 15:25:50 +05:30
Jissin Mathew 2963d05ea1 Merged PR 1171: Service Management Refactoring -1927
User Story #1955
User Story #1956

**Changes**
* Added `DataStoreLockGuard` class to provide scoped automatic locking and unlocking for the datastore.
* Refactored `Service`, `ServiceBooking`, `JobCard`, and `ComboPackage` models to replace CSV-based serialization with fixed-size `Serialized*` struct records.
* Removed legacy `serialize()`, `deserialize()`, and `getHeaders()` methods from all affected model classes.
* Updated `DataStore` getter methods (`getServices`, `getComboPackages`, `getServiceBookings`, `getJobCards`) to load records from shared memory and automatically enrich them with linked entities (inventory items, services, bookings, users).
* Implemented generic save helpers (`saveServices`, `saveComboPackages`, etc.) using the `saveRecords` template to persist tracked records directly.
* Integrated `DataStoreLockGuard` into critical `ServiceManagementService` methods including `purchaseService`, `purchaseComboPackage`, `createService`, `removeService`, `cancelCustomerServiceBookings`, and `updateJobStatus`.
* Refactored cancellation workflows (`cancelCustomerServiceBookings`, `cancelTechnicianJobs`) to use `TrackedRecord` objects, correctly marking states as `MODIFIED` before persisting changes.
* Updated inventory restoration logic to accept tracked inventory maps and increment quantities while updating record states.
* Modified service layer access patterns to extract `.data` pointers from `TrackedRecord` wrappers instead of accessing raw map values directly.
* Added necessary header dependencies (`DataStoreLockGuard.h`, `SerializedRecords.h`) across data store and service layers.
* Removed redundant manual persistence calls in the service layer, relying on explicit `save*` calls after modifications within locked scopes.

Related work items: #1927, #1955, #1956
2026-06-15 15:24:40 +05:30
Jissin Mathew d2a7420db5 Implement Review Fixes 2026-06-15 15:14:16 +05:30
Jissin Mathew 7047e96b3c Implement Service Refactoring
<UserStory> 1950: Implement Service Refactorings </UserStory>

UserStory #1950

<Changes>

1. Added `DataStoreLockGuard` integration in `PaymentManagementService` methods (`sendPaymentReminders`, `generateInvoice`, `getInvoices`, `completePayment`, `getAllInvoices`, `confirmPayment`) to ensure thread-safe access to the datastore.

2. Implemented record enrichment logic in `DataStore::getInvoices()` to automatically link `Invoice` objects with their corresponding `ServiceBooking` and `InventoryItem` entities during loading, validating relationships and throwing exceptions for invalid references.

3. Refactored invoice persistence by implementing `saveInvoices()` using `saveRecords<Invoice, SerializedInvoice>` to write tracked records directly to shared memory.

4. Updated invoice creation and modification flows:
   - `generateInvoice`: Uses `createNewRecord` to insert new invoices into the tracked cache and triggers immediate persistence.
   - `completePayment` & `confirmPayment`: Marks records as `MODIFIED` in the `TrackedRecord` state before saving changes.

5. Updated data access patterns across `PaymentManagementService` to use `.data` pointers from `TrackedRecord` objects returned by the datastore, replacing direct pointer access to mapped objects.

6. Added necessary header dependencies including `DataStoreLockGuard.h`, `Invoice.h`, and `SerializedRecords.h` to support locking mechanisms and structured serialization.

</Changes>

<Test>

N/A

</Test>

<Review>

Sreeja Reghukumar, please review

</Review>
2026-06-15 12:43:44 +05:30
Jissin Mathew f545d57f79 Implement Model Refactoring
<UserStory> 1949: Model Refactoring</UserStory>

UserStory #1949

<Changes>
Replaced CSV-based serialization and deserialization in the Invoice model with fixed-size SerializedRecord structures for shared memory storage.
Implemented serialize() method to convert Invoice objects into SerializedInvoice records using direct struct field assignment and strcpy_s for string fields.
Implemented deserialize() method to reconstruct Invoice objects directly from SerializedInvoice types instead of parsing CSV strings.
Updated Invoice class interfaces to use SerializedInvoice types, removing legacy CSV serialization APIs including getHeaders() function.
Added SerializedRecords.h dependency and forward declarations for SerializedInvoice structure in Invoice header.
Minor formatting adjustments in DataStore.cpp (closing brace placement).
</Changes>
<Test>
N/A

</Test>
<Review>
Sreeja Reghukumar, please review

</Review>
2026-06-15 11:09:42 +05:30
Avinash Rajesh f0c7d27e6c Implement Service Refactoring
<UserStory> 1958: Service Refactoring </UserStory>
UserStory #1958
<Changes>

1. Added DataStoreLockGuard.h include to project file and InventoryManagementService
   for thread-safe datastore operations.

2. Enhanced DataStore::getUsers to load SerializedUser records, refresh cache,
   and attach notifications with recipient validation.

3. Enhanced DataStore::getInventoryItems to load SerializedInventoryItem records
   and refresh cache for tracked inventory items.

4. Refactored InventoryManagementService::sendLowStockAlerts to use tracked
   inventory and user maps with DataStoreLockGuard, ensuring safe access.

5. Removed legacy observer management, load/save inventory items, and related
   persistence functions from InventoryManagementService.

6. Updated InventoryManagementService::addInventoryItem to insert new records
   into tracked inventory map and persist changes via saveInventoryItems.

7. Updated InventoryManagementService::addInventoryItemStock to validate item
   existence, update quantity, mark record as MODIFIED, and persist changes.

8. Refactored InventoryManagementService::getInventoryItems to return object
   map extracted from tracked records with DataStoreLockGuard.

9. Updated InventoryManagementService::removeInventoryItem to validate item ID,
   mark state as INACTIVE, set record state to MODIFIED, and persist changes.

10. Updated InventoryManagementService::getInventoryItem to safely retrieve
    inventory items from tracked records with error handling.

</Changes>

<Test>

N/A

</Test>

<Review>

Sreeja Reghukumar

</Review>
2026-06-12 18:32:49 +05:30
Avinash Rajesh b98062d45c Implement Model Refactoring
<UserStory> 1957: Model Refactoring </UserStory>

UserStory #1957

<Changes>

1. Added SerializedRecords.h dependency and forward declaration for SerializedInventoryItem
   to support fixed-size record storage.

2. Replaced CSV-based serialization in InventoryItem with serialize() method returning
   SerializedInventoryItem structure.

3. Replaced CSV-based deserialization logic with deserialize() method that reconstructs
   InventoryItem directly from SerializedInventoryItem record.

4. Removed legacy CSV parsing, header generation, and exception handling tied to string-based
   serialization.

5. Updated InventoryItem class interface in InventoryItem.h to use SerializedInventoryItem
   types instead of std::string serialization APIs.

</Changes>

<Test>

N/A

</Test>

<Review>

Sreeja Reghukumar

</Review>
2026-06-12 15:30:34 +05:30
34 changed files with 921 additions and 272 deletions
@@ -102,7 +102,7 @@
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<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>
<Link>
<SubSystem>Console</SubSystem>
@@ -117,7 +117,7 @@
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<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>
<Link>
<SubSystem>Console</SubSystem>
@@ -126,10 +126,11 @@
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="controllers\Controller.cpp" />
<ClCompile Include="core\events\EventManager.cpp" />
<ClCompile Include="core\patterns\Observer.cpp" />
<ClCompile Include="core\patterns\Subject.cpp" />
<ClCompile Include="core\sharedmemory\SharedMemory.cpp" />
<ClCompile Include="datastores\DataStore.cpp" />
<ClCompile Include="datastores\sharedmemory\SharedMemory.cpp" />
<ClCompile Include="models\ComboPackage.cpp" />
<ClCompile Include="models\InventoryItem.cpp" />
<ClCompile Include="models\Invoice.cpp" />
@@ -148,21 +149,23 @@
<ClCompile Include="utilities\Validator.cpp" />
<ClCompile Include="views\AdminMenu.cpp" />
<ClCompile Include="views\CustomerMenu.cpp" />
<ClCompile Include="views\Menu.cpp" />
<ClCompile Include="views\TechnicianMenu.cpp" />
<ClCompile Include="views\UserInterface.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="controllers\Controller.h" />
<ClInclude Include="core\events\EventManager.h" />
<ClInclude Include="core\patterns\Observer.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\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="models\ComboPackage.h" />
<ClInclude Include="models\InventoryItem.h" />
@@ -191,6 +194,7 @@
<ClInclude Include="utilities\Vector.h" />
<ClInclude Include="views\AdminMenu.h" />
<ClInclude Include="views\CustomerMenu.h" />
<ClInclude Include="views\Menu.h" />
<ClInclude Include="views\MenuHelper.h" />
<ClInclude Include="views\TechnicianMenu.h" />
<ClInclude Include="views\UserInterface.h" />
@@ -64,11 +64,17 @@
<Filter Include="Source Files\Core\Patterns">
<UniqueIdentifier>{8057b93d-51a9-42df-b06e-01ce395f6308}</UniqueIdentifier>
</Filter>
<Filter Include="Header Files\DataStores\SharedMemory">
<UniqueIdentifier>{ec639004-44c6-4bd6-9963-077adde82b5f}</UniqueIdentifier>
<Filter Include="Header Files\Core\SharedMemory">
<UniqueIdentifier>{d9da9793-fe6f-4914-bee3-99d5934da228}</UniqueIdentifier>
</Filter>
<Filter Include="Source Files\DataStores\SharedMemory">
<UniqueIdentifier>{7aa8722e-adfa-466e-8211-de63f3b7892b}</UniqueIdentifier>
<Filter Include="Source Files\Core\SharedMemory">
<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>
</ItemGroup>
<ItemGroup>
@@ -144,8 +150,14 @@
<ClCompile Include="models\ComboPackage.cpp">
<Filter>Source Files\Models</Filter>
</ClCompile>
<ClCompile Include="datastores\sharedmemory\SharedMemory.cpp">
<Filter>Source Files\DataStores\SharedMemory</Filter>
<ClCompile Include="core\sharedmemory\SharedMemory.cpp">
<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>
</ItemGroup>
<ItemGroup>
@@ -254,26 +266,32 @@
<ClInclude Include="views\MenuHelper.h">
<Filter>Header Files\Views</Filter>
</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">
<Filter>Header Files\DataStores</Filter>
</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>
</Project>
@@ -622,3 +622,16 @@ void Controller::shutdown()
auto& dataStore = DataStore::getInstance();
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
#include <windows.h>
#include <string>
#include "AuthenticationManagementService.h"
#include "Enums.h"
@@ -72,4 +73,5 @@ public:
void configureNotifications(bool paymentNotifications, bool serviceNotifications);
bool initialize();
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);
};
@@ -14,6 +14,7 @@ Date: 19-May-2026
#include "FileHelper.h"
#include "ServiceBooking.h"
#include "JobCard.h"
#include "Invoice.h"
/*
Function: DataStore
@@ -324,6 +325,8 @@ Returns:
*/
util::Map<std::string, TrackedRecord<InventoryItem>>& DataStore::getInventoryItems()
{
auto inventoryItems = loadRecords<InventoryItem, SerializedInventoryItem>(m_inventoryItems);
refreshCache(m_inventoryItemCache, inventoryItems);
return m_inventoryItemCache;
}
@@ -448,6 +451,45 @@ Returns:
*/
util::Map<std::string, TrackedRecord<Invoice>>& DataStore::getInvoices()
{
auto& serviceBookings = getServiceBookings();
auto& inventoryItems = getInventoryItems();
util::Map<std::string, TrackedRecord<Invoice>> invoices = loadRecords<Invoice, SerializedInvoice>(m_invoices);
refreshCache(m_invoiceCache, invoices);
for (int iterator = 0; iterator < m_invoiceCache.getSize(); iterator++)
{
auto& trackedInvoice = m_invoiceCache.getValueAt(iterator);
Invoice* invoice = trackedInvoice.data;
if (!invoice)
{
continue;
}
const std::string& currentBookingId = invoice->getBookingId();
int currentBookingIndex = serviceBookings.find(currentBookingId);
if (currentBookingIndex == -1)
{
throw std::runtime_error("Invalid Service Booking Index.");
}
ServiceBooking* currentBooking = serviceBookings.getValueAt(currentBookingIndex).data;
auto& currentInventoryItemIds = invoice->getPartIDs();
util::Map<std::string, InventoryItem*> currentInventoryItems;
for (int iterator = 0; iterator < currentInventoryItemIds.getSize(); iterator++)
{
const std::string& currentItemId = currentInventoryItemIds[iterator];
int currentItemIndex = inventoryItems.find(currentItemId);
if (currentItemIndex == -1)
{
throw std::runtime_error("Invalid Inventory item id.");
}
InventoryItem* currentItem = inventoryItems.getValueAt(currentItemIndex).data;
if (!currentItem)
{
continue;
}
currentInventoryItems[currentItemId] = currentItem;
}
invoice->setBooking(currentBooking);
invoice->setParts(currentInventoryItems);
}
return m_invoiceCache;
}
@@ -583,6 +625,7 @@ Returns:
*/
void DataStore::saveInventoryItems()
{
saveRecords<InventoryItem, SerializedInventoryItem>(m_inventoryItems, m_inventoryItemCache);
}
/*
@@ -621,6 +664,7 @@ Returns:
*/
void DataStore::saveInvoices()
{
saveRecords<Invoice, SerializedInvoice>(m_invoices, m_invoiceCache);
}
/*
@@ -736,5 +780,4 @@ bool DataStore::unlockDataStore()
return false;
}
return ReleaseMutex(m_globalMutex) != 0;
}
}
@@ -8,6 +8,7 @@ Date: 19-May-2026
#include <sstream>
#include <stdexcept>
#include "SerializedRecords.h"
#include "Factory.h"
#include "StringHelper.h"
#include "InventoryItem.h"
@@ -27,7 +28,8 @@ InventoryItem::InventoryItem()
: m_id("IIM" + std::to_string(++m_uid)),
m_quantity(0),
m_status(util::State::ACTIVE),
m_price(0.0) {}
m_price(0.0) {
}
/*
Function: InventoryItem
@@ -45,7 +47,8 @@ InventoryItem::InventoryItem(const std::string& partName, int quantity, double p
m_partName(partName),
m_quantity(quantity),
m_status(util::State::ACTIVE),
m_price(price) {}
m_price(price) {
}
/*
Function: InventoryItem (parameterized constructor with ID)
@@ -206,73 +209,37 @@ void InventoryItem::setState(util::State status)
/*
Function: serialize
Description: Serializes the inventory item into a CSV-formatted string.
Description: Serializes the InventoryItem object into a SerializedInventoryItem record.
Parameters:
- None
Returns:
- std::string: Serialized inventory item record
- SerializedInventoryItem: Serialized representation of the inventory item
*/
std::string InventoryItem::serialize() const
SerializedInventoryItem InventoryItem::serialize() const
{
std::ostringstream serializedInventoryItem;
serializedInventoryItem << m_id << ','
<< m_partName << ','
<< m_quantity << ','
<< m_price << ','
<< util::getStateString(m_status);
return serializedInventoryItem.str();
SerializedInventoryItem serialized = {};
strcpy_s(serialized.id, sizeof(serialized.id), m_id.c_str());
strcpy_s(serialized.partName, sizeof(serialized.partName), m_partName.c_str());
serialized.quantity = m_quantity;
serialized.price = m_price;
serialized.status = m_status;
return serialized;
}
/*
Function: deserialize
Description: Deserializes a CSV-formatted string into an InventoryItem object.
Description: Deserializes a SerializedInventoryItem record into an InventoryItem object.
Parameters:
- record: const std::string&, serialized inventory item record
- serializedInventoryItem: const SerializedInventoryItem&, serialized inventory item record
Returns:
- InventoryItem*: Pointer to the deserialized InventoryItem object
Throws:
- std::runtime_error if data is invalid
*/
InventoryItem* InventoryItem::deserialize(const std::string& record)
InventoryItem* InventoryItem::deserialize(const SerializedInventoryItem& serializedInventoryItem)
{
std::string id, partName;
std::string quantityString, priceString, statusString;
int quantity;
double price;
std::istringstream serializedInventoryItem(record);
getline(serializedInventoryItem, id, ',');
getline(serializedInventoryItem, partName, ',');
getline(serializedInventoryItem, quantityString, ',');
getline(serializedInventoryItem, priceString, ',');
getline(serializedInventoryItem, statusString, ',');
try
{
quantity = std::stoi(quantityString);
price = std::stod(priceString);
}
catch (...)
{
throw std::runtime_error("Invalid inventory item data");
}
util::State status = util::getState(statusString);
return Factory::getObject<InventoryItem>(
id,
partName,
quantity,
price,
status
);
}
/*
Function: getHeaders
Description: Retrieves the CSV headers for inventory item serialization.
Parameters:
- None
Returns:
- std::string: Header string ("ID,PartName,Quantity,Price,Status")
*/
std::string InventoryItem::getHeaders()
{
return "ID,PartName,Quantity,Price,Status";
serializedInventoryItem.id,
serializedInventoryItem.partName,
serializedInventoryItem.quantity,
serializedInventoryItem.price,
serializedInventoryItem.status);
}
@@ -6,11 +6,12 @@ Author: Trenser
Date: 19-May-2026
*/
#pragma once
#include <string>
#include "Enums.h"
struct SerializedInventoryItem;
class InventoryItem
{
private:
@@ -34,7 +35,6 @@ public:
void setQuantity(int quantity);
void setPrice(double price);
void setState(util::State status);
std::string serialize() const;
static InventoryItem* deserialize(const std::string&);
static std::string getHeaders();
SerializedInventoryItem serialize() const;
static InventoryItem* deserialize(const SerializedInventoryItem&);
};
@@ -9,6 +9,7 @@ Date: 19-May-2026
#include <sstream>
#include <stdexcept>
#include "SerializedRecords.h"
#include "Invoice.h"
#include "Factory.h"
#include "InventoryItem.h"
@@ -34,7 +35,8 @@ Invoice::Invoice()
m_discountPercentage(0.0),
m_totalAmount(0.0),
m_paymentMethod(util::PaymentMode()),
m_status(util::PaymentStatus()) {}
m_status(util::PaymentStatus()) {
}
/*
Function: Invoice
@@ -57,16 +59,16 @@ Returns:
Invoice::Invoice(
const std::string& bookingId,
ServiceBooking* booking,
const util::Timestamp& invoiceDate,
double laborCost,
const util::Timestamp& invoiceDate,
double laborCost,
const util::Map<std::string, InventoryItem*>& parts,
double partsCost,
double discountPercentage,
double totalAmount,
const util::Timestamp& paymentDate,
util::PaymentMode paymentMethod,
double discountPercentage,
double totalAmount,
const util::Timestamp& paymentDate,
util::PaymentMode paymentMethod,
util::PaymentStatus status
)
)
: m_id("INV" + std::to_string(++m_uid)),
m_bookingId(bookingId),
m_booking(booking),
@@ -78,7 +80,7 @@ Invoice::Invoice(
m_totalAmount(totalAmount),
m_paymentDate(paymentDate),
m_paymentMethod(paymentMethod),
m_status(status)
m_status(status)
{
int numberOfParts = m_parts.getSize();
auto partPointers = m_parts.getValues();
@@ -473,100 +475,50 @@ static util::Vector<std::string> getPartIDsAsVector(const std::string& partIDsSt
/*
Function: serialize
Description: Serializes the invoice into a CSV-formatted string.
Description: Serializes the Invoice object into a SerializedInvoice record.
Parameters:
- None
Returns:
- std::string: Serialized invoice record
- SerializedInvoice: Serialized representation of the invoice
*/
std::string Invoice::serialize() const
SerializedInvoice Invoice::serialize() const
{
std::ostringstream serializedInvoice;
serializedInvoice << m_id << ','
<< m_bookingId << ','
<< m_invoiceDate.toString() << ','
<< m_laborCost << ','
<< getPartIDsAsString(m_partIDs) << ','
<< m_partsCost << ','
<< m_discountPercentage << ','
<< m_totalAmount << ','
<< m_paymentDate.toString() << ','
<< util::getPaymentModeString(m_paymentMethod) << ','
<< util::getPaymentStatusString(m_status);
return serializedInvoice.str();
SerializedInvoice serialized = {};
strcpy_s(serialized.id, sizeof(serialized.id), m_id.c_str());
strcpy_s(serialized.bookingId, sizeof(serialized.bookingId), m_bookingId.c_str());
strcpy_s(serialized.partIDs, sizeof(serialized.partIDs), getPartIDsAsString(m_partIDs).c_str());
serialized.invoiceDate = m_invoiceDate;
serialized.laborCost = m_laborCost;
serialized.partsCost = m_partsCost;
serialized.discountPercentage = m_discountPercentage;
serialized.totalAmount = m_totalAmount;
serialized.paymentDate = m_paymentDate;
serialized.paymentMethod = m_paymentMethod;
serialized.status = m_status;
return serialized;
}
/*
Function: deserialize
Description: Deserializes a CSV-formatted string into an Invoice object.
Description: Deserializes a SerializedInvoice record into an Invoice object.
Parameters:
- record: const std::string&, serialized invoice record
- serializedInvoice: const SerializedInvoice&, serialized invoice record
Returns:
- Invoice*: Pointer to the deserialized Invoice object
Throws:
- std::runtime_error if data is invalid
*/
Invoice* Invoice::deserialize(const std::string& record)
Invoice* Invoice::deserialize(const SerializedInvoice& serializedInvoice)
{
std::string id, bookingId;
std::string invoiceDateString, laborCostString, partIDsString;
std::string partsCostString, discountPercentageString, totalAmountString;
std::string paymentDateString, paymentMethodString, statusString;
double laborCost, partsCost, discountPercentage, totalAmount;
std::istringstream serializedInvoice(record);
getline(serializedInvoice, id, ',');
getline(serializedInvoice, bookingId, ',');
getline(serializedInvoice, invoiceDateString, ',');
getline(serializedInvoice, laborCostString, ',');
getline(serializedInvoice, partIDsString, ',');
getline(serializedInvoice, partsCostString, ',');
getline(serializedInvoice, discountPercentageString, ',');
getline(serializedInvoice, totalAmountString, ',');
getline(serializedInvoice, paymentDateString, ',');
getline(serializedInvoice, paymentMethodString, ',');
getline(serializedInvoice, statusString, ',');
util::Timestamp invoiceDate;
util::Timestamp paymentDate;
try
{
invoiceDate = util::Timestamp::fromString(invoiceDateString);
paymentDate = util::Timestamp::fromString(paymentDateString);
laborCost = std::stod(laborCostString);
partsCost = std::stod(partsCostString);
discountPercentage = std::stod(discountPercentageString);
totalAmount = std::stod(totalAmountString);
}
catch (...)
{
throw std::runtime_error("Invalid invoice data");
}
util::Vector<std::string> partIDs = getPartIDsAsVector(partIDsString);
util::PaymentMode paymentMethod = util::getPaymentMode(paymentMethodString);
util::PaymentStatus status = util::getPaymentStatus(statusString);
util::Vector<std::string> partIDs = getPartIDsAsVector(serializedInvoice.partIDs);
return Factory::getObject<Invoice>(
id,
bookingId,
invoiceDate,
serializedInvoice.id,
serializedInvoice.bookingId,
serializedInvoice.invoiceDate,
partIDs,
laborCost,
partsCost,
discountPercentage,
totalAmount,
paymentDate,
paymentMethod,
status
);
}
/*
Function: getHeaders
Description: Retrieves the CSV headers for invoice serialization.
Parameters:
- None
Returns:
- std::string: Header string ("ID,BookingID,InvoiceDate,LaborCost,PartIDs,PartsCost,DiscountPercentage,TotalAmount,PaymentDate,PaymentMethod,Status")
*/
std::string Invoice::getHeaders()
{
return "ID,BookingID,InvoiceDate,LaborCost,PartIDs,PartsCost,DiscountPercentage,TotalAmount,PaymentDate,PaymentMethod,Status";
serializedInvoice.laborCost,
serializedInvoice.partsCost,
serializedInvoice.discountPercentage,
serializedInvoice.totalAmount,
serializedInvoice.paymentDate,
serializedInvoice.paymentMethod,
serializedInvoice.status);
}
@@ -6,7 +6,6 @@ Author: Trenser
Date: 19-May-2026
*/
#pragma once
#include <string>
#include "Map.h"
@@ -16,6 +15,7 @@ Date: 19-May-2026
class ServiceBooking;
class InventoryItem;
struct SerializedInvoice;
class Invoice
{
@@ -39,14 +39,14 @@ public:
Invoice(
const std::string& bookingId,
ServiceBooking* booking,
const util::Timestamp& invoiceDate,
double laborCost,
const util::Map<std::string,InventoryItem*>& parts,
const util::Timestamp& invoiceDate,
double laborCost,
const util::Map<std::string, InventoryItem*>& parts,
double partsCost,
double discountPercentage,
double totalAmount,
const util::Timestamp& paymentDate,
util::PaymentMode paymentMethod,
double discountPercentage,
double totalAmount,
const util::Timestamp& paymentDate,
util::PaymentMode paymentMethod,
util::PaymentStatus status
);
Invoice(
@@ -87,7 +87,6 @@ public:
void setPaymentDate(const util::Timestamp& paymentDate);
void setPaymentMethod(util::PaymentMode paymentMethod);
void setStatus(util::PaymentStatus status);
std::string serialize() const;
static Invoice* deserialize(const std::string&);
static std::string getHeaders();
SerializedInvoice serialize() const;
static Invoice* deserialize(const SerializedInvoice&);
};
@@ -14,6 +14,9 @@ Date:19-May-2026
#include "DataStoreLockGuard.h"
User* AuthenticationManagementService::m_authenticatedUser = nullptr;
EventManager AuthenticationManagementService::m_eventManager;
HANDLE AuthenticationManagementService::m_accountDisabledEvent = NULL;
HANDLE AuthenticationManagementService::m_notificationsAvailableEvent = NULL;
/*
Function: login
@@ -37,6 +40,22 @@ bool AuthenticationManagementService::login(const std::string& username, const s
if (password == user->getPassword())
{
m_authenticatedUser = user;
m_eventManager.initialize(
user->getId(),
[]()
{
if (m_accountDisabledEvent)
{
SetEvent(m_accountDisabledEvent);
}
},
[]()
{
if (m_notificationsAvailableEvent)
{
SetEvent(m_notificationsAvailableEvent);
}
});
return true;
}
return false;
@@ -65,7 +84,10 @@ Return type: void
*/
void AuthenticationManagementService::logout()
{
m_eventManager.shutdown();
m_authenticatedUser = nullptr;
m_accountDisabledEvent = NULL;
m_notificationsAvailableEvent = NULL;
}
/*
@@ -92,3 +114,17 @@ void AuthenticationManagementService::changePassword(const std::string& newPassw
trackedUsersMap.getValueAt(index).state = RecordState::MODIFIED;
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
#include <string>
#include <windows.h>
#include "EventManager.h"
#include "DataStore.h"
class User;
@@ -17,6 +19,9 @@ class AuthenticationManagementService
{
private:
static User* m_authenticatedUser;
static EventManager m_eventManager;
static HANDLE m_accountDisabledEvent;
static HANDLE m_notificationsAvailableEvent;
DataStore& m_dataStore;
public:
AuthenticationManagementService() : m_dataStore(DataStore::getInstance()) {}
@@ -24,4 +29,5 @@ public:
void logout();
void changePassword(const std::string& newPassword);
User* getAuthenticatedUser();
void registerEvents(HANDLE accountDisabledEvent, HANDLE notificationAvailableEvent);
};
@@ -19,6 +19,7 @@ Date: 22-May-2026
#include "Utility.h"
#include "Vector.h"
#include "DataStoreLockGuard.h"
#include "EventManager.h"
util::Map<std::string, User*> InventoryManagementService::m_observers{};
@@ -57,18 +58,19 @@ Returns:
*/
void InventoryManagementService::sendLowStockAlerts()
{
auto& inventoryItems = m_dataStore.getInventoryItems();
if (inventoryItems.isEmpty())
DataStoreLockGuard lock(m_dataStore);
auto& trackedInventoryItemsMap = m_dataStore.getInventoryItems();
auto& trackedUserMap = m_dataStore.getUsers();
if (trackedInventoryItemsMap.isEmpty())
{
return;
}
int inventoryItemsSize = inventoryItems.getSize();
auto& usersMap = m_dataStore.getUsers();
int usersMapSize = usersMap.getSize();
int inventoryItemsSize = trackedInventoryItemsMap.getSize();
int usersMapSize = trackedUserMap.getSize();
util::Vector<User*> adminUsers;
for (int index = 0; index < usersMapSize; index++)
{
User* user = usersMap.getValueAt(index);
User* user = trackedUserMap.getValueAt(index).data;
if (user->getUserType() == util::UserType::ADMIN)
{
adminUsers.push_back(user);
@@ -81,7 +83,7 @@ void InventoryManagementService::sendLowStockAlerts()
}
for (int index = 0; index < inventoryItemsSize; index++)
{
InventoryItem* inventoryItem = inventoryItems.getValueAt(index);
InventoryItem* inventoryItem = trackedInventoryItemsMap.getValueAt(index).data;
if (inventoryItem && inventoryItem->getQuantity() < config::threshold::INVENTORY_LOW_STOCK_THRESHOLD)
{
sendLowStockAlertsToAdmins(*this, inventoryItem, adminUsers);
@@ -100,8 +102,11 @@ Return type: void
*/
void InventoryManagementService::addInventoryItem(const std::string& partName, int quantity, double price)
{
DataStoreLockGuard lock(m_dataStore);
auto& trackedInventoryItemMap = m_dataStore.getInventoryItems();
InventoryItem* newItem = Factory::getObject<InventoryItem>(partName, quantity, price);
m_dataStore.getInventoryItems().insert(newItem->getId(), newItem);
trackedInventoryItemMap.insert(newItem->getId(), util::createNewRecord(newItem));
m_dataStore.saveInventoryItems();
}
/*
@@ -113,16 +118,22 @@ Return type: void
*/
void InventoryManagementService::addInventoryItemStock(const std::string& selectedItemId, int quantity)
{
int index = m_dataStore.getInventoryItems().find(selectedItemId);
if (index != -1)
{
InventoryItem* item = m_dataStore.getInventoryItems().getValueAt(index);
if (item != nullptr)
{
int totalQuantity = item->getQuantity() + quantity;
item->setQuantity(totalQuantity);
}
}
DataStoreLockGuard lock(m_dataStore);
auto& trackedInventoryItemMap = m_dataStore.getInventoryItems();
int index = trackedInventoryItemMap.find(selectedItemId);
if (index == -1)
{
throw std::runtime_error("Inventory update failed: Item ID '" + selectedItemId + "' not found.");
}
InventoryItem* item = trackedInventoryItemMap.getValueAt(index).data;
if (item == nullptr)
{
throw std::runtime_error("Inventory update failed. Item does not exist.\n");
}
int totalQuantity = item->getQuantity() + quantity;
item->setQuantity(totalQuantity);
trackedInventoryItemMap.getValueAt(index).state = RecordState::MODIFIED;
m_dataStore.saveInventoryItems();
}
/*
@@ -133,7 +144,10 @@ Return type: util::Map<std::string, InventoryItem*>
*/
util::Map<std::string, InventoryItem*> InventoryManagementService::getInventoryItems()
{
return m_dataStore.getInventoryItems();
DataStoreLockGuard lock(m_dataStore);
auto& trackedInventoryItemMap = m_dataStore.getInventoryItems();
auto inventoryMap = util::getObjects(trackedInventoryItemMap);
return inventoryMap;
}
/*
@@ -144,15 +158,21 @@ Return type: void
*/
void InventoryManagementService::removeInventoryItem(const std::string& inventoryItemID)
{
int index = m_dataStore.getInventoryItems().find(inventoryItemID);
if (index != -1)
{
InventoryItem* item = m_dataStore.getInventoryItems().getValueAt(index);
if (item != nullptr)
{
item->setState(util::State::INACTIVE);
}
}
DataStoreLockGuard lock(m_dataStore);
auto& trackedInventoryItemMap = m_dataStore.getInventoryItems();
int index = trackedInventoryItemMap.find(inventoryItemID);
if (index == -1)
{
throw std::runtime_error("Inventory removal failed: Item ID '" + inventoryItemID + "' not found.");
}
InventoryItem* item = trackedInventoryItemMap.getValueAt(index).data;
if (item == nullptr)
{
throw std::runtime_error("Inventory removal failed: Item ID does not exist.");
}
item->setState(util::State::INACTIVE);
trackedInventoryItemMap.getValueAt(index).state = RecordState::MODIFIED;
m_dataStore.saveInventoryItems();
}
/*
@@ -163,12 +183,19 @@ Return type: InventoryItem*
*/
InventoryItem* InventoryManagementService::getInventoryItem(const std::string& inventoryItemID)
{
int index = m_dataStore.getInventoryItems().find(inventoryItemID);
if (index != -1)
DataStoreLockGuard lock(m_dataStore);
auto& trackedInventoryItemMap = m_dataStore.getInventoryItems();
int index = trackedInventoryItemMap.find(inventoryItemID);
if (index == -1)
{
return m_dataStore.getInventoryItems().getValueAt(index);
return nullptr;
}
return nullptr;
InventoryItem* inventoryItem = trackedInventoryItemMap.getValueAt(index).data;
if (inventoryItem == nullptr)
{
throw std::runtime_error("Item ID does not exist.");
}
return inventoryItem;
}
/*
@@ -255,5 +282,6 @@ void InventoryManagementService::sendNotification(User* user, const std::string&
auto& trackedNotificationsMap = m_dataStore.getNotifications();
trackedNotificationsMap.insert(notification->getId(), util::createNewRecord(notification));
m_dataStore.saveNotifications();
EventManager::sendNotificationAvailableEvent(user->getId());
}
@@ -15,12 +15,14 @@ Date: 20-May-2026
#include "Invoice.h"
#include "JobCard.h"
#include "PaymentManagementService.h"
#include "DataStoreLockGuard.h"
#include "Service.h"
#include "ServiceBooking.h"
#include "Timestamp.h"
#include "User.h"
#include "Utility.h"
#include "DataStoreLockGuard.h"
#include "EventManager.h"
util::Map<std::string, User*> PaymentManagementService::m_observers{};
@@ -108,6 +110,7 @@ void PaymentManagementService::sendNotification(User* user, const std::string& t
auto& trackedNotificationsMap = m_dataStore.getNotifications();
trackedNotificationsMap.insert(notification->getId(), util::createNewRecord(notification));
m_dataStore.saveNotifications();
EventManager::sendNotificationAvailableEvent(user->getId());
}
/*
@@ -121,11 +124,12 @@ Returns:
*/
void PaymentManagementService::sendPaymentReminders()
{
auto& invoicesMap = m_dataStore.getInvoices();
int invoicesMapSize = invoicesMap.getSize();
DataStoreLockGuard lock(m_dataStore);
auto& trackedInvoicesMap = m_dataStore.getInvoices();
int invoicesMapSize = trackedInvoicesMap.getSize();
for (int index = 0; index < invoicesMapSize; index++)
{
const Invoice* invoice = invoicesMap.getValueAt(index);
const Invoice* invoice = trackedInvoicesMap.getValueAt(index).data;
if (invoice && invoice->getStatus() == util::PaymentStatus::PENDING)
{
util::Timestamp invoiceCreationTimestamp = invoice->getInvoiceDate();
@@ -181,6 +185,7 @@ Throws:
*/
void PaymentManagementService::generateInvoice(ServiceBooking* booking)
{
DataStoreLockGuard lock(m_dataStore);
if (!booking)
{
throw std::runtime_error("Invoice generation failed: booking is null.");
@@ -190,10 +195,10 @@ void PaymentManagementService::generateInvoice(ServiceBooking* booking)
std::string bookingID = booking->getId();
util::Map<std::string, Service*> servicesInTheBookedService = booking->getServices();
util::Map<std::string, InventoryItem*> completeInventoryItemMapOfBooking;
util::Map<std::string, JobCard*> currentJobCards = m_dataStore.getJobCards();
for (int iterator = 0; iterator < currentJobCards.getSize(); iterator++)
auto& currentTrackedJobCards = m_dataStore.getJobCards();
for (int iterator = 0; iterator < currentTrackedJobCards.getSize(); iterator++)
{
JobCard* currentJobCard = currentJobCards.getValueAt(iterator);
JobCard* currentJobCard = currentTrackedJobCards.getValueAt(iterator).data;
util::ServiceJobStatus currentJobCardStatus = currentJobCard->getStatus();
if (currentJobCard->getBookingId() == bookingID && currentJobCardStatus != util::ServiceJobStatus::CANCELLED && currentJobCardStatus != util::ServiceJobStatus::COMPLETED)
{
@@ -213,8 +218,9 @@ void PaymentManagementService::generateInvoice(ServiceBooking* booking)
totalServiceCost = totalLaborCost + totalPartsCost;
totalServiceCost -= (totalServiceCost * (discountPercentage / 100));
Invoice* invoice = Factory::getObject<Invoice>(bookingID, booking, util::Timestamp(), totalLaborCost, completeInventoryItemMapOfBooking, totalPartsCost, discountPercentage, totalServiceCost, util::Timestamp(), util::PaymentMode::NOTSET, util::PaymentStatus::PENDING);
util::Map<std::string, Invoice*>& currentInvoices = m_dataStore.getInvoices();
currentInvoices.insert(invoice->getId(), invoice);
auto& currentTrackedInvoices = m_dataStore.getInvoices();
currentTrackedInvoices.insert(invoice->getId(), util::createNewRecord(invoice));
m_dataStore.saveInvoices();
}
/*
@@ -227,11 +233,12 @@ Returns:
*/
util::Map<std::string, Invoice*> PaymentManagementService::getInvoices(const std::string& customerID)
{
util::Map<std::string, Invoice*>& currentInvoices = m_dataStore.getInvoices();
DataStoreLockGuard lock(m_dataStore);
auto& currentTrackedInvoices = m_dataStore.getInvoices();
util::Map<std::string, Invoice*> currentUserInvoices;
for (int iterator = 0; iterator < currentInvoices.getSize(); iterator++)
for (int iterator = 0; iterator < currentTrackedInvoices.getSize(); iterator++)
{
Invoice* currentInvoice = currentInvoices.getValueAt(iterator);
Invoice* currentInvoice = currentTrackedInvoices.getValueAt(iterator).data;
if (currentInvoice->getBooking()->getCustomerId() == customerID)
{
currentUserInvoices.insert(currentInvoice->getId(), currentInvoice);
@@ -254,11 +261,13 @@ Throws:
*/
void PaymentManagementService::completePayment(const std::string& invoiceID, util::PaymentMode paymentMode)
{
auto& currentInvoices = m_dataStore.getInvoices();
int invoiceIndex = currentInvoices.find(invoiceID);
DataStoreLockGuard lock(m_dataStore);
auto& currentTrackedInvoices = m_dataStore.getInvoices();
int invoiceIndex = currentTrackedInvoices.find(invoiceID);
if (invoiceIndex != -1)
{
Invoice* invoice = currentInvoices.getValueAt(invoiceIndex);
auto& trackedInvoice = currentTrackedInvoices.getValueAt(invoiceIndex);
Invoice* invoice = trackedInvoice.data;
if (invoice && invoice->getStatus() != util::PaymentStatus::PAID)
{
User* currentUser = invoice->getBooking()->getCustomer();
@@ -269,12 +278,14 @@ void PaymentManagementService::completePayment(const std::string& invoiceID, uti
title = "Payment successful";
message = "Payment successful for Invoice ID " + invoiceID;
sendNotification(currentUser, title, message);
trackedInvoice.state = RecordState::MODIFIED;
}
}
else
{
throw std::runtime_error("Payment failed: invalid invoice ID.");
}
m_dataStore.saveInvoices();
}
/*
@@ -283,11 +294,14 @@ Description: Provides access to all invoices stored in the data store.
Parameters:
- none
Returns:
- util::Map<std::string, Invoice*>&: Map of invoice IDs to invoice objects
- util::Map<std::string, Invoice*>: Map of invoice IDs to invoice objects
*/
util::Map<std::string, Invoice*>& PaymentManagementService::getAllInvoices()
util::Map<std::string, Invoice*> PaymentManagementService::getAllInvoices()
{
return m_dataStore.getInvoices();
DataStoreLockGuard lock(m_dataStore);
util::Map<std::string, Invoice*> invoices;
invoices = util::getObjects(m_dataStore.getInvoices());
return invoices;
}
/*
@@ -303,20 +317,24 @@ Throws:
*/
void PaymentManagementService::confirmPayment(const std::string& invoiceID)
{
auto& currentInvoices = m_dataStore.getInvoices();
int invoiceIndex = currentInvoices.find(invoiceID);
DataStoreLockGuard lock(m_dataStore);
auto& currentTrackedInvoices = m_dataStore.getInvoices();
int invoiceIndex = currentTrackedInvoices.find(invoiceID);
if (invoiceIndex == -1)
{
throw std::runtime_error("Payment confirmation failed: invalid invoice ID.");
}
Invoice* invoice = currentInvoices.getValueAt(invoiceIndex);
auto& trackedInvoice = currentTrackedInvoices.getValueAt(invoiceIndex);
Invoice* invoice = trackedInvoice.data;
if (!invoice || invoice->getStatus() != util::PaymentStatus::PAID)
{
throw std::runtime_error("Payment confirmation failed: invoice is not awaiting confirmation.");
}
User* currentUser = invoice->getBooking()->getCustomer();
invoice->setStatus(util::PaymentStatus::COMPLETED);
trackedInvoice.state = RecordState::MODIFIED;
std::string title = "Payment Confirmed";
std::string message = "Payment Confirmed for Invoice ID " + invoiceID;
sendNotification(currentUser, title, message);
m_dataStore.saveInvoices();
}
@@ -27,7 +27,7 @@ public:
void generateInvoice(ServiceBooking* booking);
util::Map<std::string, Invoice*> getInvoices(const std::string& customerID);
void completePayment(const std::string& invoiceID, util::PaymentMode paymentMode);
util::Map<std::string, Invoice*>& getAllInvoices();
util::Map<std::string, Invoice*> getAllInvoices();
void confirmPayment(const std::string& invoiceID);
void sendPaymentReminders();
void sendNotification(User* user, const std::string& title, const std::string& message) override;
@@ -27,6 +27,7 @@ Date:19-May-2026
#include "DataStoreLockGuard.h"
#include "Utility.h"
#include "DataStoreLockGuard.h"
#include "EventManager.h"
/*
Function: purchaseService
@@ -199,6 +200,7 @@ void ServiceManagementService::sendNotification(User* user, const std::string& t
auto& trackedNotificationsMap = m_dataStore.getNotifications();
trackedNotificationsMap.insert(notification->getId(), util::createNewRecord(notification));
m_dataStore.saveNotifications();
EventManager::sendNotificationAvailableEvent(user->getId());
}
/*
@@ -22,6 +22,7 @@ Date:19-May-2026
#include "Utility.h"
#include "TrackedRecord.h"
#include "DataStoreLockGuard.h"
#include "EventManager.h"
/*
Function: ensureAdminExists
@@ -267,30 +268,38 @@ void UserManagementService::removeUser(const std::string& userID)
InventoryManagementService inventoryManagementService;
PaymentManagementService paymentManagementService;
ServiceManagementService serviceManagementService;
DataStoreLockGuard lock(m_dataStore);
auto& trackedUsersMap = m_dataStore.getUsers();
int index = trackedUsersMap.find(userID);
if (index != -1)
std::string removedUserID;
{
User* user = trackedUsersMap.getValueAt(index).data;
if (user != nullptr)
DataStoreLockGuard lock(m_dataStore);
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()
{
startEventListener();
while (true)
{
try
{
if (!m_isMenuActive)
{
logout();
break;
}
int choice;
util::clear();
std::cout << "Admin Menu"
@@ -68,6 +74,7 @@ void AdminMenu::showMenu()
util::pressEnter();
}
}
stopEventListener();
}
/*
@@ -78,6 +85,11 @@ Return type: bool - true if menu continues, false if logout
*/
bool AdminMenu::handleOperation(int choice)
{
if (!m_isMenuActive)
{
logout();
return false;
}
switch (choice)
{
case 1:
@@ -141,6 +153,19 @@ bool AdminMenu::handleOperation(int choice)
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
Description: Logs out the currently authenticated admin user.
@@ -9,12 +9,13 @@ Date:19-May-2026
#pragma once
#include "Controller.h"
#include "Menu.h"
class AdminMenu
class AdminMenu : public Menu
{
private:
Controller m_controller;
bool handleOperation(int choice);
void handleNotificationEvent() override;
public:
void showMenu();
void logout();
@@ -32,10 +32,17 @@ Return type: void
*/
void CustomerMenu::showMenu()
{
startEventListener();
while (true)
{
try
{
if (!m_isMenuActive)
{
logout();
break;
}
int choice;
util::clear();
std::cout << "Customer Menu"
@@ -62,6 +69,7 @@ void CustomerMenu::showMenu()
util::pressEnter();
}
}
stopEventListener();
}
/*
@@ -72,6 +80,11 @@ Return type: bool - true if menu continues, false if logout
*/
bool CustomerMenu::handleOperation(int choice)
{
if (!m_isMenuActive)
{
logout();
return false;
}
switch (choice)
{
case 1:
@@ -111,6 +124,19 @@ bool CustomerMenu::handleOperation(int choice)
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
Description: Logs out the currently authenticated customer user.
@@ -9,13 +9,14 @@ Date:19-May-2026
*/
#pragma once
#include "Menu.h"
#include "Controller.h"
class CustomerMenu
class CustomerMenu : public Menu
{
private:
Controller m_controller;
bool handleOperation(int choice);
void handleNotificationEvent();
public:
void showMenu();
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";
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()
{
startEventListener();
while (true)
{
try
{
if (!m_isMenuActive)
{
logout();
break;
}
int choice;
util::clear();
std::cout << "Technician Menu"
@@ -52,6 +58,7 @@ void TechnicianMenu::showMenu()
util::pressEnter();
}
}
stopEventListener();
}
/*
@@ -62,6 +69,11 @@ Return type: bool - true if menu continues, false if logout
*/
bool TechnicianMenu::handleOperation(int choice)
{
if (!m_isMenuActive)
{
logout();
return false;
}
switch (choice)
{
case 1:
@@ -86,6 +98,19 @@ bool TechnicianMenu::handleOperation(int choice)
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
Description: Displays all Jobs assigned to a Technician
@@ -9,12 +9,13 @@ Date:19-May-2026
#pragma once
#include "Controller.h"
#include "Menu.h"
class TechnicianMenu
class TechnicianMenu : public Menu
{
private:
Controller m_controller;
bool handleOperation(int choice);
void handleNotificationEvent();
public:
void showMenu();
void displayJobs();