Compare commits

..

1 Commits

Author SHA1 Message Date
joelthomastrenser 8a3ec278ce Merged PR 1159: Vehicle Service System v1.1.0.0
**New Features**

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

- Introduced a new PAID payment status.

- Implemented invoice confirmation flow from PAID → COMPLETED.

- Added invoice filtering based on payment status.

- Added admin menu option to confirm customer payments.

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

- Added support for job status transitions:

- STARTED → IN_PROGRESS

- IN_PROGRESS → COMPLETED

- Added invoice generation and customer notification upon booking completion.

- Improved job visibility by displaying current job status.

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

- Standardized status labels using "In Progress".

- Improved prompts, headings, and job selection messages.

- Enhanced readability of job listings.

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

- Prevented creation of duplicate usernames across all user states.

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

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

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

Related work items: #1797, #1798, #1807, #1808, #1809
2026-06-01 18:57:46 +05:30
62 changed files with 1890 additions and 3503 deletions
+2 -2
View File
@@ -427,5 +427,5 @@ FodyWeavers.xsd
*.msm
*.msp
# DAT Files
*.dat
# CSV Files
*.csv
@@ -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)core\sharedmemory;$(ProjectDir)core\events;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(ProjectDir)models;$(ProjectDir)controllers;$(ProjectDir)factories;$(ProjectDir)views;$(ProjectDir)services;$(ProjectDir)utilities;$(ProjectDir)core\patterns;$(ProjectDir)datastores;%(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)core\sharedmemory;$(ProjectDir)core\events;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(ProjectDir)models;$(ProjectDir)controllers;$(ProjectDir)factories;$(ProjectDir)views;$(ProjectDir)services;$(ProjectDir)utilities;$(ProjectDir)core\patterns;$(ProjectDir)datastores;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
@@ -126,10 +126,8 @@
</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="models\ComboPackage.cpp" />
<ClCompile Include="models\InventoryItem.cpp" />
@@ -141,6 +139,7 @@
<ClCompile Include="models\User.cpp" />
<ClCompile Include="services\AuthenticationManagementService.cpp" />
<ClCompile Include="services\InventoryManagementService.cpp" />
<ClCompile Include="services\NotificationManagementService.cpp" />
<ClCompile Include="services\PaymentManagementService.cpp" />
<ClCompile Include="services\ServiceManagementService.cpp" />
<ClCompile Include="services\UserManagementService.cpp" />
@@ -149,23 +148,14 @@
<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="factories\Factory.h" />
<ClInclude Include="models\ComboPackage.h" />
<ClInclude Include="models\InventoryItem.h" />
@@ -184,6 +174,7 @@
<ClInclude Include="utilities\Config.h" />
<ClInclude Include="utilities\Enums.h" />
<ClInclude Include="utilities\FileHelper.h" />
<ClInclude Include="utilities\FileManager.h" />
<ClInclude Include="utilities\InputHelper.h" />
<ClInclude Include="utilities\Map.h" />
<ClInclude Include="utilities\OutputHelper.h" />
@@ -194,7 +185,6 @@
<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,18 +64,6 @@
<Filter Include="Source Files\Core\Patterns">
<UniqueIdentifier>{8057b93d-51a9-42df-b06e-01ce395f6308}</UniqueIdentifier>
</Filter>
<Filter Include="Header Files\Core\SharedMemory">
<UniqueIdentifier>{d9da9793-fe6f-4914-bee3-99d5934da228}</UniqueIdentifier>
</Filter>
<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>
<ClCompile Include="Trenser.VehicleServiceSystem.cpp">
@@ -120,6 +108,9 @@
<ClCompile Include="datastores\DataStore.cpp">
<Filter>Source Files\DataStores</Filter>
</ClCompile>
<ClCompile Include="services\NotificationManagementService.cpp">
<Filter>Source Files\Services</Filter>
</ClCompile>
<ClCompile Include="core\patterns\Observer.cpp">
<Filter>Source Files\Core\Patterns</Filter>
</ClCompile>
@@ -150,15 +141,6 @@
<ClCompile Include="models\ComboPackage.cpp">
<Filter>Source Files\Models</Filter>
</ClCompile>
<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>
<ClInclude Include="utilities\InputHelper.h">
@@ -254,6 +236,9 @@
<ClInclude Include="utilities\Config.h">
<Filter>Header Files\Utilities</Filter>
</ClInclude>
<ClInclude Include="utilities\FileManager.h">
<Filter>Header Files\Utilities</Filter>
</ClInclude>
<ClInclude Include="utilities\StringHelper.h">
<Filter>Header Files\Utilities</Filter>
</ClInclude>
@@ -266,32 +251,5 @@
<ClInclude Include="views\MenuHelper.h">
<Filter>Header Files\Views</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>
@@ -6,7 +6,6 @@ Description: Implementation file containing the method definitions of the
Author: Trenser
Date:19-May-2026
*/
#include "ComboPackage.h"
#include "Controller.h"
#include "Enums.h"
@@ -234,17 +233,6 @@ void Controller::removeInventoryItem(const std::string& inventoryItemID)
m_inventoryManagementService.removeInventoryItem(inventoryItemID);
}
/*
Function: removeServiceBooking
Description: Removes a service booking from the service management system by its booking ID.
Parameter: const std::string& bookingID - ID of the service booking
Return type: void
*/
void Controller::removeServiceBooking(const std::string& bookingID)
{
m_serviceManagementService.removeServiceBooking(bookingID);
}
/*
Function: addInventoryItemStock
Description: Adds stock to an existing inventory item in the inventory management service.
@@ -276,7 +264,6 @@ util::Map<std::string, const ServiceBooking*> Controller::getServiceBookings()
return readOnlyServiceBookings;
}
/*
Function: getServiceBookingsByUser
Description: Retrieves all service bookings for a specific user.
@@ -600,50 +587,63 @@ void Controller::configureNotifications(bool paymentNotifications, bool serviceN
}
/*
Function: initialize
Description: Initializes the system and run system checks to ensure critical configurations, such as verifying admin existence.
Parameters:
- None
Returns:
- bool
*/
bool Controller::initialize()
{
auto& dataStore = DataStore::getInstance();
if (!dataStore.initialize())
{
return false;
}
m_userManagementService.ensureAdminExists();
m_inventoryManagementService.sendLowStockAlerts();
m_paymentManagementService.sendPaymentReminders();
return true;
}
/*
Function: shutdown
Description: Shutdown the system, and do necessary cleanups
Function: loadSystemData
Description: Loads all system data from persistent storage into memory.
Invokes the respective management services to load users, inventory items, services,
combo packages, service bookings, job cards, invoices, and observers.
Parameters:
- None
Returns:
- void
*/
void Controller::shutdown()
void Controller::loadSystemData()
{
auto& dataStore = DataStore::getInstance();
dataStore.shutdown();
m_userManagementService.loadUsers();
m_inventoryManagementService.loadInventoryItems();
m_serviceManagementService.loadServices();
m_serviceManagementService.loadComboPackages();
m_serviceManagementService.loadServiceBookings();
m_serviceManagementService.loadJobCards();
m_paymentManagementService.loadInvoices();
m_serviceManagementService.loadObservers();
m_paymentManagementService.loadObservers();
m_inventoryManagementService.loadObservers();
}
/*
Function: registerEvents
Description: Registers menu event handles with the authentication
service.
Parameter: HANDLE accountDisabledEvent - account disabled event handle
HANDLE notificationAvailableEvent - notification event handle
Function: saveSystemData
Description: Saves all system data from memory back to persistent storage.
Invokes the respective management services to save users, inventory items, services,
combo packages, service bookings, job cards, invoices, and observers.
Parameters:
- None
Returns:
- void
*/
void Controller::saveSystemData()
{
m_userManagementService.saveUsers();
m_inventoryManagementService.saveInventoryItems();
m_serviceManagementService.saveServices();
m_serviceManagementService.saveComboPackages();
m_serviceManagementService.saveServiceBookings();
m_serviceManagementService.saveJobCards();
m_paymentManagementService.saveInvoices();
m_serviceManagementService.saveObservers();
m_paymentManagementService.saveObservers();
m_inventoryManagementService.saveObservers();
}
/*
Function: runSystemChecks
Description: Runs system checks to ensure critical configurations, such as verifying admin existence.
Parameter: None
Return type: void
*/
void Controller::registerEvents(HANDLE accountDisabledEvent, HANDLE notificationAvailableEvent)
void Controller::runSystemChecks()
{
m_authenticationManagementService.registerEvents(accountDisabledEvent, notificationAvailableEvent);
m_userManagementService.ensureAdminExists();
m_inventoryManagementService.sendLowStockAlerts();
m_paymentManagementService.sendPaymentReminders();
}
@@ -8,7 +8,6 @@ Date:19-May-2026
*/
#pragma once
#include <windows.h>
#include <string>
#include "AuthenticationManagementService.h"
#include "Enums.h"
@@ -58,7 +57,6 @@ public:
util::Map<std::string, const User*> getUsers(util::UserType userType);
void createJobCard(const std::string& bookingID, const std::string& technicianID, const std::string& serviceID);
void createService(const std::string& name, const util::Vector<std::string>& inventoryItemIDs, double laborCost);
void removeServiceBooking(const std::string& bookingID);
void removeService(const std::string& serviceID);
util::Map<std::string, const JobCard*> getJobCardsByUser();
void updateJobStatus(const std::string& jobID);
@@ -72,7 +70,7 @@ public:
util::Vector<const Notification*> getNotifications();
void deleteNotification(const std::string& notificationID);
void configureNotifications(bool paymentNotifications, bool serviceNotifications);
bool initialize();
void shutdown();
void registerEvents(HANDLE accountDisabledEvent, HANDLE notificationAvailableEvent);
void loadSystemData();
void saveSystemData();
void runSystemChecks();
};
@@ -1,225 +0,0 @@
/*
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);
}
@@ -1,37 +0,0 @@
/*
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);
};
@@ -7,8 +7,11 @@ Date: 19-May-2026
#pragma once
class Notification;
class Observer
{
public:
virtual ~Observer() = default;
virtual void addNotification(Notification* notification) = 0;
};
@@ -1,17 +0,0 @@
/*
File: FileHeader.h
Description: Defines the FileHeader structure used to store
metadata for binary record files, including
record count and capacity.
Author: Trenser
Created: 10-June-2026
*/
#pragma once
#include <cstddef>
struct FileHeader
{
size_t recordCount;
size_t capacity;
};
@@ -1,29 +0,0 @@
/*
File: MappingInfo.h
Description: Defines the MappingInfo structure used for
managing Windows file mapping operations.
Stores handles, mapped view pointer,
file metadata, and capacity information.
Author: Trenser
Created: 10-June-2026
*/
#pragma once
#include <windows.h>
#include <string>
struct MappingInfo
{
HANDLE fileHandle;
HANDLE mappingHandle;
void* mappedView;
std::string fileName;
size_t recordSize;
size_t mappedCapacity;
MappingInfo()
: fileHandle(NULL),
mappingHandle(NULL),
mappedView(nullptr),
recordSize(0),
mappedCapacity(0) {}
};
@@ -1,17 +0,0 @@
/*
File: RecordState.h
Description: Defines the RecordState enumeration used to
represent the state of a record in storage.
States include CLEAN, NEW_RECORD, and MODIFIED.
Author: Trenser
Created: 10-June-2026
*/
#pragma once
enum class RecordState : int
{
CLEAN,
NEW_RECORD,
MODIFIED
};
@@ -1,109 +0,0 @@
/*
File: SerializedRecords.h
Description: Defines serialized structures for persistent storage
and retrieval of system entities including User,
Notification, Service, ComboPackage, InventoryItem,
ServiceBooking, JobCard, Invoice, and Observer.
These structures use fixed-size character arrays
and primitive types for binary serialization.
Author: Trenser
Created: 10-June-2026
*/
#pragma once
#include "Utility.h"
#include "Enums.h"
#include "Timestamp.h"
struct SerializedUser
{
char id[64];
char username[64];
char password[64];
char name[128];
char phone[32];
char email[128];
util::UserType userType;
util::State status;
};
struct SerializedNotification
{
char id[64];
char recipientUserId[64];
char title[128];
char message[1024];
util::Timestamp createdAt;
util::State state;
};
struct SerializedService
{
char id[64];
char name[128];
char inventoryItemIDs[1024];
double laborCost;
util::State status;
};
struct SerializedComboPackage
{
char id[64];
char packageName[128];
double discountPercentage;
char serviceIDs[1024];
util::State status;
};
struct SerializedInventoryItem
{
char id[64];
char partName[128];
int quantity;
double price;
util::State status;
};
struct SerializedServiceBooking
{
char id[64];
util::ServiceJobStatus status;
char serviceIDs[1024];
char customerId[64];
char vehicleNumber[64];
char vehicleBrand[64];
char vehicleModel[64];
char assignedTechnicianId[64];
double discountPercentage;
};
struct SerializedJobCard
{
char id[64];
char bookingId[64];
char serviceId[64];
char technicianId[64];
util::Timestamp assignedDate;
util::ServiceJobStatus status;
util::Timestamp completionDate;
};
struct SerializedInvoice
{
char id[64];
char bookingId[64];
util::Timestamp invoiceDate;
char partIDs[1024];
double laborCost;
double partsCost;
double discountPercentage;
double totalAmount;
util::Timestamp paymentDate;
util::PaymentMode paymentMethod;
util::PaymentStatus status;
};
struct SerializedObserver
{
char id[64];
};
@@ -1,386 +0,0 @@
/*
File: SharedMemory.cpp
Description: Implements shared memory utilities for managing
Windows file mapping operations. Provides functions
to create, open, resize, and close mappings, as well
as access headers, records, and ensure synchronization
across processes.
Author: Trenser
Created: 11-June-2026
*/
#include "SharedMemory.h"
#include "Windows.h"
#include "Config.h"
/*
Function: invalidateMapping
Description: Releases all mapping resources and resets the mapping to an invalid state.
Parameters:
- mapping: MappingInfo&, mapping information and handles
Returns:
- None
*/
static void invalidateMapping(MappingInfo& mapping)
{
if (mapping.mappedView != nullptr)
{
UnmapViewOfFile(mapping.mappedView);
mapping.mappedView = nullptr;
}
if (mapping.mappingHandle != NULL)
{
CloseHandle(mapping.mappingHandle);
mapping.mappingHandle = NULL;
}
if (mapping.fileHandle != INVALID_HANDLE_VALUE)
{
CloseHandle(mapping.fileHandle);
mapping.fileHandle = INVALID_HANDLE_VALUE;
}
mapping.mappedCapacity = 0;
}
/*
Function: createOrOpenMapping
Description: Creates or opens a file mapping and maps it into the process address space.
Parameters:
- mapping: MappingInfo&, mapping information and handles
Returns:
- bool: True if the mapping was successfully created/opened, otherwise false
*/
bool SharedMemory::createOrOpenMapping(MappingInfo& mapping)
{
if (mapping.recordSize == 0)
{
return false;
}
mapping.fileHandle =
CreateFileA(
mapping.fileName.c_str(),
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (mapping.fileHandle == INVALID_HANDLE_VALUE)
{
return false;
}
LARGE_INTEGER fileSize;
if (!GetFileSizeEx(mapping.fileHandle, &fileSize))
{
invalidateMapping(mapping);
return false;
}
bool isNewFile = (fileSize.QuadPart == 0);
const size_t initialCapacity = config::file::INITIAL_CAPACITY;
if (isNewFile)
{
LARGE_INTEGER newSize;
newSize.QuadPart = sizeof(FileHeader) + initialCapacity * mapping.recordSize;
if (!SetFilePointerEx(mapping.fileHandle, newSize, NULL, FILE_BEGIN))
{
invalidateMapping(mapping);
return false;
}
if (!SetEndOfFile(mapping.fileHandle))
{
invalidateMapping(mapping);
return false;
}
}
mapping.mappingHandle =
CreateFileMappingA(
mapping.fileHandle,
NULL,
PAGE_READWRITE,
0,
0,
NULL);
if (mapping.mappingHandle == NULL)
{
invalidateMapping(mapping);
return false;
}
mapping.mappedView =
MapViewOfFile(
mapping.mappingHandle,
FILE_MAP_ALL_ACCESS,
0,
0,
0);
if (mapping.mappedView == nullptr)
{
invalidateMapping(mapping);
return false;
}
FileHeader* header = getHeader(mapping);
if (header == nullptr)
{
invalidateMapping(mapping);
return false;
}
if (isNewFile)
{
header->recordCount = 0;
header->capacity = initialCapacity;
}
mapping.mappedCapacity = header->capacity;
return true;
}
/*
Function: closeMapping
Description: Unmaps and closes all resources associated with a file mapping.
Parameters:
- mapping: MappingInfo&, mapping to close
Returns:
- None
*/
void SharedMemory::closeMapping(MappingInfo& mapping)
{
invalidateMapping(mapping);
}
/*
Function: getHeader
Description: Returns the file header stored at the beginning of the mapped memory region.
Parameters:
- mapping: MappingInfo&, mapping information and handles
Returns:
- FileHeader*: Pointer to the file header, or nullptr if the mapping is not valid
*/
FileHeader* SharedMemory::getHeader(MappingInfo& mapping)
{
if (mapping.mappedView == nullptr)
{
return nullptr;
}
return reinterpret_cast<FileHeader*>(mapping.mappedView);
}
/*
Function: getRecordAddress
Description: Returns the address of a record at the specified index within the mapped memory region.
Parameters:
- mapping: MappingInfo&, mapping information and handles
- index: size_t, record index
Returns:
- void*: Pointer to the record, or nullptr if the mapping is not valid
*/
void* SharedMemory::getRecordAddress(MappingInfo& mapping, size_t index)
{
if (mapping.mappedView == nullptr)
{
return nullptr;
}
return reinterpret_cast<char*>(mapping.mappedView) + sizeof(FileHeader) + index * mapping.recordSize;
}
/*
Function: getRecordCount
Description: Returns the number of records currently stored in the mapping.
Parameters:
- mapping: MappingInfo&, mapping information and handles
Returns:
- size_t: Number of records in the mapping
*/
size_t SharedMemory::getRecordCount(MappingInfo& mapping)
{
FileHeader* header = getHeader(mapping);
if (header == nullptr)
{
return 0;
}
return header->recordCount;
}
/*
Function: setRecordCount
Description: Updates the number of records stored in the mapping.
Parameters:
- mapping: MappingInfo&, mapping information and handles
- count: size_t, new record count
Returns:
- None
*/
void SharedMemory::setRecordCount(MappingInfo& mapping, size_t count)
{
FileHeader* header = getHeader(mapping);
if (header == nullptr)
{
return;
}
header->recordCount = count;
}
/*
Function: getCapacity
Description: Returns the current capacity of the mapping.
Parameters:
- mapping: MappingInfo&, mapping information and handles
Returns:
- size_t: Maximum number of records that can be stored in the mapping
*/
size_t SharedMemory::getCapacity(MappingInfo& mapping)
{
FileHeader* header = getHeader(mapping);
if (header == nullptr)
{
return 0;
}
return header->capacity;
}
/*
Function: resizeMapping
Description: Resizes the file mapping to the specified capacity.
Parameters:
- mapping: MappingInfo&, mapping information and handles
- newCapacity: size_t, new mapping capacity
Returns:
- bool: True if the resize succeeded, otherwise false
*/
bool SharedMemory::resizeMapping(MappingInfo& mapping, size_t newCapacity)
{
FileHeader* header = getHeader(mapping);
if (header == nullptr)
{
invalidateMapping(mapping);
return false;
}
header->capacity = newCapacity;
if (!FlushViewOfFile(mapping.mappedView, sizeof(FileHeader)))
{
return false;
}
if (!UnmapViewOfFile(mapping.mappedView))
{
return false;
}
mapping.mappedView = nullptr;
CloseHandle(mapping.mappingHandle);
mapping.mappingHandle = NULL;
LARGE_INTEGER newSize;
newSize.QuadPart = sizeof(FileHeader) + newCapacity * mapping.recordSize;
if (!SetFilePointerEx(mapping.fileHandle, newSize, NULL, FILE_BEGIN))
{
invalidateMapping(mapping);
return false;
}
if (!SetEndOfFile(mapping.fileHandle))
{
invalidateMapping(mapping);
return false;
}
mapping.mappingHandle =
CreateFileMappingA(
mapping.fileHandle,
NULL,
PAGE_READWRITE,
0,
0,
NULL);
if (mapping.mappingHandle == NULL)
{
invalidateMapping(mapping);
return false;
}
mapping.mappedView =
MapViewOfFile(
mapping.mappingHandle,
FILE_MAP_ALL_ACCESS,
0,
0,
0);
if (mapping.mappedView == nullptr)
{
invalidateMapping(mapping);
return false;
}
mapping.mappedCapacity = newCapacity;
return true;
}
/*
Function: ensureCapacityForInsert
Description: Ensures that the mapping has space for at least one additional record, growing it if necessary.
Parameters:
- mapping: MappingInfo&, mapping information and handles
Returns:
- bool: True if capacity is available, otherwise false
*/
bool SharedMemory::ensureCapacityForInsert(MappingInfo& mapping)
{
size_t recordCount = getRecordCount(mapping);
size_t capacity = getCapacity(mapping);
if (recordCount < capacity)
{
return true;
}
return resizeMapping(mapping, capacity * config::file::GROWTH_FACTOR);
}
/*
Function: ensureLatestMapping
Description: Remaps the file if another process has resized it.
Parameters:
- mapping: MappingInfo&, mapping information and handles
Returns:
- bool: True if the mapping is valid and up to date, otherwise false
*/
bool SharedMemory::ensureLatestMapping(MappingInfo& mapping)
{
FileHeader* header = getHeader(mapping);
if (header == nullptr)
{
return false;
}
if (header->capacity == mapping.mappedCapacity)
{
return true;
}
if (!UnmapViewOfFile(mapping.mappedView))
{
invalidateMapping(mapping);
return false;
}
mapping.mappedView = nullptr;
CloseHandle(mapping.mappingHandle);
mapping.mappingHandle = NULL;
mapping.mappingHandle =
CreateFileMappingA(
mapping.fileHandle,
NULL,
PAGE_READWRITE,
0,
0,
NULL);
if (mapping.mappingHandle == NULL)
{
invalidateMapping(mapping);
return false;
}
mapping.mappedView =
MapViewOfFile(
mapping.mappingHandle,
FILE_MAP_ALL_ACCESS,
0,
0,
0);
if (mapping.mappedView == nullptr)
{
invalidateMapping(mapping);
return false;
}
header = getHeader(mapping);
if (header == nullptr)
{
invalidateMapping(mapping);
return false;
}
mapping.mappedCapacity = header->capacity;
return true;
}
@@ -1,29 +0,0 @@
/*
File: SharedMemory.h
Description: Declares functions for managing Windows file
mapping and shared memory operations. Provides
utilities for creating, resizing, and closing
mappings, as well as accessing headers and
record data.
Author: Trenser
Created: 10-June-2026
*/
#pragma once
#include <cstddef>
#include "MappingInfo.h"
#include "FileHeader.h"
namespace SharedMemory
{
bool createOrOpenMapping(MappingInfo& mapping);
void closeMapping(MappingInfo& mapping);
bool ensureLatestMapping(MappingInfo& mapping);
bool resizeMapping(MappingInfo& mapping, size_t newCapacity);
FileHeader* getHeader(MappingInfo& mapping);
void* getRecordAddress(MappingInfo& mapping, size_t index);
size_t getRecordCount(MappingInfo& mapping);
void setRecordCount(MappingInfo& mapping, size_t count);
size_t getCapacity(MappingInfo& mapping);
bool ensureCapacityForInsert(MappingInfo& mapping);
};
@@ -1,33 +0,0 @@
/*
File: TrackedRecord.h
Description: Defines the TrackedRecord template structure used
to manage objects with associated record state and
slot index. Supports tracking of CLEAN, NEW_RECORD,
and MODIFIED states for persistence and synchronization.
Author: Trenser
Created: 10-June-2026
*/
#pragma once
#include "RecordState.h"
static const size_t INVALID_SLOT = static_cast<size_t>(-1);
template<typename T>
struct TrackedRecord
{
T* data;
RecordState state;
size_t slotIndex;
TrackedRecord()
: data(nullptr),
state(RecordState::CLEAN),
slotIndex(INVALID_SLOT) {}
TrackedRecord(
T* object,
RecordState recordState,
size_t slot)
: data(object),
state(recordState),
slotIndex(slot) {}
};
@@ -9,197 +9,6 @@ Date: 19-May-2026
*/
#include "DataStore.h"
#include "Config.h"
#include "SerializedRecords.h"
#include "FileHelper.h"
#include "ServiceBooking.h"
#include "JobCard.h"
#include "Invoice.h"
/*
Function: DataStore
Description: Constructs the DataStore singleton and initializes
internal handles to their default values.
Parameters:
- None
Returns:
- None
*/
DataStore::DataStore() :
m_globalMutex(NULL) {}
/*
Function: ~DataStore
Description: Destroys the DataStore singleton and releases all
cached application objects owned by the datastore.
This includes users, notifications, services,
combo packages, inventory items, service bookings,
job cards, and invoices.
Parameters:
- None
Returns:
- None
*/
DataStore::~DataStore()
{
clearCache(m_userCache);
clearCache(m_notificationCache);
clearCache(m_serviceCache);
clearCache(m_comboPackageCache);
clearCache(m_inventoryItemCache);
clearCache(m_serviceBookingCache);
clearCache(m_jobCardCache);
clearCache(m_invoiceCache);
}
/*
Function: initialize
Description: Initializes the shared-memory datastore.
Creates or opens the global datastore mutex,
configures all mapping metadata, and creates
or opens the file mappings used to persist
application data. After successful completion,
all datastore files are mapped into memory and
ready for use by the application.
Parameter: None
Return type: bool
- true : Initialization completed successfully.
- false : Failed to create the mutex or open
one or more file mappings.
*/
bool DataStore::initialize()
{
m_globalMutex = CreateMutexA(NULL, FALSE, "VehicleServiceSystemMutex");
if (m_globalMutex == NULL)
{
return false;
}
if (!lockDataStore())
{
CloseHandle(m_globalMutex);
m_globalMutex = NULL;
return false;
}
bool success = true;
do
{
util::ensureDirectoryExists(config::file::DIRECTORY);
m_users.fileName = config::file::USER_FILE;
m_users.recordSize = sizeof(SerializedUser);
m_notifications.fileName = config::file::NOTIFICATION_FILE;
m_notifications.recordSize = sizeof(SerializedNotification);
m_services.fileName = config::file::SERVICE_FILE;
m_services.recordSize = sizeof(SerializedService);
m_comboPackages.fileName = config::file::COMBOPACKAGE_FILE;
m_comboPackages.recordSize = sizeof(SerializedComboPackage);
m_inventoryItems.fileName = config::file::INVENTORYITEM_FILE;
m_inventoryItems.recordSize = sizeof(SerializedInventoryItem);
m_serviceBookings.fileName = config::file::SERVICEBOOKING_FILE;
m_serviceBookings.recordSize = sizeof(SerializedServiceBooking);
m_jobCards.fileName = config::file::JOBCARD_FILE;
m_jobCards.recordSize = sizeof(SerializedJobCard);
m_invoices.fileName = config::file::INVOICE_FILE;
m_invoices.recordSize = sizeof(SerializedInvoice);
m_serviceManagementObservers.fileName = config::file::SERVICEMANAGEMENTOBSERVERS;
m_serviceManagementObservers.recordSize = sizeof(SerializedObserver);
m_paymentManagementObservers.fileName = config::file::PAYMENTMANAGEMENTOBSERVERS;
m_paymentManagementObservers.recordSize = sizeof(SerializedObserver);
m_inventoryManagementObservers.fileName = config::file::INVENTORYMANAGEMENTOBSERVERS;
m_inventoryManagementObservers.recordSize = sizeof(SerializedObserver);
if (!SharedMemory::createOrOpenMapping(m_users))
{
success = false;
break;
}
if (!SharedMemory::createOrOpenMapping(m_notifications))
{
success = false;
break;
}
if (!SharedMemory::createOrOpenMapping(m_services))
{
success = false;
break;
}
if (!SharedMemory::createOrOpenMapping(m_comboPackages))
{
success = false;
break;
}
if (!SharedMemory::createOrOpenMapping(m_inventoryItems))
{
success = false;
break;
}
if (!SharedMemory::createOrOpenMapping(m_serviceBookings))
{
success = false;
break;
}
if (!SharedMemory::createOrOpenMapping(m_jobCards))
{
success = false;
break;
}
if (!SharedMemory::createOrOpenMapping(m_invoices))
{
success = false;
break;
}
if (!SharedMemory::createOrOpenMapping(m_serviceManagementObservers))
{
success = false;
break;
}
if (!SharedMemory::createOrOpenMapping(m_paymentManagementObservers))
{
success = false;
break;
}
if (!SharedMemory::createOrOpenMapping(m_inventoryManagementObservers))
{
success = false;
break;
}
} while (false);
unlockDataStore();
if (!success)
{
shutdown();
}
return success;
}
/*
Function: shutdown
Description: Releases all shared-memory resources owned by the
datastore. Closes every file mapping, unmaps all
mapped views, and releases the datastore mutex.
After this call, the datastore must be initialized
again before use.
Parameter: None
Return type: void
*/
void DataStore::shutdown()
{
SharedMemory::closeMapping(m_users);
SharedMemory::closeMapping(m_notifications);
SharedMemory::closeMapping(m_services);
SharedMemory::closeMapping(m_comboPackages);
SharedMemory::closeMapping(m_inventoryItems);
SharedMemory::closeMapping(m_serviceBookings);
SharedMemory::closeMapping(m_jobCards);
SharedMemory::closeMapping(m_invoices);
SharedMemory::closeMapping(m_serviceManagementObservers);
SharedMemory::closeMapping(m_paymentManagementObservers);
SharedMemory::closeMapping(m_inventoryManagementObservers);
if (m_globalMutex != NULL)
{
CloseHandle(m_globalMutex);
m_globalMutex = NULL;
}
}
/*
Function: getInstance
@@ -217,567 +26,104 @@ DataStore& DataStore::getInstance()
/*
Function: getUsers
Description: Retrieves all user records from the datastore.
Description: Retrieves the internal map of users.
Parameters:
- None
Returns:
- util::Map<std::string, TrackedRecord<User>>: Collection of user records
- Reference to util::Map<std::string, User*> containing all users.
*/
util::Map<std::string, TrackedRecord<User>>& DataStore::getUsers()
util::Map<std::string, User*>& DataStore::getUsers()
{
auto users = loadRecords<User, SerializedUser>(m_users);
refreshCache(m_userCache, users);
return m_userCache;
}
/*
Function: getNotifications
Description: Retrieves all notification records from the datastore.
Parameters:
- None
Returns:
- util::Map<std::string, TrackedRecord<Notification>>: Collection of notification records
*/
util::Map<std::string, TrackedRecord<Notification>>& DataStore::getNotifications()
{
auto notifications = loadRecords<Notification, SerializedNotification>(m_notifications);
refreshCache(m_notificationCache, notifications);
return m_notificationCache;
return m_users;
}
/*
Function: getServices
Description: Retrieves all service records from the datastore.
Description: Retrieves the internal map of services.
Parameters:
- None
Returns:
- util::Map<std::string, TrackedRecord<Service>>: Collection of service records
- Reference to util::Map<std::string, Service*> containing all services.
*/
util::Map<std::string, TrackedRecord<Service>>& DataStore::getServices()
util::Map<std::string, Service*>& DataStore::getServices()
{
util::Map<std::string, TrackedRecord<Service>> services = loadRecords<Service, SerializedService>(m_services);
refreshCache(m_serviceCache, services);
util::Map<std::string, TrackedRecord<InventoryItem>>& inventoryItems = getInventoryItems();
size_t numberOfServices = m_serviceCache.getSize();
for (int iteratorOne =0; iteratorOne < numberOfServices; iteratorOne++)
{
Service* currentService = m_serviceCache.getValueAt(iteratorOne).data;
util::Map<std::string, InventoryItem*> inventoryItemMap;
util::Vector<std::string> currentServiceInventoryItem = currentService->getRequiredInventoryItemIDs();
for (int iteratorTwo = 0; iteratorTwo < currentServiceInventoryItem.getSize(); iteratorTwo++)
{
const std::string& currentInventoryItemId = currentServiceInventoryItem[iteratorTwo];
int currentInventoryItemIndex = inventoryItems.find(currentInventoryItemId);
if (currentInventoryItemIndex == -1)
{
throw std::runtime_error("Invalid inventory item ID");
}
InventoryItem* currentItem = inventoryItems.getValueAt(currentInventoryItemIndex).data;
inventoryItemMap[currentInventoryItemId] = currentItem;
}
currentService->setRequiredInventoryItems(inventoryItemMap);
}
return m_serviceCache;
return m_services;
}
/*
Function: getComboPackages
Description: Retrieves all combo package records from the datastore.
Description: Retrieves the internal map of combo packages.
Parameters:
- None
Returns:
- util::Map<std::string, TrackedRecord<ComboPackage>>: Collection of combo package records
- Reference to util::Map<std::string, ComboPackage*> containing all combo packages.
*/
util::Map<std::string, TrackedRecord<ComboPackage>>& DataStore::getComboPackages()
util::Map<std::string, ComboPackage*>& DataStore::getComboPackages()
{
util::Map<std::string, TrackedRecord<ComboPackage>> comboPackages = loadRecords<ComboPackage, SerializedComboPackage>(m_comboPackages);
refreshCache(m_comboPackageCache, comboPackages);
util::Map<std::string, TrackedRecord<Service>>& services = getServices();
size_t numberOfComboPackages = m_comboPackageCache.getSize();
for (int iteratorOne = 0; iteratorOne < numberOfComboPackages; iteratorOne++)
{
ComboPackage* currentComboPackage = m_comboPackageCache.getValueAt(iteratorOne).data;
util::Vector<std::string> currentServiceIds = currentComboPackage->getServiceIDs();
util::Map<std::string, Service*> currentComboPackageServices;
for (int iteratorTwo = 0; iteratorTwo < currentServiceIds.getSize(); iteratorTwo++)
{
const std::string& currentServiceId = currentServiceIds[iteratorTwo];
int serviceIndex = services.find(currentServiceId);
if (serviceIndex == -1)
{
throw std::runtime_error("Invalid service ID");
}
Service* currentService = services.getValueAt(serviceIndex).data;
currentComboPackageServices[currentServiceId] = currentService;
}
currentComboPackage->setServices(currentComboPackageServices);
}
return m_comboPackageCache;
}
/*
Function: getInventoryItems
Description: Retrieves all inventory item records from the datastore.
Parameters:
- None
Returns:
- util::Map<std::string, TrackedRecord<InventoryItem>>: Collection of inventory item records
*/
util::Map<std::string, TrackedRecord<InventoryItem>>& DataStore::getInventoryItems()
{
auto inventoryItems = loadRecords<InventoryItem, SerializedInventoryItem>(m_inventoryItems);
refreshCache(m_inventoryItemCache, inventoryItems);
return m_inventoryItemCache;
return m_comboPackages;
}
/*
Function: getServiceBookings
Description: Retrieves all service booking records from the datastore.
Description: Retrieves the internal map of service bookings.
Parameters:
- None
Returns:
- util::Map<std::string, TrackedRecord<ServiceBooking>>: Collection of service booking records
- Reference to util::Map<std::string, ServiceBooking*> containing all service bookings.
*/
util::Map<std::string, TrackedRecord<ServiceBooking>>& DataStore::getServiceBookings()
util::Map<std::string, ServiceBooking*>& DataStore::getServiceBookings()
{
util::Map<std::string, TrackedRecord<ServiceBooking>> serviceBookings = loadRecords<ServiceBooking, SerializedServiceBooking>(m_serviceBookings);
refreshCache(m_serviceBookingCache, serviceBookings);
auto& users = getUsers();
auto& services = getServices();
size_t numberOfServiceBookings = m_serviceBookingCache.getSize();
for (int iteratorOne = 0; iteratorOne < numberOfServiceBookings; iteratorOne++)
{
ServiceBooking* serviceBooking = m_serviceBookingCache.getValueAt(iteratorOne).data;
auto& serviceIds = serviceBooking->getServiceIDs();
util::Map<std::string, Service*> servicesInBooking;
for (int iteratorTwo = 0; iteratorTwo < serviceIds.getSize(); iteratorTwo++)
{
const std::string& currentServiceId = serviceIds[iteratorTwo];
int serviceIndex = services.find(currentServiceId);
if (serviceIndex == -1)
{
throw std::runtime_error("Invalid service index.");
}
auto currentService = services.getValueAt(serviceIndex);
servicesInBooking[currentServiceId] = currentService.data;
}
serviceBooking->setServices(servicesInBooking);
if (!serviceBooking->getCustomerId().empty())
{
int userIndex = users.find(serviceBooking->getCustomerId());
if (userIndex == -1)
{
throw std::runtime_error("Invalid user index.");
}
auto customer = users.getValueAt(userIndex);
serviceBooking->setCustomer(customer.data);
}
if (!serviceBooking->getAssignedTechnicianId().empty())
{
int technicianIndex = users.find(serviceBooking->getAssignedTechnicianId());
if (technicianIndex == -1)
{
throw std::runtime_error("Invalid technician index.");
}
auto technician = users.getValueAt(technicianIndex);
serviceBooking->setAssignedTechnician(technician.data);
}
}
return m_serviceBookingCache;
return m_serviceBookings;
}
/*
Function: getJobCards
Description: Retrieves all job card records from the datastore.
Description: Retrieves the internal map of job cards.
Parameters:
- None
Returns:
- util::Map<std::string, TrackedRecord<JobCard>>: Collection of job card records
- Reference to util::Map<std::string, JobCard*> containing all job cards.
*/
util::Map<std::string, TrackedRecord<JobCard>>& DataStore::getJobCards()
util::Map<std::string, JobCard*>& DataStore::getJobCards()
{
util::Map<std::string, TrackedRecord<JobCard>> jobCards = loadRecords<JobCard, SerializedJobCard>(m_jobCards);
refreshCache(m_jobCardCache, jobCards);
auto& serviceBookings = getServiceBookings();
auto& services = getServices();
auto& users = getUsers();
int numberOfJobCards = m_jobCardCache.getSize();
for (int iterator = 0; iterator < numberOfJobCards; iterator++)
{
JobCard* jobCard = m_jobCardCache.getValueAt(iterator).data;
if (!jobCard)
{
continue;
}
const std::string& bookingId = jobCard->getBookingId();
int bookingIndex = serviceBookings.find(bookingId);
if (bookingIndex == -1)
{
throw std::runtime_error("Invalid booking ID: " + bookingId);
}
auto& trackedBooking = serviceBookings.getValueAt(bookingIndex);
jobCard->setBooking(trackedBooking.data);
const std::string& serviceId = jobCard->getServiceId();
int serviceIndex = services.find(serviceId);
if (serviceIndex == -1)
{
throw std::runtime_error("Invalid service ID: " + serviceId);
}
auto trackedService = services.getValueAt(serviceIndex);
jobCard->setService(trackedService.data);
const std::string& technicianId = jobCard->getTechnicianId();
if (!technicianId.empty())
{
int technicianIndex = users.find(technicianId);
if (technicianIndex == -1)
{
throw std::runtime_error("Invalid technician ID: " + technicianId);
}
auto trackedTechnician = users.getValueAt(technicianIndex);
jobCard->setTechnician(trackedTechnician.data);
}
}
return m_jobCardCache;
return m_jobCards;
}
/*
Function: getInventoryItems
Description: Retrieves the internal map of inventory items.
Parameters:
- None
Returns:
- Reference to util::Map<std::string, InventoryItem*> containing all inventory items.
*/
util::Map<std::string, InventoryItem*>& DataStore::getInventoryItems()
{
return m_inventoryItems;
}
/*
Function: getInvoices
Description: Retrieves all invoice records from the datastore.
Description: Retrieves the internal map of invoices.
Parameters:
- None
Returns:
- util::Map<std::string, TrackedRecord<Invoice>>: Collection of invoice records
- Reference to util::Map<std::string, Invoice*> containing all invoices.
*/
util::Map<std::string, TrackedRecord<Invoice>>& DataStore::getInvoices()
util::Map<std::string, 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;
return m_invoices;
}
/*
Function: getObservers
Description: Retrieves observer records from the specified observer mapping
and resolves them to User objects.
Parameters:
- mapping: Observer mapping to read from
Returns:
- util::Map<std::string, User*>: Collection of observer records
Throws:
- std::runtime_error if an observer references an invalid user ID
*/
util::Map<std::string, User*> DataStore::getObservers(MappingInfo& mapping)
{
auto& users = getUsers();
util::Map<std::string, User*> observers;
SharedMemory::ensureLatestMapping(mapping);
size_t recordCount = SharedMemory::getRecordCount(mapping);
for (size_t index = 0; index < recordCount; index++)
{
const SerializedObserver* observer = static_cast<SerializedObserver*>(SharedMemory::getRecordAddress(mapping, index));
int userIndex = users.find(observer->id);
if (userIndex == -1)
{
throw std::runtime_error("Invalid observer user ID");
}
User* user = users.getValueAt(userIndex).data;
observers.insert(user->getId(), user);
}
return observers;
}
/*
Function: getServiceManagementObservers
Description: Retrieves all service management observer records from the datastore.
Function: getPayments
Description: Retrieves the internal map of payments.
Parameters:
- None
Returns:
- util::Map<std::string, User*>: Collection of observer records
- Reference to util::Map<std::string, Payment*> containing all payments.
*/
util::Map<std::string, User*> DataStore::getServiceManagementObservers()
util::Map<std::string, Payment*>& DataStore::getPayments()
{
return getObservers(m_serviceManagementObservers);
}
/*
Function: getPaymentManagementObservers
Description: Retrieves all payment management observer records from the datastore.
Parameters:
- None
Returns:
- util::Map<std::string, User*>: Collection of observer records
*/
util::Map<std::string, User*> DataStore::getPaymentManagementObservers()
{
return getObservers(m_paymentManagementObservers);
}
/*
Function: getInventoryManagementObservers
Description: Retrieves all inventory management observer records from the datastore.
Parameters:
- None
Returns:
- util::Map<std::string, User*>: Collection of observer records
*/
util::Map<std::string, User*> DataStore::getInventoryManagementObservers()
{
return getObservers(m_inventoryManagementObservers);
}
/*
Function: saveUsers
Description: Persists all user records to the datastore.
Parameters:
- None
Returns:
- None
*/
void DataStore::saveUsers()
{
saveRecords<User, SerializedUser>(m_users, m_userCache);
}
/*
Function: saveNotifications
Description: Persists all notification records to the datastore.
Parameters:
- None
Returns:
- None
*/
void DataStore::saveNotifications()
{
saveRecords<Notification, SerializedNotification>(m_notifications, m_notificationCache);
}
/*
Function: saveServices
Description: Persists all service records to the datastore.
Parameters:
- None
Returns:
- None
*/
void DataStore::saveServices()
{
saveRecords<Service, SerializedService>(m_services, m_serviceCache);
}
/*
Function: saveComboPackages
Description: Persists all combo package records to the datastore.
Parameters:
- None
Returns:
- None
*/
void DataStore::saveComboPackages()
{
saveRecords<ComboPackage, SerializedComboPackage>(m_comboPackages, m_comboPackageCache);
}
/*
Function: saveInventoryItems
Description: Persists all inventory item records to the datastore.
Parameters:
- None
Returns:
- None
*/
void DataStore::saveInventoryItems()
{
saveRecords<InventoryItem, SerializedInventoryItem>(m_inventoryItems, m_inventoryItemCache);
}
/*
Function: saveServiceBookings
Description: Persists all service booking records to the datastore.
Parameters:
- None
Returns:
- None
*/
void DataStore::saveServiceBookings()
{
saveRecords<ServiceBooking, SerializedServiceBooking>(m_serviceBookings, m_serviceBookingCache);
}
/*
Function: saveJobCards
Description: Persists all job card records to the datastore.
Parameters:
- None
Returns:
- None
*/
void DataStore::saveJobCards()
{
saveRecords<JobCard, SerializedJobCard>(m_jobCards, m_jobCardCache);
}
/*
Function: saveInvoices
Description: Persists all invoice records to the datastore.
Parameters:
- None
Returns:
- None
*/
void DataStore::saveInvoices()
{
saveRecords<Invoice, SerializedInvoice>(m_invoices, m_invoiceCache);
}
/*
Function: saveObservers
Description: Persists observer records to the specified observer mapping.
Parameters:
- mapping: MappingInfo&, observer mapping to save to
- observers: util::Map<std::string, User*>&, collection of observer records
Returns:
- None
*/
void DataStore::saveObservers(MappingInfo& mapping, util::Map<std::string, User*>& observers)
{
size_t observerCount = static_cast<size_t>(observers.getSize());
size_t capacity = config::file::INITIAL_CAPACITY;
while (capacity < observerCount)
{
capacity *= config::file::GROWTH_FACTOR;
}
if (!SharedMemory::resizeMapping(mapping, capacity))
{
throw std::runtime_error("Failed to resize observer mapping");
}
SharedMemory::setRecordCount(mapping, observerCount);
for (size_t index = 0; index < observerCount; index++)
{
SerializedObserver serializedObserver;
User* user = observers.getValueAt(static_cast<int>(index));
strcpy_s(serializedObserver.id, sizeof(serializedObserver.id), user->getId().c_str());
SerializedObserver* destination = static_cast<SerializedObserver*>(SharedMemory::getRecordAddress(mapping, index));
*destination = serializedObserver;
}
}
/*
Function: saveServiceManagementObservers
Description: Persists all service management observer records to the datastore.
Parameters:
- observers: util::Map<std::string, User*>&, collection of observer records
Returns:
- None
*/
void DataStore::saveServiceManagementObservers(util::Map<std::string, User*>& observers)
{
saveObservers(m_serviceManagementObservers, observers);
}
/*
Function: savePaymentManagementObservers
Description: Persists all payment management observer records to the datastore.
Parameters:
- observers: util::Map<std::string, User*>&, collection of observer records
Returns:
- None
*/
void DataStore::savePaymentManagementObservers(util::Map<std::string, User*>& observers)
{
saveObservers(m_paymentManagementObservers, observers);
}
/*
Function: saveInventoryManagementObservers
Description: Persists all inventory management observer records to the datastore.
Parameters:
- observers: util::Map<std::string, User*>&, collection of observer records
Returns:
- None
*/
void DataStore::saveInventoryManagementObservers(util::Map<std::string, User*>& observers)
{
saveObservers(m_inventoryManagementObservers, observers);
}
/*
Function: lockDataStore
Description: Acquires the datastore mutex, providing
exclusive access to the shared-memory
datastore. This function blocks until
the mutex becomes available or an error
occurs.
Parameter: None
Return type:
bool
- true : Mutex successfully acquired.
- false : Failed to acquire the mutex.
*/
bool DataStore::lockDataStore()
{
if (m_globalMutex == NULL)
{
return false;
}
DWORD result = WaitForSingleObject(m_globalMutex, INFINITE);
return result == WAIT_OBJECT_0;
}
/*
Function: unlockDataStore
Description: Releases the datastore mutex after a
successful call to lockDataStore(),
allowing other processes to access the
shared-memory datastore.
Parameter: None
Return type:
bool
- true : Mutex successfully released.
- false : Failed to release the mutex.
*/
bool DataStore::unlockDataStore()
{
if (m_globalMutex == NULL)
{
return false;
}
return ReleaseMutex(m_globalMutex) != 0;
return m_payments;
}
@@ -6,248 +6,42 @@ Date: 19-May-2026
*/
#pragma once
#include <windows.h>
#include <string>
#include "Map.h"
#include "MappingInfo.h"
#include "TrackedRecord.h"
#include "SerializedRecords.h"
#include "SharedMemory.h"
#include "User.h"
#include "Notification.h"
#include "Service.h"
#include "ComboPackage.h"
#include "InventoryItem.h"
#include "ServiceBooking.h"
#include "JobCard.h"
#include "Invoice.h"
class User;
class Service;
class ComboPackage;
class ServiceBooking;
class JobCard;
class InventoryItem;
class Invoice;
class Payment;
class DataStore
{
private:
DataStore();
~DataStore();
util::Map<std::string, User*> m_users;
util::Map<std::string, Service*> m_services;
util::Map<std::string, ComboPackage*> m_comboPackages;
util::Map<std::string, ServiceBooking*> m_serviceBookings;
util::Map<std::string, JobCard*> m_jobCards;
util::Map<std::string, InventoryItem*> m_inventoryItems;
util::Map<std::string, Invoice*> m_invoices;
util::Map<std::string, Payment*> m_payments;
DataStore() {}
public:
static DataStore& getInstance();
DataStore(const DataStore&) = delete;
DataStore& operator=(const DataStore&) = delete;
DataStore(DataStore&&) = delete;
DataStore& operator=(DataStore&&) = delete;
HANDLE m_globalMutex;
MappingInfo m_users;
MappingInfo m_notifications;
MappingInfo m_services;
MappingInfo m_comboPackages;
MappingInfo m_inventoryItems;
MappingInfo m_serviceBookings;
MappingInfo m_jobCards;
MappingInfo m_invoices;
MappingInfo m_serviceManagementObservers;
MappingInfo m_paymentManagementObservers;
MappingInfo m_inventoryManagementObservers;
util::Map<std::string, TrackedRecord<User>> m_userCache;
util::Map<std::string, TrackedRecord<Notification>> m_notificationCache;
util::Map<std::string, TrackedRecord<Service>> m_serviceCache;
util::Map<std::string, TrackedRecord<ComboPackage>> m_comboPackageCache;
util::Map<std::string, TrackedRecord<InventoryItem>> m_inventoryItemCache;
util::Map<std::string, TrackedRecord<ServiceBooking>> m_serviceBookingCache;
util::Map<std::string, TrackedRecord<JobCard>> m_jobCardCache;
util::Map<std::string, TrackedRecord<Invoice>> m_invoiceCache;
public:
static DataStore& getInstance();
bool initialize();
void shutdown();
util::Map<std::string, TrackedRecord<User>>& getUsers();
util::Map<std::string, TrackedRecord<Notification>>& getNotifications();
util::Map<std::string, TrackedRecord<Service>>& getServices();
util::Map<std::string, TrackedRecord<ComboPackage>>& getComboPackages();
util::Map<std::string, TrackedRecord<InventoryItem>>& getInventoryItems();
util::Map<std::string, TrackedRecord<ServiceBooking>>& getServiceBookings();
util::Map<std::string, TrackedRecord<JobCard>>& getJobCards();
util::Map<std::string, TrackedRecord<Invoice>>& getInvoices();
util::Map<std::string, User*> getServiceManagementObservers();
util::Map<std::string, User*> getPaymentManagementObservers();
util::Map<std::string, User*> getInventoryManagementObservers();
void saveUsers();
void saveNotifications();
void saveServices();
void saveComboPackages();
void saveInventoryItems();
void saveServiceBookings();
void saveJobCards();
void saveInvoices();
void saveServiceManagementObservers(util::Map<std::string, User*>& observers);
void savePaymentManagementObservers(util::Map<std::string, User*>& observers);
void saveInventoryManagementObservers(util::Map<std::string, User*>& observers);
bool lockDataStore();
bool unlockDataStore();
private:
template<typename TObject, typename TSerialized>
util::Map<std::string, TrackedRecord<TObject>> loadRecords(MappingInfo& mapping);
template<typename TObject, typename TSerialized>
void saveRecords(MappingInfo& mapping, util::Map<std::string, TrackedRecord<TObject>>& records);
template<typename TObject> void clearCache(util::Map<std::string, TrackedRecord<TObject>>&cache);
template<typename TObject> void refreshCache(util::Map<std::string, TrackedRecord<TObject>>&cache, util::Map<std::string, TrackedRecord<TObject>>&refreshedCache);
util::Map<std::string, User*> getObservers(MappingInfo& mapping);
void saveObservers(MappingInfo& mapping, util::Map<std::string, User*>& observers);
};
/*
Function: loadRecords
Description: Loads all serialized records from the specified
shared-memory mapping, deserializes them into
application objects, and returns them as a map
of tracked records. Each loaded record is marked
as CLEAN and assigned its corresponding slot index
within the mapping so that future modifications
can be written back efficiently.
Parameter:
- mapping: Reference to the mapping containing the
serialized records.
Return type:
util::Map<std::string, TrackedRecord<TObject>>
A map containing all loaded records keyed by
their unique identifier.
*/
template<typename TObject, typename TSerialized>
util::Map<std::string, TrackedRecord<TObject>> DataStore::loadRecords(MappingInfo& mapping)
{
util::Map<std::string, TrackedRecord<TObject>> records;
SharedMemory::ensureLatestMapping(mapping);
size_t recordCount = SharedMemory::getRecordCount(mapping);
for (size_t index = 0; index < recordCount; ++index)
{
TSerialized* serialized = static_cast<TSerialized*>(SharedMemory::getRecordAddress(mapping,index));
TObject* object = TObject::deserialize(*serialized);
TrackedRecord<TObject> trackedRecord;
trackedRecord.data = object;
trackedRecord.state = RecordState::CLEAN;
trackedRecord.slotIndex = index;
records.insert(object->getId(), trackedRecord);
}
return records;
}
/*
Function: saveRecords
Description: Persists all modified and newly added records
contained in the provided map to the specified
shared-memory mapping. Modified records overwrite
their existing slots, while new records are
appended to the end of the mapping. Records marked
as CLEAN are ignored.
Parameter:
- mapping: Reference to the mapping where records are
stored.
- records: Map containing the records to be persisted.
Return type: void
*/
template<typename TObject, typename TSerialized> void DataStore::saveRecords(MappingInfo& mapping, util::Map<std::string, TrackedRecord<TObject>>& records)
{
SharedMemory::ensureLatestMapping(mapping);
for (int index = 0; index < records.getSize(); ++index)
{
TrackedRecord<TObject>& record = records.getValueAt(index);
if (record.state == RecordState::CLEAN)
{
continue;
}
TSerialized serialized = record.data->serialize();
if (record.state == RecordState::MODIFIED)
{
TSerialized* destination = static_cast<TSerialized*>(SharedMemory::getRecordAddress(mapping, record.slotIndex));
if (destination)
{
*destination = serialized;
}
}
else if (record.state == RecordState::NEW_RECORD)
{
if (!SharedMemory::ensureCapacityForInsert(mapping))
{
continue;
}
size_t recordCount = SharedMemory::getRecordCount(mapping);
TSerialized* destination = static_cast<TSerialized*>(SharedMemory::getRecordAddress(mapping,recordCount));
*destination = serialized;
SharedMemory::setRecordCount(mapping, recordCount + 1);
record.slotIndex = recordCount;
}
record.state = RecordState::CLEAN;
}
}
/*
Function: clearCache
Description: Releases all objects owned by the cache and
clears the cache contents.
Parameters:
- cache: Cache to be cleared.
Returns:
- None
*/
template<typename TObject>
void DataStore::clearCache(util::Map<std::string, TrackedRecord<TObject>>&cache)
{
for (int index = 0; index < cache.getSize(); ++index)
{
delete cache.getValueAt(index).data;
cache.getValueAt(index).data = nullptr;
}
cache.clear();
}
/*
Function: refreshCache
Description: Refreshes the cache while preserving object addresses
for records that already exist. Existing objects are
updated in-place so that pointers held elsewhere remain
valid after the refresh.
Parameters:
- cache: Existing cache to refresh.
- refreshedCache: Newly loaded cache contents.
Returns:
- None
*/
template<typename TObject>
void DataStore::refreshCache(util::Map<std::string, TrackedRecord<TObject>>& cache, util::Map<std::string, TrackedRecord<TObject>>& refreshedCache)
{
util::Map<std::string, TrackedRecord<TObject>> oldCache = cache;
cache.clear();
for (int index = 0; index < refreshedCache.getSize(); ++index)
{
const std::string& id = refreshedCache.getKeyAt(index);
TrackedRecord<TObject>& refreshedRecord = refreshedCache.getValueAt(index);
int oldIndex = oldCache.find(id);
if (oldIndex != -1)
{
TrackedRecord<TObject>& oldRecord = oldCache.getValueAt(oldIndex);
if (oldRecord.state == RecordState::MODIFIED)
{
delete refreshedRecord.data;
cache.insert(id, oldRecord);
continue;
}
*oldRecord.data = *refreshedRecord.data;
oldRecord.slotIndex = refreshedRecord.slotIndex;
oldRecord.state = refreshedRecord.state;
delete refreshedRecord.data;
refreshedRecord.data = oldRecord.data;
}
cache.insert(id, refreshedRecord);
}
for (int index = 0; index < oldCache.getSize(); ++index)
{
const std::string& id = oldCache.getKeyAt(index);
const TrackedRecord<TObject>& localTrackedRecord = oldCache.getValueAt(index);
if (cache.find(id) == -1)
{
if (localTrackedRecord.state == RecordState::NEW_RECORD)
{
cache.insert(id, localTrackedRecord);
}
else
{
delete localTrackedRecord.data;
}
}
}
}
util::Map<std::string, User*>& getUsers();
util::Map<std::string, Service*>& getServices();
util::Map<std::string, ComboPackage*>& getComboPackages();
util::Map<std::string, ServiceBooking*>& getServiceBookings();
util::Map<std::string, JobCard*>& getJobCards();
util::Map<std::string, InventoryItem*>& getInventoryItems();
util::Map<std::string, Invoice*>& getInvoices();
util::Map<std::string, Payment*>& getPayments();
};
@@ -1,28 +0,0 @@
/*
File: DataStoreLockGuard.h
Description: Defines the DataStoreLockGuard class used to manage DataStore
locking and unlocking automatically within a scope.
Author: Trenser
Date: 12-June-2026
*/
#pragma once
#include "DataStore.h"
class DataStoreLockGuard
{
public:
explicit DataStoreLockGuard(DataStore& dataStore)
: m_dataStore(dataStore)
{
m_dataStore.lockDataStore();
}
~DataStoreLockGuard()
{
m_dataStore.unlockDataStore();
}
DataStoreLockGuard(const DataStoreLockGuard&) = delete;
DataStoreLockGuard& operator=(const DataStoreLockGuard&) = delete;
private:
DataStore& m_dataStore;
};
@@ -9,7 +9,6 @@ Date: 19-May-2026
#include <sstream>
#include <stdexcept>
#include "SerializedRecords.h"
#include "ComboPackage.h"
#include "Service.h"
#include "Factory.h"
@@ -29,8 +28,7 @@ Returns:
ComboPackage::ComboPackage()
: m_id("CMP" + std::to_string(++m_uid)),
m_status(util::State::ACTIVE),
m_discountPercentage(0.0) {
}
m_discountPercentage(0.0) {}
/*
Function: ComboPackage
@@ -272,38 +270,72 @@ static util::Vector<std::string> getServiceIDsAsVector(const std::string& servic
/*
Function: serialize
Description: Serializes the ComboPackage object into a SerializedComboPackage record.
Description: Serializes the combo package into a CSV-formatted string.
Parameters:
- None
Returns:
- SerializedComboPackage: Serialized representation of the combo package
- std::string: Serialized combo package record
*/
SerializedComboPackage ComboPackage::serialize() const
std::string ComboPackage::serialize() const
{
SerializedComboPackage serialized = {};
strcpy_s(serialized.id, sizeof(serialized.id), m_id.c_str());
strcpy_s(serialized.packageName, sizeof(serialized.packageName), m_packageName.c_str());
strcpy_s(serialized.serviceIDs, sizeof(serialized.serviceIDs), getServiceIDsAsString(m_serviceIDs).c_str());
serialized.discountPercentage = m_discountPercentage;
serialized.status = m_status;
return serialized;
std::ostringstream serializedComboPackage;
serializedComboPackage << m_id << ','
<< m_packageName << ','
<< m_discountPercentage << ','
<< getServiceIDsAsString(m_serviceIDs) << ','
<< util::getStateString(m_status);
return serializedComboPackage.str();
}
/*
Function: deserialize
Description: Deserializes a SerializedComboPackage record into a ComboPackage object.
Description: Deserializes a CSV-formatted string into a ComboPackage object.
Parameters:
- serializedComboPackage: const SerializedComboPackage&, serialized combo package record
- record: const std::string&, serialized combo package record
Returns:
- ComboPackage*: Pointer to the deserialized ComboPackage object
Throws:
- std::runtime_error if data is invalid
*/
ComboPackage* ComboPackage::deserialize(const SerializedComboPackage& serializedComboPackage)
ComboPackage* ComboPackage::deserialize(const std::string& record)
{
util::Vector<std::string> serviceIDs = getServiceIDsAsVector(serializedComboPackage.serviceIDs);
std::string id, packageName;
std::string discountPercentageString, serviceIDsString, statusString;
double discountPercentage;
std::istringstream serializedComboPackage(record);
getline(serializedComboPackage, id, ',');
getline(serializedComboPackage, packageName, ',');
getline(serializedComboPackage, discountPercentageString, ',');
getline(serializedComboPackage, serviceIDsString, ',');
getline(serializedComboPackage, statusString, ',');
try
{
discountPercentage = std::stod(discountPercentageString);
}
catch (...)
{
throw std::runtime_error("Invalid combo package data");
}
util::Vector<std::string> serviceIDs = getServiceIDsAsVector(serviceIDsString);
util::State status = util::getState(statusString);
return Factory::getObject<ComboPackage>(
serializedComboPackage.id,
serializedComboPackage.packageName,
serializedComboPackage.discountPercentage,
id,
packageName,
discountPercentage,
serviceIDs,
serializedComboPackage.status);
}
status
);
}
/*
Function: getHeaders
Description: Retrieves the CSV headers for combo package serialization.
Parameters:
- None
Returns:
- std::string: Header string ("ID,PackageName,DiscountPercentage,ServiceIDs,Status")
*/
std::string ComboPackage::getHeaders()
{
return "ID,PackageName,DiscountPercentage,ServiceIDs,Status";
}
@@ -12,7 +12,6 @@ Date: 19-May-2026
#include "Enums.h"
class Service;
struct SerializedComboPackage;
class ComboPackage
{
@@ -39,6 +38,7 @@ public:
void setDiscountPercentage(double discountPercentage);
void setServices(const util::Map<std::string, Service*>& services);
void setState(util::State status);
SerializedComboPackage serialize() const;
static ComboPackage* deserialize(const SerializedComboPackage&);
std::string serialize() const;
static ComboPackage* deserialize(const std::string&);
static std::string getHeaders();
};
@@ -8,7 +8,6 @@ Date: 19-May-2026
#include <sstream>
#include <stdexcept>
#include "SerializedRecords.h"
#include "Factory.h"
#include "StringHelper.h"
#include "InventoryItem.h"
@@ -28,8 +27,7 @@ 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
@@ -47,8 +45,7 @@ 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)
@@ -209,37 +206,73 @@ void InventoryItem::setState(util::State status)
/*
Function: serialize
Description: Serializes the InventoryItem object into a SerializedInventoryItem record.
Description: Serializes the inventory item into a CSV-formatted string.
Parameters:
- None
Returns:
- SerializedInventoryItem: Serialized representation of the inventory item
- std::string: Serialized inventory item record
*/
SerializedInventoryItem InventoryItem::serialize() const
std::string InventoryItem::serialize() const
{
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;
std::ostringstream serializedInventoryItem;
serializedInventoryItem << m_id << ','
<< m_partName << ','
<< m_quantity << ','
<< m_price << ','
<< util::getStateString(m_status);
return serializedInventoryItem.str();
}
/*
Function: deserialize
Description: Deserializes a SerializedInventoryItem record into an InventoryItem object.
Description: Deserializes a CSV-formatted string into an InventoryItem object.
Parameters:
- serializedInventoryItem: const SerializedInventoryItem&, serialized inventory item record
- record: const std::string&, serialized inventory item record
Returns:
- InventoryItem*: Pointer to the deserialized InventoryItem object
Throws:
- std::runtime_error if data is invalid
*/
InventoryItem* InventoryItem::deserialize(const SerializedInventoryItem& serializedInventoryItem)
InventoryItem* InventoryItem::deserialize(const std::string& record)
{
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>(
serializedInventoryItem.id,
serializedInventoryItem.partName,
serializedInventoryItem.quantity,
serializedInventoryItem.price,
serializedInventoryItem.status);
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";
}
@@ -6,12 +6,11 @@ Author: Trenser
Date: 19-May-2026
*/
#pragma once
#include <string>
#include "Enums.h"
struct SerializedInventoryItem;
class InventoryItem
{
private:
@@ -35,6 +34,7 @@ public:
void setQuantity(int quantity);
void setPrice(double price);
void setState(util::State status);
SerializedInventoryItem serialize() const;
static InventoryItem* deserialize(const SerializedInventoryItem&);
std::string serialize() const;
static InventoryItem* deserialize(const std::string&);
static std::string getHeaders();
};
@@ -9,7 +9,6 @@ Date: 19-May-2026
#include <sstream>
#include <stdexcept>
#include "SerializedRecords.h"
#include "Invoice.h"
#include "Factory.h"
#include "InventoryItem.h"
@@ -35,8 +34,7 @@ Invoice::Invoice()
m_discountPercentage(0.0),
m_totalAmount(0.0),
m_paymentMethod(util::PaymentMode()),
m_status(util::PaymentStatus()) {
}
m_status(util::PaymentStatus()) {}
/*
Function: Invoice
@@ -59,16 +57,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),
@@ -80,7 +78,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();
@@ -475,50 +473,100 @@ static util::Vector<std::string> getPartIDsAsVector(const std::string& partIDsSt
/*
Function: serialize
Description: Serializes the Invoice object into a SerializedInvoice record.
Description: Serializes the invoice into a CSV-formatted string.
Parameters:
- None
Returns:
- SerializedInvoice: Serialized representation of the invoice
- std::string: Serialized invoice record
*/
SerializedInvoice Invoice::serialize() const
std::string Invoice::serialize() const
{
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;
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();
}
/*
Function: deserialize
Description: Deserializes a SerializedInvoice record into an Invoice object.
Description: Deserializes a CSV-formatted string into an Invoice object.
Parameters:
- serializedInvoice: const SerializedInvoice&, serialized invoice record
- record: const std::string&, serialized invoice record
Returns:
- Invoice*: Pointer to the deserialized Invoice object
Throws:
- std::runtime_error if data is invalid
*/
Invoice* Invoice::deserialize(const SerializedInvoice& serializedInvoice)
Invoice* Invoice::deserialize(const std::string& record)
{
util::Vector<std::string> partIDs = getPartIDsAsVector(serializedInvoice.partIDs);
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);
return Factory::getObject<Invoice>(
serializedInvoice.id,
serializedInvoice.bookingId,
serializedInvoice.invoiceDate,
id,
bookingId,
invoiceDate,
partIDs,
serializedInvoice.laborCost,
serializedInvoice.partsCost,
serializedInvoice.discountPercentage,
serializedInvoice.totalAmount,
serializedInvoice.paymentDate,
serializedInvoice.paymentMethod,
serializedInvoice.status);
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";
}
@@ -6,6 +6,7 @@ Author: Trenser
Date: 19-May-2026
*/
#pragma once
#include <string>
#include "Map.h"
@@ -15,7 +16,6 @@ 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,6 +87,7 @@ public:
void setPaymentDate(const util::Timestamp& paymentDate);
void setPaymentMethod(util::PaymentMode paymentMethod);
void setStatus(util::PaymentStatus status);
SerializedInvoice serialize() const;
static Invoice* deserialize(const SerializedInvoice&);
std::string serialize() const;
static Invoice* deserialize(const std::string&);
static std::string getHeaders();
};
@@ -9,7 +9,6 @@ Date:19-May-2026
#include <sstream>
#include <stdexcept>
#include "SerializedRecords.h"
#include "JobCard.h"
#include "Factory.h"
#include "StringHelper.h"
@@ -29,8 +28,7 @@ JobCard::JobCard()
m_booking(nullptr),
m_service(nullptr),
m_technician(nullptr),
m_status(util::ServiceJobStatus()) {
}
m_status(util::ServiceJobStatus()) {}
/*
Function: JobCard
@@ -67,8 +65,7 @@ JobCard::JobCard(const std::string& bookingId,
m_technician(technician),
m_assignedDate(assignedDate),
m_status(status),
m_completionDate(completionDate) {
}
m_completionDate(completionDate) {}
/*
Function: JobCard (parameterized constructor with ID)
@@ -354,41 +351,79 @@ void JobCard::setCompletionDate(const util::Timestamp& completionDate)
/*
Function: serialize
Description: Serializes the JobCard object into a SerializedJobCard record.
Description: Serializes the job card into a CSV-formatted string.
Parameters:
- None
Returns:
- SerializedJobCard: Serialized representation of the job card
- std::string: Serialized job card record
*/
SerializedJobCard JobCard::serialize() const
std::string JobCard::serialize() const
{
SerializedJobCard 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.serviceId, sizeof(serialized.serviceId), m_serviceId.c_str());
strcpy_s(serialized.technicianId, sizeof(serialized.technicianId), m_technicianId.c_str());
serialized.assignedDate = m_assignedDate;
serialized.status = m_status;
serialized.completionDate = m_completionDate;
return serialized;
std::ostringstream serializedJobCard;
serializedJobCard << m_id << ','
<< m_bookingId << ','
<< m_serviceId << ','
<< m_technicianId << ','
<< m_assignedDate.toString() << ','
<< util::getServiceJobStatusString(m_status) << ','
<< m_completionDate.toString();
return serializedJobCard.str();
}
/*
Function: deserialize
Description: Deserializes a SerializedJobCard record into a JobCard object.
Description: Deserializes a CSV-formatted string into a JobCard object.
Parameters:
- serializedJobCard: const SerializedJobCard&, serialized job card record
- record: const std::string&, serialized job card record
Returns:
- JobCard*: Pointer to the deserialized JobCard object
Throws:
- std::runtime_error if timestamp parsing fails
*/
JobCard* JobCard::deserialize(const SerializedJobCard& serializedJobCard)
JobCard* JobCard::deserialize(const std::string& record)
{
std::string id, bookingId, serviceId, technicianId;
std::string assignedDateString, statusString, completionDateString;
std::istringstream serializedJobCard(record);
getline(serializedJobCard, id, ',');
getline(serializedJobCard, bookingId, ',');
getline(serializedJobCard, serviceId, ',');
getline(serializedJobCard, technicianId, ',');
getline(serializedJobCard, assignedDateString, ',');
getline(serializedJobCard, statusString, ',');
getline(serializedJobCard, completionDateString, ',');
util::Timestamp assignedDate;
util::Timestamp completionDate;
try
{
assignedDate = util::Timestamp::fromString(assignedDateString);
completionDate = util::Timestamp::fromString(completionDateString);
}
catch (...)
{
throw std::runtime_error("Invalid timestamp");
}
util::ServiceJobStatus status = util::getServiceJobStatus(statusString);
return Factory::getObject<JobCard>(
serializedJobCard.id,
serializedJobCard.bookingId,
serializedJobCard.serviceId,
serializedJobCard.technicianId,
serializedJobCard.assignedDate,
serializedJobCard.status,
serializedJobCard.completionDate);
id,
bookingId,
serviceId,
technicianId,
assignedDate,
status,
completionDate
);
}
/*
Function: getHeaders
Description: Retrieves the CSV headers for job card serialization.
Parameters:
- None
Returns:
- std::string: Header string ("ID,BookingID,ServiceID,TechnicianID,AssignedDate,Status,CompletionDate")
*/
std::string JobCard::getHeaders()
{
return "ID,BookingID,ServiceID,TechnicianID,AssignedDate,Status,CompletionDate";
}
@@ -15,7 +15,6 @@ Date:19-May-2026
class ServiceBooking;
class Service;
class User;
struct SerializedJobCard;
class JobCard
{
@@ -35,11 +34,11 @@ public:
JobCard();
JobCard(const std::string& bookingId,
ServiceBooking* booking,
Service* service,
const std::string& serviceId,
const std::string& technicianId,
User* technician,
const util::Timestamp& assignedDate,
Service* service,
const std::string& serviceId,
const std::string& technicianId,
User* technician,
const util::Timestamp& assignedDate,
util::ServiceJobStatus status,
const util::Timestamp& completionDate
);
@@ -71,6 +70,7 @@ public:
void setAssignedDate(const util::Timestamp& assignedDate);
void setStatus(util::ServiceJobStatus status);
void setCompletionDate(const util::Timestamp& completionDate);
SerializedJobCard serialize() const;
static JobCard* deserialize(const SerializedJobCard&);
std::string serialize() const;
static JobCard* deserialize(const std::string&);
static std::string getHeaders();
};
@@ -1,13 +1,12 @@
/*
File: Notification.cpp
Description: Implements the Notification class which represents system notifications in the Vehicle Service Management System.
Provides constructors, accessors, and mutators for notification details such as ID, recipientID, title, message, and timestamp.
Provides constructors, accessors, and mutators for notification details such as ID, recipient, title, message, and timestamp.
Author: Trenser
Date: 19-May-2026
*/
#include <sstream>
#include "SerializedRecords.h"
#include "Notification.h"
#include "StringHelper.h"
#include "Factory.h"
@@ -23,8 +22,8 @@ Returns:
- A new Notification object.
*/
Notification::Notification()
: m_id("NOT" + std::to_string(++m_uid)),
m_state(util::State::ACTIVE) {}
: m_id("NOT" + std::to_string(++m_uid)),
m_recipient(nullptr) {}
/*
Function: Notification
@@ -38,14 +37,13 @@ Parameters:
Returns:
- A new Notification object.
*/
Notification::Notification(const std::string& recipientUserId, const std::string& title, const std::string& message, const util::Timestamp& createdAt)
Notification::Notification(const std::string& recipientUserId, User* recipient, const std::string& title, const std::string& message, const util::Timestamp& createdAt)
: m_id("NOT" + std::to_string(++m_uid)),
m_recipientUserId(recipientUserId),
m_recipient(recipient),
m_title(title),
m_message(message),
m_state(util::State::ACTIVE),
m_createdAt(createdAt) {
}
m_createdAt(createdAt) {}
/*
Function: Notification (parameterized constructor with ID)
@@ -60,13 +58,13 @@ Parameters:
Returns:
- A new Notification object
*/
Notification::Notification(const std::string& id, const std::string& recipientUserId, const std::string& title, const std::string& message, const util::Timestamp& createdAt, const util::State& state)
Notification::Notification(const std::string& id, const std::string& recipientUserId, const std::string& title, const std::string& message, const util::Timestamp& createdAt)
: m_id(id),
m_recipientUserId(recipientUserId),
m_recipient(nullptr),
m_title(title),
m_message(message),
m_createdAt(createdAt),
m_state(state)
m_createdAt(createdAt)
{
int idNumber = util::extractNumber(m_id);
if (idNumber > m_uid)
@@ -81,7 +79,7 @@ Description: Retrieves the unique ID of the notification.
Returns:
- const std::string& representing the notification ID.
*/
const std::string& Notification::getId() const
const std::string& Notification::getId() const
{
return m_id;
}
@@ -97,6 +95,17 @@ const std::string& Notification::getRecipientUserId() const
return m_recipientUserId;
}
/*
Function: getRecipient
Description: Retrieves the pointer to the recipient user.
Returns:
- User* representing the recipient.
*/
User* Notification::getRecipient() const
{
return m_recipient;
}
/*
Function: getTitle
Description: Retrieves the title of the notification.
@@ -130,17 +139,6 @@ const util::Timestamp& Notification::getCreatedAt() const
return m_createdAt;
}
/*
Function: getState
Description: Retrieves the Notification state
Returns:
- const util::Timestamp& representing the creation timestamp.
*/
util::State Notification::getState() const
{
return m_state;
}
/*
Function: setId
Description: Sets the unique ID of the notification.
@@ -167,6 +165,19 @@ void Notification::setRecipientUserId(const std::string& recipientUserId)
m_recipientUserId = recipientUserId;
}
/*
Function: setRecipient
Description: Sets the recipient user pointer for the notification.
Parameters:
- recipient: Pointer to the User object.
Returns:
- void
*/
void Notification::setRecipient(User* recipient)
{
m_recipient = recipient;
}
/*
Function: setTitle
Description: Sets the title of the notification.
@@ -206,54 +217,71 @@ void Notification::setCreatedAt(const util::Timestamp& createdAt)
m_createdAt = createdAt;
}
/*
Function: setState
Description: Sets the Notification state.
Parameters:
- state: Notification state value.
Returns:
- void
*/
void Notification::setState(util::State state)
{
m_state = state;
}
/*
Function: serialize
Description: Serializes the Notification object into a SerializedNotification record.
Description: Serializes the notification into a CSV-formatted string.
Parameters:
- None
Returns:
- SerializedNotification: Serialized representation of the notification
- std::string: Serialized notification record
*/
SerializedNotification Notification::serialize() const
std::string Notification::serialize() const
{
SerializedNotification serialized = {};
strcpy_s(serialized.id, sizeof(serialized.id), m_id.c_str());
strcpy_s(serialized.recipientUserId, sizeof(serialized.recipientUserId), m_recipientUserId.c_str());
strcpy_s(serialized.title, sizeof(serialized.title), m_title.c_str());
strcpy_s(serialized.message, sizeof(serialized.message), m_message.c_str());
serialized.createdAt = m_createdAt;
serialized.state = m_state;
return serialized;
std::ostringstream serializedNotification;
serializedNotification << m_id << ','
<< m_recipientUserId << ','
<< m_title << ','
<< m_message << ','
<< m_createdAt.toString();
return serializedNotification.str();
}
/*
Function: deserialize
Description: Deserializes a SerializedNotification record into a Notification object.
Description: Deserializes a CSV-formatted string into a Notification object.
Parameters:
- serializedNotification: const SerializedNotification&, serialized notification record
- record: const std::string&, serialized notification record
Returns:
- Notification*: Pointer to the deserialized Notification object
Throws:
- std::runtime_error if timestamp parsing fails
*/
Notification* Notification::deserialize(const SerializedNotification& serializedNotification)
Notification* Notification::deserialize(const std::string& record)
{
std::string id, recipientUserId, title, message, createdAtTimestampString;
std::istringstream serializedNotification(record);
getline(serializedNotification, id, ',');
getline(serializedNotification, recipientUserId, ',');
getline(serializedNotification, title, ',');
getline(serializedNotification, message, ',');
getline(serializedNotification, createdAtTimestampString, ',');
util::Timestamp createdAtTimestamp;
try
{
createdAtTimestamp = util::Timestamp::fromString(createdAtTimestampString);
}
catch (...)
{
throw std::runtime_error("Invalid createdAt timestamp");
}
return Factory::getObject<Notification>(
serializedNotification.id,
serializedNotification.recipientUserId,
serializedNotification.title,
serializedNotification.message,
serializedNotification.createdAt,
serializedNotification.state);
id,
recipientUserId,
title,
message,
createdAtTimestamp
);
}
/*
Function: getHeaders
Description: Retrieves the CSV headers for notification serialization.
Parameters:
- None
Returns:
- std::string: Header string ("ID,RecipientID,Title,Message,Timestamp")
*/
std::string Notification::getHeaders()
{
return "ID,RecipientID,Title,Message,Timestamp";
}
@@ -9,10 +9,8 @@ Date: 19-May-2026
#pragma once
#include <string>
#include "Timestamp.h"
#include "Enums.h"
class User;
struct SerializedNotification;
class Notification
{
@@ -20,26 +18,27 @@ private:
static int m_uid;
std::string m_id;
std::string m_recipientUserId;
User* m_recipient;
std::string m_title;
std::string m_message;
util::Timestamp m_createdAt;
util::State m_state;
public:
Notification();
Notification(const std::string& recipientUserId, const std::string& title, const std::string& message, const util::Timestamp& createdAt);
Notification(const std::string& id, const std::string& recipientUserId, const std::string& title, const std::string& message, const util::Timestamp& createdAt, const util::State& state);
Notification(const std::string& recipientUserId, User* recipient, const std::string& title, const std::string& message, const util::Timestamp& createdAt);
Notification(const std::string& id, const std::string& recipientUserId, const std::string& title, const std::string& message, const util::Timestamp& createdAt);
const std::string& getId() const;
const std::string& getRecipientUserId() const;
User* getRecipient() const;
const std::string& getTitle() const;
const std::string& getMessage() const;
const util::Timestamp& getCreatedAt() const;
void setId(const std::string& id);
void setRecipientUserId(const std::string& recipientUserId);
void setRecipient(User* recipient);
void setTitle(const std::string& title);
void setMessage(const std::string& message);
void setCreatedAt(const util::Timestamp& createdAt);
util::State getState() const;
void setState(util::State state);
SerializedNotification serialize() const;
static Notification* deserialize(const SerializedNotification&);
std::string serialize() const;
static Notification* deserialize(const std::string&);
static std::string getHeaders();
};
@@ -8,7 +8,6 @@ Date: 19-May-2026
*/
#include <sstream>
#include "SerializedRecords.h"
#include "Service.h"
#include "InventoryItem.h"
#include "StringHelper.h"
@@ -28,8 +27,7 @@ Returns:
Service::Service()
: m_id("SRV" + std::to_string(++m_uid)),
m_status(util::State::ACTIVE),
m_laborCost(0.0) {
}
m_laborCost(0.0) {}
/*
Function: Service
@@ -46,7 +44,7 @@ Service::Service(const std::string& name, const util::Map<std::string, Inventory
m_name(name),
m_requiredInventoryItems(requiredInventoryItems),
m_status(util::State::ACTIVE),
m_laborCost(laborCost)
m_laborCost(laborCost)
{
int numberOfInventoryItems = m_requiredInventoryItems.getSize();
auto inventoryItemPointers = m_requiredInventoryItems.getValues();
@@ -268,38 +266,72 @@ static util::Vector<std::string> getInventoryItemIDsAsVector(const std::string&
/*
Function: serialize
Description: Serializes the Service object into a SerializedService record.
Description: Serializes the service into a CSV-formatted string.
Parameters:
- None
Returns:
- SerializedService: Serialized representation of the service
- std::string: Serialized service record
*/
SerializedService Service::serialize() const
std::string Service::serialize() const
{
SerializedService serialized = {};
strcpy_s(serialized.id, sizeof(serialized.id), m_id.c_str());
strcpy_s(serialized.name, sizeof(serialized.name), m_name.c_str());
strcpy_s(serialized.inventoryItemIDs, sizeof(serialized.inventoryItemIDs), getInventoryItemIDsAsString(m_requiredInventoryItemIDs).c_str());
serialized.laborCost = m_laborCost;
serialized.status = m_status;
return serialized;
std::ostringstream serializedService;
serializedService << m_id << ','
<< m_name << ','
<< getInventoryItemIDsAsString(m_requiredInventoryItemIDs) << ','
<< m_laborCost << ','
<< util::getStateString(m_status);
return serializedService.str();
}
/*
Function: deserialize
Description: Deserializes a SerializedService record into a Service object.
Description: Deserializes a CSV-formatted string into a Service object.
Parameters:
- serializedService: const SerializedService&, serialized service record
- record: const std::string&, serialized service record
Returns:
- Service*: Pointer to the deserialized Service object
Throws:
- std::runtime_error if labor cost parsing fails
*/
Service* Service::deserialize(const SerializedService& serializedService)
Service* Service::deserialize(const std::string& record)
{
util::Vector<std::string> inventoryItemIDs = getInventoryItemIDsAsVector(serializedService.inventoryItemIDs);
std::string id, name;
std::string inventoryItemIDsString, laborCostString, statusString;
double laborCost;
std::istringstream serializedService(record);
getline(serializedService, id, ',');
getline(serializedService, name, ',');
getline(serializedService, inventoryItemIDsString, ',');
getline(serializedService, laborCostString, ',');
getline(serializedService, statusString, ',');
util::Vector<std::string> inventoryItemIDs = getInventoryItemIDsAsVector(inventoryItemIDsString);
try
{
laborCost = std::stod(laborCostString);
}
catch (...)
{
throw std::runtime_error("Invalid labor cost");
}
util::State status = util::getState(statusString);
return Factory::getObject<Service>(
serializedService.id,
serializedService.name,
id,
name,
inventoryItemIDs,
serializedService.laborCost,
serializedService.status);
laborCost,
status
);
}
/*
Function: getHeaders
Description: Retrieves the CSV headers for service serialization.
Parameters:
- None
Returns:
- std::string: Header string ("ID,Name,InventoryIDs,LaborCost,Status")
*/
std::string Service::getHeaders()
{
return "ID,Name,InventoryIDs,LaborCost,Status";
}
@@ -6,6 +6,7 @@ Author: Trenser
Date: 19-May-2026
*/
#pragma once
#include <string>
#include "Map.h"
@@ -13,7 +14,6 @@ Date: 19-May-2026
#include "Enums.h"
class InventoryItem;
struct SerializedService;
class Service
{
@@ -40,6 +40,7 @@ public:
void setRequiredInventoryItems(const util::Map<std::string, InventoryItem*>& requiredInventoryItems);
void setLaborCost(double laborCost);
void setState(util::State status);
SerializedService serialize() const;
static Service* deserialize(const SerializedService&);
std::string serialize() const;
static Service* deserialize(const std::string&);
static std::string getHeaders();
};
@@ -6,10 +6,8 @@ Description: Implementation file containing the method definitions of the
Author: Trenser
Date:19-May-2026
*/
#include <stdexcept>
#include <sstream>
#include "SerializedRecords.h"
#include "ServiceBooking.h"
#include "Service.h"
#include "Enums.h"
@@ -30,8 +28,7 @@ ServiceBooking::ServiceBooking()
m_customer(nullptr),
m_assignedTechnician(nullptr),
m_status(util::ServiceJobStatus::PENDING),
m_discountPercentage(0.0) {
}
m_discountPercentage(0.0) {}
/*
Function: ServiceBooking
@@ -440,46 +437,84 @@ static util::Vector<std::string> getServiceIDsAsVector(const std::string& servic
/*
Function: serialize
Description: Serializes the ServiceBooking object into a SerializedServiceBooking record.
Description: Serializes the service booking into a CSV-formatted string.
Parameters:
- None
Returns:
- SerializedServiceBooking: Serialized representation of the service booking
- std::string: Serialized booking record
*/
SerializedServiceBooking ServiceBooking::serialize() const
std::string ServiceBooking::serialize() const
{
SerializedServiceBooking serialized = {};
strcpy_s(serialized.id, sizeof(serialized.id), m_id.c_str());
strcpy_s(serialized.serviceIDs, sizeof(serialized.serviceIDs), getServiceIDsAsString(m_serviceIDs).c_str());
strcpy_s(serialized.customerId, sizeof(serialized.customerId), m_customerId.c_str());
strcpy_s(serialized.vehicleNumber, sizeof(serialized.vehicleNumber), m_vehicleNumber.c_str());
strcpy_s(serialized.vehicleBrand, sizeof(serialized.vehicleBrand), m_vehicleBrand.c_str());
strcpy_s(serialized.vehicleModel, sizeof(serialized.vehicleModel), m_vehicleModel.c_str());
strcpy_s(serialized.assignedTechnicianId, sizeof(serialized.assignedTechnicianId), m_assignedTechnicianId.c_str());
serialized.status = m_status;
serialized.discountPercentage = m_discountPercentage;
return serialized;
std::ostringstream serializedBooking;
serializedBooking << m_id << ','
<< util::getServiceJobStatusString(m_status) << ','
<< getServiceIDsAsString(m_serviceIDs) << ','
<< m_customerId << ','
<< m_vehicleNumber << ','
<< m_vehicleBrand << ','
<< m_vehicleModel << ','
<< m_assignedTechnicianId << ','
<< m_discountPercentage << ',';
return serializedBooking.str();
}
/*
Function: deserialize
Description: Deserializes a SerializedServiceBooking record into a ServiceBooking object.
Description: Deserializes a CSV-formatted string into a ServiceBooking object.
Parameters:
- serializedServiceBooking: const SerializedServiceBooking&, serialized service booking record
- record: const std::string&, serialized booking record
Returns:
- ServiceBooking*: Pointer to the deserialized ServiceBooking object
Throws:
- std::runtime_error if discount percentage parsing fails
*/
ServiceBooking* ServiceBooking::deserialize(const SerializedServiceBooking& serializedServiceBooking)
ServiceBooking* ServiceBooking::deserialize(const std::string& record)
{
util::Vector<std::string> serviceIDs = getServiceIDsAsVector(serializedServiceBooking.serviceIDs);
std::string id, customerId, vehicleNumber, vehicleBrand, vehicleModel, assignedTechnicianId;
std::string serviceJobStatusString, serviceIDsString, discountPercentageString;
double discountPercentage;
std::istringstream serializedBooking(record);
getline(serializedBooking, id, ',');
getline(serializedBooking, serviceJobStatusString, ',');
getline(serializedBooking, serviceIDsString, ',');
getline(serializedBooking, customerId, ',');
getline(serializedBooking, vehicleNumber, ',');
getline(serializedBooking, vehicleBrand, ',');
getline(serializedBooking, vehicleModel, ',');
getline(serializedBooking, assignedTechnicianId, ',');
getline(serializedBooking, discountPercentageString, ',');
util::Vector<std::string> serviceIDs = getServiceIDsAsVector(serviceIDsString);
try
{
discountPercentage = std::stod(discountPercentageString);
}
catch (...)
{
throw std::runtime_error("Invalid discount percentage");
}
util::ServiceJobStatus status = util::getServiceJobStatus(serviceJobStatusString);
return Factory::getObject<ServiceBooking>(
serializedServiceBooking.id,
serializedServiceBooking.status,
id,
status,
serviceIDs,
serializedServiceBooking.customerId,
serializedServiceBooking.vehicleNumber,
serializedServiceBooking.vehicleBrand,
serializedServiceBooking.vehicleModel,
serializedServiceBooking.assignedTechnicianId,
serializedServiceBooking.discountPercentage);
customerId,
vehicleNumber,
vehicleBrand,
vehicleModel,
assignedTechnicianId,
discountPercentage
);
}
/*
Function: getHeaders
Description: Retrieves the CSV headers for service booking serialization.
Parameters:
- None
Returns:
- std::string: Header string ("ID,Status,ServiceIDs,CustomerID,VehicleNumber,VehicleBrand,VehicleModel,AssignedTechnicianID,DiscountPercentage")
*/
std::string ServiceBooking::getHeaders()
{
return "ID,Status,ServiceIDs,CustomerID,VehicleNumber,VehicleBrand,VehicleModel,AssignedTechnicianID,DiscountPercentage";
}
@@ -6,7 +6,6 @@ Description: Header file declaring the ServiceBooking class, which represents
Author: Trenser
Date:19-May-2026
*/
#pragma once
#include <string>
#include "Map.h"
@@ -15,7 +14,6 @@ Date:19-May-2026
class Service;
class User;
struct SerializedServiceBooking;
class ServiceBooking
{
@@ -80,6 +78,7 @@ public:
void setAssignedTechnicianId(const std::string& assignedTechnicianId);
void setAssignedTechnician(User* assignedTechnician);
void setDiscountPercentage(double discountPercentage);
SerializedServiceBooking serialize() const;
static ServiceBooking* deserialize(const SerializedServiceBooking&);
std::string serialize() const;
static ServiceBooking* deserialize(const std::string&);
static std::string getHeaders();
};
@@ -8,7 +8,6 @@ Date: 19-May-2026
*/
#include <sstream>
#include "SerializedRecords.h"
#include "User.h"
#include "Notification.h"
#include "Enums.h"
@@ -29,8 +28,7 @@ Returns:
User::User()
: m_id("USR" + std::to_string(++m_uid)),
m_type(util::UserType::CUSTOMER),
m_status(util::State::ACTIVE) {
}
m_status(util::State::ACTIVE) {}
/*
Function: User
@@ -53,8 +51,7 @@ User::User(const std::string& userName, const std::string& password, const std::
m_phone(phone),
m_email(email),
m_type(role),
m_status(util::State::ACTIVE) {
}
m_status(util::State::ACTIVE) {}
/*
Function: User (parameterized constructor with ID)
@@ -89,6 +86,23 @@ User::User(const std::string& userId, const std::string& userName, const std::st
}
}
/*
Function: ~User
Description: Destructor that cleans up dynamically allocated notifications associated with the user.
Parameters:
- None
Returns:
- void
*/
User::~User()
{
auto values = m_notifications.getValues();
for (int index = 0; index < values.getSize(); index++)
{
delete values[index];
}
}
/*
Function: getId
Description: Retrieves the unique ID of the user.
@@ -155,6 +169,17 @@ const std::string& User::getEmail() const
return m_email;
}
/*
Function: getNotifications
Description: Retrieves the map of notifications associated with the user.
Returns:
- util::Map<std::string, Notification*>& representing the notifications.
*/
util::Map<std::string, Notification*>& User::getNotifications()
{
return m_notifications;
}
/*
Function: getUserType
Description: Retrieves the role of the user.
@@ -255,6 +280,22 @@ void User::setEmail(const std::string& email)
m_email = email;
}
/*
Function: addNotification
Description: Adds a new notification to the users notification map.
Parameters:
- notification: Pointer to the Notification object.
Returns:
- void
*/
void User::addNotification(Notification* notification)
{
if (notification)
{
m_notifications.insert(notification->getId(), notification);
}
}
/*
Function: setRole
Description: Sets the role of the user.
@@ -283,43 +324,68 @@ void User::setState(util::State status)
/*
Function: serialize
Description: Serializes the User object into a SerializedUser record.
Description: Serializes the user into a CSV-formatted string.
Parameters:
- None
Returns:
- SerializedUser: Serialized representation of the user
- std::string: Serialized user record
*/
SerializedUser User::serialize() const
std::string User::serialize() const
{
SerializedUser serialized = {};
strcpy_s(serialized.id, sizeof(serialized.id), m_id.c_str());
strcpy_s(serialized.username, sizeof(serialized.username), m_userName.c_str());
strcpy_s(serialized.password, sizeof(serialized.password), m_password.c_str());
strcpy_s(serialized.name, sizeof(serialized.name), m_name.c_str());
strcpy_s(serialized.phone, sizeof(serialized.phone), m_phone.c_str());
strcpy_s(serialized.email, sizeof(serialized.email), m_email.c_str());
serialized.userType = m_type;
serialized.status = m_status;
return serialized;
std::ostringstream serializedUser;
serializedUser << m_id << ','
<< m_userName << ','
<< m_password << ','
<< m_name << ','
<< m_phone << ','
<< m_email << ','
<< util::getUserTypeString(m_type) << ','
<< util::getStateString(m_status);
return serializedUser.str();
}
/*
Function: deserialize
Description: Deserializes a SerializedUser record into a User object.
Description: Deserializes a CSV-formatted string into a User object.
Parameters:
- serializedUser: const SerializedUser&, serialized user record
- record: const std::string&, serialized user record
Returns:
- User*: Pointer to the deserialized User object
*/
User* User::deserialize(const SerializedUser& serializedUser)
User* User::deserialize(const std::string& record)
{
return Factory::getObject<User>(
serializedUser.id,
serializedUser.username,
serializedUser.password,
serializedUser.name,
serializedUser.phone,
serializedUser.email,
serializedUser.userType,
serializedUser.status);
std::string id, name, username, phone, password, email;
std::string userTypeString, stateString;
std::istringstream serializedUser(record);
getline(serializedUser, id, ',');
getline(serializedUser, username, ',');
getline(serializedUser, password, ',');
getline(serializedUser, name, ',');
getline(serializedUser, phone, ',');
getline(serializedUser, email, ',');
getline(serializedUser, userTypeString, ',');
getline(serializedUser, stateString);
util::UserType userType = util::getUserType(userTypeString);
util::State status = util::getState(stateString);
return Factory::getObject<User>(id,
username,
password,
name,
phone,
email,
userType,
status);
}
/*
Function: getHeaders
Description: Retrieves the CSV headers for user serialization.
Parameters:
- None
Returns:
- std::string: Header string ("ID,Username,Password,Name,Phone,Email,UserType,UserStatus")
*/
std::string User::getHeaders()
{
return "ID,Username,Password,Name,Phone,Email,UserType,UserStatus";
}
@@ -14,7 +14,6 @@ Date: 19-May-2026
#include "Enums.h"
class Notification;
struct SerializedUser;
class User : public Observer
{
@@ -26,19 +25,21 @@ private:
std::string m_name;
std::string m_phone;
std::string m_email;
util::Map<std::string, Notification*> m_notifications;
util::UserType m_type;
util::State m_status;
public:
User();
User(const std::string& userName, const std::string& password, const std::string& name, const std::string& phone, const std::string& email, util::UserType role);
User(const std::string& userId, const std::string& userName, const std::string& password, const std::string& name, const std::string& phone, const std::string& email, util::UserType role, util::State status);
~User() = default;
~User();
const std::string& getId() const;
const std::string& getUserName() const;
const std::string& getPassword() const;
const std::string& getName() const;
const std::string& getPhone() const;
const std::string& getEmail() const;
util::Map<std::string, Notification*>& getNotifications();
util::UserType getUserType() const;
util::State getState() const;
void setId(const std::string& id);
@@ -47,8 +48,10 @@ public:
void setName(const std::string& name);
void setPhone(const std::string& phone);
void setEmail(const std::string& email);
void addNotification(Notification* notification) override;
void setRole(util::UserType role);
void setState(util::State status);
SerializedUser serialize() const;
static User* deserialize(const SerializedUser& serializedUser);
std::string serialize() const;
static User* deserialize(const std::string&);
static std::string getHeaders();
};
@@ -10,32 +10,8 @@ Date:19-May-2026
#include <stdexcept>
#include "AuthenticationManagementService.h"
#include "User.h"
#include "Utility.h"
#include "DataStoreLockGuard.h"
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
@@ -48,35 +24,16 @@ Return type: bool - true if login successful, false otherwise
*/
bool AuthenticationManagementService::login(const std::string& username, const std::string& password)
{
DataStoreLockGuard lock(m_dataStore);
auto& trackedUserMap = m_dataStore.getUsers();
int trackedUserMapSize = trackedUserMap.getSize();
for (int index = 0; index < trackedUserMapSize; index++)
util::Map<std::string, User*> users = m_dataStore.getUsers();
int usersMapSize = users.getSize();
for (int index = 0; index < usersMapSize; index++)
{
User* user = trackedUserMap.getValueAt(index).data;
User* user = users.getValueAt(index);
if (username == user->getUserName())
{
if (password == user->getPassword())
{
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 false;
@@ -105,11 +62,7 @@ Return type: void
*/
void AuthenticationManagementService::logout()
{
m_eventManager.shutdown();
m_authenticatedUser = nullptr;
m_isAuthorized = false;
m_accountDisabledEvent = NULL;
m_notificationsAvailableEvent = NULL;
}
/*
@@ -121,33 +74,9 @@ Return type: void
*/
void AuthenticationManagementService::changePassword(const std::string& newPassword)
{
AuthenticationManagementService::ensureAuthorization();
DataStoreLockGuard lock(m_dataStore);
auto& trackedUsersMap = m_dataStore.getUsers();
if (m_authenticatedUser == nullptr)
{
throw std::runtime_error("There is no user currently logged in!");
}
int index = trackedUsersMap.find(m_authenticatedUser->getId());
if (index == -1)
{
throw std::runtime_error("User does not exist!\n");
}
m_authenticatedUser->setPassword(newPassword);
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,8 +9,6 @@ Date:19-May-2026
#pragma once
#include <string>
#include <windows.h>
#include "EventManager.h"
#include "DataStore.h"
class User;
@@ -19,17 +17,11 @@ class AuthenticationManagementService
{
private:
static User* m_authenticatedUser;
static bool m_isAuthorized;
static EventManager m_eventManager;
static HANDLE m_accountDisabledEvent;
static HANDLE m_notificationsAvailableEvent;
DataStore& m_dataStore;
public:
AuthenticationManagementService() : m_dataStore(DataStore::getInstance()) {}
static void ensureAuthorization();
bool login(const std::string& username, const std::string& password);
void logout();
void changePassword(const std::string& newPassword);
User* getAuthenticatedUser();
void registerEvents(HANDLE accountDisabledEvent, HANDLE notificationAvailableEvent);
};
@@ -12,15 +12,14 @@ Date: 22-May-2026
#include "Config.h"
#include "Enums.h"
#include "Factory.h"
#include "FileManager.h"
#include "InventoryItem.h"
#include "InventoryManagementService.h"
#include "AuthenticationManagementService.h"
#include "Timestamp.h"
#include "User.h"
#include "Utility.h"
#include "Vector.h"
#include "DataStoreLockGuard.h"
#include "EventManager.h"
util::Map<std::string, User*> InventoryManagementService::m_observers{};
@@ -59,19 +58,18 @@ Returns:
*/
void InventoryManagementService::sendLowStockAlerts()
{
DataStoreLockGuard lock(m_dataStore);
auto& trackedInventoryItemsMap = m_dataStore.getInventoryItems();
auto& trackedUserMap = m_dataStore.getUsers();
if (trackedInventoryItemsMap.isEmpty())
auto& inventoryItems = m_dataStore.getInventoryItems();
if (inventoryItems.isEmpty())
{
return;
}
int inventoryItemsSize = trackedInventoryItemsMap.getSize();
int usersMapSize = trackedUserMap.getSize();
int inventoryItemsSize = inventoryItems.getSize();
auto& usersMap = m_dataStore.getUsers();
int usersMapSize = usersMap.getSize();
util::Vector<User*> adminUsers;
for (int index = 0; index < usersMapSize; index++)
{
User* user = trackedUserMap.getValueAt(index).data;
User* user = usersMap.getValueAt(index);
if (user->getUserType() == util::UserType::ADMIN)
{
adminUsers.push_back(user);
@@ -84,7 +82,7 @@ void InventoryManagementService::sendLowStockAlerts()
}
for (int index = 0; index < inventoryItemsSize; index++)
{
InventoryItem* inventoryItem = trackedInventoryItemsMap.getValueAt(index).data;
InventoryItem* inventoryItem = inventoryItems.getValueAt(index);
if (inventoryItem && inventoryItem->getQuantity() < config::threshold::INVENTORY_LOW_STOCK_THRESHOLD)
{
sendLowStockAlertsToAdmins(*this, inventoryItem, adminUsers);
@@ -92,6 +90,95 @@ void InventoryManagementService::sendLowStockAlerts()
}
}
/*
Function: getObserverIDs
Description: Retrieves the IDs of all observers currently attached to the
InventoryManagementService.
Parameters:
- None
Returns:
- util::Vector<std::string>: Vector of observer user IDs
*/
util::Vector<std::string> InventoryManagementService::getObserverIDs()
{
util::Vector<std::string> observerIDs;
int numberOfObservers = m_observers.getSize();
for (int index = 0; index < numberOfObservers; index++)
{
User* observer = m_observers.getValueAt(index);
if (observer)
{
observerIDs.push_back(observer->getId());
}
}
return observerIDs;
}
/*
Function: loadInventoryItems
Description: Loads inventory items from persistent storage into the datastore.
Uses FileManager to deserialize inventory items from the configured file.
Parameters:
- None
Returns:
- void
*/
void InventoryManagementService::loadInventoryItems()
{
util::FileManager<InventoryItem> inventoryItemFileManager(config::file::INVENTORYITEM_FILE);
auto& inventoryItems = m_dataStore.getInventoryItems();
auto inventoryItemsMap = inventoryItemFileManager.load();
int numberOfInventoryItems = inventoryItemsMap.getSize();
for (int index = 0; index < numberOfInventoryItems; index++)
{
inventoryItems[inventoryItemsMap.getKeyAt(index)] = inventoryItemsMap.getValueAt(index);
}
}
/*
Function: saveInventoryItems
Description: Saves inventory items from the datastore to persistent storage.
Uses FileManager to serialize inventory items into the configured file.
Parameters:
- None
Returns:
- void
*/
void InventoryManagementService::saveInventoryItems()
{
util::FileManager<InventoryItem> inventoryItemFileManager(config::file::INVENTORYITEM_FILE);
auto& inventoryItems = m_dataStore.getInventoryItems();
inventoryItemFileManager.save(inventoryItems);
}
/*
Function: loadObservers
Description: Loads observer IDs from persistent storage and attaches corresponding
users as observers to the InventoryManagementService.
Parameters:
- None
Returns:
- void
*/
void InventoryManagementService::loadObservers()
{
util::loadObservers(config::file::INVENTORYMANAGEMENTOBSERVERS, this, m_dataStore);
}
/*
Function: saveObservers
Description: Saves the current observer IDs of the InventoryManagementService
to persistent storage for future retrieval.
Parameters:
- None
Returns:
- void
*/
void InventoryManagementService::saveObservers()
{
util::saveObservers(config::file::INVENTORYMANAGEMENTOBSERVERS, this);
}
/*
Function: addInventoryItem
Description: Creates a new inventory item using the Factory and inserts it
@@ -103,12 +190,8 @@ Return type: void
*/
void InventoryManagementService::addInventoryItem(const std::string& partName, int quantity, double price)
{
AuthenticationManagementService::ensureAuthorization();
DataStoreLockGuard lock(m_dataStore);
auto& trackedInventoryItemMap = m_dataStore.getInventoryItems();
InventoryItem* newItem = Factory::getObject<InventoryItem>(partName, quantity, price);
trackedInventoryItemMap.insert(newItem->getId(), util::createNewRecord(newItem));
m_dataStore.saveInventoryItems();
m_dataStore.getInventoryItems().insert(newItem->getId(), newItem);
}
/*
@@ -120,23 +203,16 @@ Return type: void
*/
void InventoryManagementService::addInventoryItemStock(const std::string& selectedItemId, int quantity)
{
AuthenticationManagementService::ensureAuthorization();
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();
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);
}
}
}
/*
@@ -147,11 +223,7 @@ Return type: util::Map<std::string, InventoryItem*>
*/
util::Map<std::string, InventoryItem*> InventoryManagementService::getInventoryItems()
{
AuthenticationManagementService::ensureAuthorization();
DataStoreLockGuard lock(m_dataStore);
auto& trackedInventoryItemMap = m_dataStore.getInventoryItems();
auto inventoryMap = util::getObjects(trackedInventoryItemMap);
return inventoryMap;
return m_dataStore.getInventoryItems();
}
/*
@@ -162,22 +234,15 @@ Return type: void
*/
void InventoryManagementService::removeInventoryItem(const std::string& inventoryItemID)
{
AuthenticationManagementService::ensureAuthorization();
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();
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);
}
}
}
/*
@@ -188,20 +253,12 @@ Return type: InventoryItem*
*/
InventoryItem* InventoryManagementService::getInventoryItem(const std::string& inventoryItemID)
{
AuthenticationManagementService::ensureAuthorization();
DataStoreLockGuard lock(m_dataStore);
auto& trackedInventoryItemMap = m_dataStore.getInventoryItems();
int index = trackedInventoryItemMap.find(inventoryItemID);
if (index == -1)
int index = m_dataStore.getInventoryItems().find(inventoryItemID);
if (index != -1)
{
return nullptr;
return m_dataStore.getInventoryItems().getValueAt(index);
}
InventoryItem* inventoryItem = trackedInventoryItemMap.getValueAt(index).data;
if (inventoryItem == nullptr)
{
throw std::runtime_error("Item ID does not exist.");
}
return inventoryItem;
return nullptr;
}
/*
@@ -214,9 +271,6 @@ Returns:
*/
void InventoryManagementService::attach(User* user)
{
DataStoreLockGuard lock(m_dataStore);
m_observers.clear();
m_observers = m_dataStore.getInventoryManagementObservers();
if (user)
{
const std::string& userID = user->getId();
@@ -225,7 +279,6 @@ void InventoryManagementService::attach(User* user)
m_observers[userID] = user;
}
}
m_dataStore.saveInventoryManagementObservers(m_observers);
}
/*
@@ -238,9 +291,6 @@ Returns:
*/
void InventoryManagementService::detach(User* user)
{
DataStoreLockGuard lock(m_dataStore);
m_observers.clear();
m_observers = m_dataStore.getInventoryManagementObservers();
if (user)
{
const std::string& userID = user->getId();
@@ -249,7 +299,6 @@ void InventoryManagementService::detach(User* user)
m_observers.remove(userID);
}
}
m_dataStore.saveInventoryManagementObservers(m_observers);
}
/*
@@ -266,28 +315,27 @@ Throws:
*/
void InventoryManagementService::sendNotification(User* user, const std::string& title, const std::string& message)
{
if (!user)
if (user)
{
return;
if (m_observers.find(user->getId()) != -1)
{
Notification* notification =
Factory::getObject<Notification>(
user->getId(),
user,
title,
message,
util::Timestamp()
);
if (notification)
{
user->addNotification(notification);
}
else
{
throw std::runtime_error("Failed to create notification");
}
}
}
DataStoreLockGuard lock(m_dataStore);
m_observers = m_dataStore.getInventoryManagementObservers();
if (m_observers.find(user->getId()) == -1)
{
return;
}
auto& trackedNotificationsMap = m_dataStore.getNotifications();
Notification* notification = Factory::getObject<Notification>(
user->getId(),
title,
message,
util::Timestamp());
if (!notification)
{
throw std::runtime_error("Failed to create notification");
}
trackedNotificationsMap.insert(notification->getId(), util::createNewRecord(notification));
m_dataStore.saveNotifications();
EventManager::sendNotificationAvailableEvent(user->getId());
}
@@ -21,6 +21,7 @@ class InventoryManagementService : public NotificationManagementService
private:
DataStore& m_dataStore;
static util::Map<std::string, User*> m_observers;
util::Vector<std::string> getObserverIDs() override;
public:
InventoryManagementService() : m_dataStore(DataStore::getInstance()) {}
util::Map<std::string, InventoryItem*> getInventoryItems();
@@ -32,4 +33,8 @@ public:
void sendNotification(User* user, const std::string& title, const std::string& message) override;
void attach(User* user) override;
void detach(User* user) override;
void loadInventoryItems();
void saveInventoryItems();
void loadObservers();
void saveObservers();
};
@@ -0,0 +1 @@
#include "NotificationManagementService.h"
@@ -19,4 +19,5 @@ public:
virtual void sendNotification(User* recipient, const std::string& title, const std::string& message) = 0;
virtual void attach(User* user) = 0;
virtual void detach(User* user) = 0;
virtual util::Vector<std::string> getObserverIDs() = 0;
};
@@ -11,19 +11,16 @@ Date: 20-May-2026
#include "Config.h"
#include "Enums.h"
#include "Factory.h"
#include "FileManager.h"
#include "InventoryItem.h"
#include "Invoice.h"
#include "JobCard.h"
#include "PaymentManagementService.h"
#include "AuthenticationManagementService.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{};
@@ -37,9 +34,6 @@ Returns:
*/
void PaymentManagementService::attach(User* user)
{
DataStoreLockGuard lock(m_dataStore);
m_observers.clear();
m_observers = m_dataStore.getPaymentManagementObservers();
if (user)
{
const std::string& userID = user->getId();
@@ -48,7 +42,6 @@ void PaymentManagementService::attach(User* user)
m_observers[userID] = user;
}
}
m_dataStore.savePaymentManagementObservers(m_observers);
}
/*
@@ -61,9 +54,6 @@ Returns:
*/
void PaymentManagementService::detach(User* user)
{
DataStoreLockGuard lock(m_dataStore);
m_observers.clear();
m_observers = m_dataStore.getPaymentManagementObservers();
if (user)
{
const std::string& userID = user->getId();
@@ -72,7 +62,6 @@ void PaymentManagementService::detach(User* user)
m_observers.remove(userID);
}
}
m_dataStore.savePaymentManagementObservers(m_observers);
}
/*
@@ -89,29 +78,28 @@ Throws:
*/
void PaymentManagementService::sendNotification(User* user, const std::string& title, const std::string& message)
{
if (!user)
if (user)
{
return;
if (m_observers.find(user->getId()) != -1)
{
Notification* notification =
Factory::getObject<Notification>(
user->getId(),
user,
title,
message,
util::Timestamp()
);
if (notification)
{
user->addNotification(notification);
}
else
{
throw std::runtime_error("Failed to create notification");
}
}
}
DataStoreLockGuard lock(m_dataStore);
m_observers = m_dataStore.getPaymentManagementObservers();
if (m_observers.find(user->getId()) == -1)
{
return;
}
auto& trackedNotificationsMap = m_dataStore.getNotifications();
Notification* notification = Factory::getObject<Notification>(
user->getId(),
title,
message,
util::Timestamp());
if (!notification)
{
throw std::runtime_error("Failed to create notification");
}
trackedNotificationsMap.insert(notification->getId(), util::createNewRecord(notification));
m_dataStore.saveNotifications();
EventManager::sendNotificationAvailableEvent(user->getId());
}
/*
@@ -125,12 +113,11 @@ Returns:
*/
void PaymentManagementService::sendPaymentReminders()
{
DataStoreLockGuard lock(m_dataStore);
auto& trackedInvoicesMap = m_dataStore.getInvoices();
int invoicesMapSize = trackedInvoicesMap.getSize();
auto& invoicesMap = m_dataStore.getInvoices();
int invoicesMapSize = invoicesMap.getSize();
for (int index = 0; index < invoicesMapSize; index++)
{
const Invoice* invoice = trackedInvoicesMap.getValueAt(index).data;
const Invoice* invoice = invoicesMap.getValueAt(index);
if (invoice && invoice->getStatus() == util::PaymentStatus::PENDING)
{
util::Timestamp invoiceCreationTimestamp = invoice->getInvoiceDate();
@@ -153,6 +140,123 @@ void PaymentManagementService::sendPaymentReminders()
}
}
/*
Function: getObserverIDs
Description: Retrieves the IDs of all observers currently attached to the
PaymentManagementService.
Parameters:
- None
Returns:
- util::Vector<std::string>: Vector of observer user IDs
*/
util::Vector<std::string> PaymentManagementService::getObserverIDs()
{
util::Vector<std::string> observerIDs;
int numberOfObservers = m_observers.getSize();
for (int index = 0; index < numberOfObservers; index++)
{
User* observer = m_observers.getValueAt(index);
if (observer)
{
observerIDs.push_back(observer->getId());
}
}
return observerIDs;
}
/*
Function: loadInvoices
Description: Loads invoices from persistent storage into the datastore.
Validates associated service bookings and inventory parts before
attaching them to each invoice. Throws exceptions if invalid IDs
are encountered.
Parameters:
- None
Returns:
- void
Throws:
- std::runtime_error if a booking ID or part ID is invalid
*/
void PaymentManagementService::loadInvoices()
{
util::FileManager<Invoice> invoiceFileManager(config::file::INVOICE_FILE);
auto& invoices = m_dataStore.getInvoices();
auto& serviceBookings = m_dataStore.getServiceBookings();
auto& inventoryItems = m_dataStore.getInventoryItems();
auto invoicesMap = invoiceFileManager.load();
for (int invoiceIndex = 0; invoiceIndex < invoicesMap.getSize(); invoiceIndex++)
{
Invoice* invoice = invoicesMap.getValueAt(invoiceIndex);
int bookingIndex = serviceBookings.find(invoice->getBookingId());
if (bookingIndex == -1)
{
throw std::runtime_error("Invalid Booking ID");
}
ServiceBooking* booking = serviceBookings.getValueAt(bookingIndex);
invoice->setBooking(booking);
util::Map<std::string, InventoryItem*> invoiceParts;
auto& partIDs = invoice->getPartIDs();
for (int partIndex = 0; partIndex < partIDs.getSize(); partIndex++)
{
const std::string& partID = partIDs[partIndex];
int inventoryIndex = inventoryItems.find(partID);
if (inventoryIndex == -1)
{
throw std::runtime_error("Invalid Part ID");
}
invoiceParts[partID] = inventoryItems.getValueAt(inventoryIndex);
}
invoice->setParts(invoiceParts);
invoices[invoice->getId()] = invoice;
}
}
/*
Function: saveInvoices
Description: Saves invoices from the datastore to persistent storage.
Uses FileManager to serialize invoices into the configured file.
Parameters:
- None
Returns:
- void
*/
void PaymentManagementService::saveInvoices()
{
util::FileManager<Invoice> invoiceFileManager(config::file::INVOICE_FILE);
auto& invoices = m_dataStore.getInvoices();
invoiceFileManager.save(invoices);
}
/*
Function: loadObservers
Description: Loads observer IDs from persistent storage and attaches corresponding
users as observers to the PaymentManagementService.
Parameters:
- None
Returns:
- void
Throws:
- std::runtime_error if an observer ID is invalid (not found in datastore)
*/
void PaymentManagementService::loadObservers()
{
util::loadObservers(config::file::PAYMENTMANAGEMENTOBSERVERS, this, m_dataStore);
}
/*
Function: saveObservers
Description: Saves the current observer IDs of the PaymentManagementService
to persistent storage for future retrieval.
Parameters:
- None
Returns:
- void
*/
void PaymentManagementService::saveObservers()
{
util::saveObservers(config::file::PAYMENTMANAGEMENTOBSERVERS, this);
}
/*
Function: createInventoryItemsMap (static helper)
Description: Builds a map of inventory items required for a given service and adds them to the bookings inventory map.
@@ -186,7 +290,6 @@ Throws:
*/
void PaymentManagementService::generateInvoice(ServiceBooking* booking)
{
DataStoreLockGuard lock(m_dataStore);
if (!booking)
{
throw std::runtime_error("Invoice generation failed: booking is null.");
@@ -196,10 +299,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;
auto& currentTrackedJobCards = m_dataStore.getJobCards();
for (int iterator = 0; iterator < currentTrackedJobCards.getSize(); iterator++)
util::Map<std::string, JobCard*> currentJobCards = m_dataStore.getJobCards();
for (int iterator = 0; iterator < currentJobCards.getSize(); iterator++)
{
JobCard* currentJobCard = currentTrackedJobCards.getValueAt(iterator).data;
JobCard* currentJobCard = currentJobCards.getValueAt(iterator);
util::ServiceJobStatus currentJobCardStatus = currentJobCard->getStatus();
if (currentJobCard->getBookingId() == bookingID && currentJobCardStatus != util::ServiceJobStatus::CANCELLED && currentJobCardStatus != util::ServiceJobStatus::COMPLETED)
{
@@ -219,9 +322,8 @@ 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);
auto& currentTrackedInvoices = m_dataStore.getInvoices();
currentTrackedInvoices.insert(invoice->getId(), util::createNewRecord(invoice));
m_dataStore.saveInvoices();
util::Map<std::string, Invoice*>& currentInvoices = m_dataStore.getInvoices();
currentInvoices.insert(invoice->getId(), invoice);
}
/*
@@ -234,13 +336,11 @@ Returns:
*/
util::Map<std::string, Invoice*> PaymentManagementService::getInvoices(const std::string& customerID)
{
AuthenticationManagementService::ensureAuthorization();
DataStoreLockGuard lock(m_dataStore);
auto& currentTrackedInvoices = m_dataStore.getInvoices();
util::Map<std::string, Invoice*>& currentInvoices = m_dataStore.getInvoices();
util::Map<std::string, Invoice*> currentUserInvoices;
for (int iterator = 0; iterator < currentTrackedInvoices.getSize(); iterator++)
for (int iterator = 0; iterator < currentInvoices.getSize(); iterator++)
{
Invoice* currentInvoice = currentTrackedInvoices.getValueAt(iterator).data;
Invoice* currentInvoice = currentInvoices.getValueAt(iterator);
if (currentInvoice->getBooking()->getCustomerId() == customerID)
{
currentUserInvoices.insert(currentInvoice->getId(), currentInvoice);
@@ -263,14 +363,11 @@ Throws:
*/
void PaymentManagementService::completePayment(const std::string& invoiceID, util::PaymentMode paymentMode)
{
AuthenticationManagementService::ensureAuthorization();
DataStoreLockGuard lock(m_dataStore);
auto& currentTrackedInvoices = m_dataStore.getInvoices();
int invoiceIndex = currentTrackedInvoices.find(invoiceID);
auto& currentInvoices = m_dataStore.getInvoices();
int invoiceIndex = currentInvoices.find(invoiceID);
if (invoiceIndex != -1)
{
auto& trackedInvoice = currentTrackedInvoices.getValueAt(invoiceIndex);
Invoice* invoice = trackedInvoice.data;
Invoice* invoice = currentInvoices.getValueAt(invoiceIndex);
if (invoice && invoice->getStatus() != util::PaymentStatus::PAID)
{
User* currentUser = invoice->getBooking()->getCustomer();
@@ -281,31 +378,25 @@ 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();
}
/*
Function: getAllInvoices
Function: getAllInvoice
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()
{
AuthenticationManagementService::ensureAuthorization();
DataStoreLockGuard lock(m_dataStore);
util::Map<std::string, Invoice*> invoices;
invoices = util::getObjects(m_dataStore.getInvoices());
return invoices;
return m_dataStore.getInvoices();
}
/*
@@ -321,25 +412,20 @@ Throws:
*/
void PaymentManagementService::confirmPayment(const std::string& invoiceID)
{
AuthenticationManagementService::ensureAuthorization();
DataStoreLockGuard lock(m_dataStore);
auto& currentTrackedInvoices = m_dataStore.getInvoices();
int invoiceIndex = currentTrackedInvoices.find(invoiceID);
auto& currentInvoices = m_dataStore.getInvoices();
int invoiceIndex = currentInvoices.find(invoiceID);
if (invoiceIndex == -1)
{
throw std::runtime_error("Payment confirmation failed: invalid invoice ID.");
}
auto& trackedInvoice = currentTrackedInvoices.getValueAt(invoiceIndex);
Invoice* invoice = trackedInvoice.data;
Invoice* invoice = currentInvoices.getValueAt(invoiceIndex);
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();
}
@@ -22,15 +22,20 @@ class PaymentManagementService : public NotificationManagementService
private:
DataStore& m_dataStore;
static util::Map<std::string, User*> m_observers;
util::Vector<std::string> getObserverIDs() override;
public:
PaymentManagementService() : m_dataStore(DataStore::getInstance()) {}
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;
void attach(User* user) override;
void detach(User* user) override;
void loadInvoices();
void saveInvoices();
void loadObservers();
void saveObservers();
};
@@ -23,6 +23,7 @@ class ServiceManagementService : public NotificationManagementService
private:
DataStore& m_dataStore;
static util::Map<std::string, User*> m_observers;
util::Vector<std::string> getObserverIDs() override;
public:
ServiceManagementService() : m_dataStore(DataStore::getInstance()) {}
util::Map<std::string, Service*> getServices();
@@ -35,7 +36,6 @@ public:
void createJobCard(const std::string& bookingID, const std::string& technicianID, const std::string& serviceID);
void createService(const std::string& name, const util::Vector<std::string>& inventoryItemIDs, double laborCost);
void removeService(const std::string& serviceID);
void removeServiceBooking(const std::string& bookingID);
util::Map<std::string, JobCard*> getJobCards(const std::string& technicianID);
void updateJobStatus(const std::string& jobID);
void cancelCustomerServiceBookings(const std::string& customerID);
@@ -45,4 +45,14 @@ public:
void sendNotification(User* user, const std::string& title, const std::string& message) override;
void attach(User* user) override;
void detach(User* user) override;
void loadServices();
void saveServices();
void loadComboPackages();
void saveComboPackages();
void loadServiceBookings();
void saveServiceBookings();
void loadJobCards();
void saveJobCards();
void loadObservers();
void saveObservers();
};
@@ -11,19 +11,15 @@ Date:19-May-2026
#include "Config.h"
#include "Enums.h"
#include "Factory.h"
#include "FileManager.h"
#include "InventoryManagementService.h"
#include "Notification.h"
#include "PaymentManagementService.h"
#include "ServiceManagementService.h"
#include "AuthenticationManagementService.h"
#include "User.h"
#include "UserManagementService.h"
#include "Vector.h"
#include "Validator.h"
#include "Utility.h"
#include "TrackedRecord.h"
#include "DataStoreLockGuard.h"
#include "EventManager.h"
/*
Function: ensureAdminExists
@@ -35,13 +31,12 @@ Return type: void
*/
void UserManagementService::ensureAdminExists()
{
DataStoreLockGuard lock(m_dataStore);
auto& usersMap = m_dataStore.getUsers();
int usersMapSize = usersMap.getSize();
bool isAdminFound = false;
for (int index = 0; index < usersMapSize; index++)
{
User* user = usersMap.getValueAt(index).data;
User* user = usersMap.getValueAt(index);
if (user && user->getUserType() == util::UserType::ADMIN)
{
isAdminFound = true;
@@ -78,9 +73,7 @@ void UserManagementService::createUser(const std::string& username, const std::s
InventoryManagementService inventoryManagementService;
PaymentManagementService paymentManagementService;
ServiceManagementService serviceManagementService;
DataStoreLockGuard lock(m_dataStore);
auto& trackedUsersMap = m_dataStore.getUsers();
auto usersMap = util::getObjects(trackedUsersMap);
auto& usersMap = m_dataStore.getUsers();
if (util::isUsernameDuplicate(username, usersMap))
{
throw std::runtime_error("Username already exists");
@@ -94,14 +87,13 @@ void UserManagementService::createUser(const std::string& username, const std::s
throw std::runtime_error("Phone already exists");
}
User* newUser = Factory::getObject<User>(username, password, name, phone, email, type);
trackedUsersMap.insert(newUser->getId(), util::createNewRecord(newUser));
usersMap.insert(newUser->getId(), newUser);
paymentManagementService.attach(newUser);
serviceManagementService.attach(newUser);
if (newUser->getUserType() == util::UserType::ADMIN)
{
inventoryManagementService.attach(newUser);
}
m_dataStore.saveUsers();
}
/*
@@ -115,25 +107,19 @@ Return type: void
*/
void UserManagementService::updateUserDetails(const std::string& userID, const std::string& email, const std::string& phone)
{
AuthenticationManagementService::ensureAuthorization();
DataStoreLockGuard lock(m_dataStore);
auto& trackedUsersMap = m_dataStore.getUsers();
auto usersMap = util::getObjects(trackedUsersMap);
int index = trackedUsersMap.find(userID);
auto& usersMap = m_dataStore.getUsers();
int index = usersMap.find(userID);
if (index == -1)
{
throw std::runtime_error("User does not exist!\n");
}
User* user = trackedUsersMap.getValueAt(index).data;
bool isModified = false;
User* user = usersMap.getValueAt(index);
if (email != user->getEmail())
{
if (util::isEmailDuplicate(email, usersMap))
{
throw std::runtime_error("Email already exists!\n");
}
user->setEmail(email);
isModified = true;
}
if (phone != user->getPhone())
{
@@ -141,14 +127,9 @@ void UserManagementService::updateUserDetails(const std::string& userID, const s
{
throw std::runtime_error("Phone number already exists!\n");
}
user->setPhone(phone);
isModified = true;
}
if (isModified)
{
trackedUsersMap.getValueAt(index).state = RecordState::MODIFIED;
m_dataStore.saveUsers();
}
user->setEmail(email);
user->setPhone(phone);
}
/*
@@ -163,26 +144,20 @@ Throws:
*/
util::Vector<Notification*> UserManagementService::getUserNotifications(const std::string& userID)
{
AuthenticationManagementService::ensureAuthorization();
DataStoreLockGuard lock(m_dataStore);
auto& trackedUsersMap = m_dataStore.getUsers();
if (trackedUsersMap.find(userID) == -1)
auto& usersMap = m_dataStore.getUsers();
if (usersMap.find(userID) == -1)
{
throw std::runtime_error("No user found with given UserID");
}
User* user = trackedUsersMap[userID].data;
User* user = usersMap[userID];
if (user)
{
auto& trackedNotificationMap = m_dataStore.getNotifications();
int numberOfNotifications = trackedNotificationMap.getSize();
auto& notifications = user->getNotifications();
int numberOfNotifications = notifications.getSize();
util::Vector<Notification*> notificationsVector;
for (int index = 0; index < numberOfNotifications; index++)
{
Notification* notification = trackedNotificationMap.getValueAt(index).data;
if (notification->getRecipientUserId() == userID && notification->getState() == util::State::ACTIVE)
{
notificationsVector.push_back(notification);
}
notificationsVector.push_back(notifications.getValueAt(index));
}
return notificationsVector;
}
@@ -194,40 +169,97 @@ util::Vector<Notification*> UserManagementService::getUserNotifications(const st
/*
Function: deleteNotification
Description: Marks a specific notification associated with a given user
as inactive.
Description: Deletes a specific notification associated with a given user ID.
Parameters:
- notificationID: The unique ID of the notification to be deleted.
- userID: The unique ID of the user whose notification is to be deleted.
Returns:
- void
Throws:
- std::runtime_error if no user is found with the given UserID or
if no notification is found with the given NotificationID.
- std::runtime_error if no user is found with the given UserID or if no notification is found with the given NotificationID.
*/
void UserManagementService::deleteNotification(const std::string& notificationID, const std::string& userID)
{
AuthenticationManagementService::ensureAuthorization();
DataStoreLockGuard lock(m_dataStore);
auto& trackedUsersMap = m_dataStore.getUsers();
auto& trackedNotificationsMap = m_dataStore.getNotifications();
int userIndex = trackedUsersMap.find(userID);
if (userIndex == -1)
auto& usersMap = m_dataStore.getUsers();
if (usersMap.find(userID) == -1)
{
throw std::runtime_error("No user found with given UserID");
}
int notificationIndex = trackedNotificationsMap.find(notificationID);
if (notificationIndex == -1)
User* user = usersMap[userID];
auto& notifications = user->getNotifications();
if (notifications.find(notificationID) == -1)
{
throw std::runtime_error("No notification found with given NotificationID");
}
Notification* notification = trackedNotificationsMap.getValueAt(notificationIndex).data;
if (notification->getRecipientUserId() == userID)
{
notification->setState(util::State::INACTIVE);
trackedNotificationsMap.getValueAt(notificationIndex).state = RecordState::MODIFIED;
m_dataStore.saveNotifications();
}
notifications.remove(notificationID);
}
/*
Function: loadUsers
Description: Loads users and notifications from persistent storage into the datastore.
Validates that each notifications recipient exists and attaches the
notification to the corresponding user.
Parameters:
- None
Returns:
- void
Throws:
- std::runtime_error if a notification recipient user ID is invalid
*/
void UserManagementService::loadUsers()
{
util::FileManager<User> userFileManager(config::file::USER_FILE);
util::FileManager<Notification> notificationFileManager(config::file::NOTIFICATION_FILE);
auto& users = m_dataStore.getUsers();
auto usersMap = userFileManager.load();
auto notificationsMap = notificationFileManager.load();
int numberOfUsers = usersMap.getSize();
int numberOfNotifications = notificationsMap.getSize();
for (int index = 0; index < numberOfUsers; index++)
{
users[usersMap.getKeyAt(index)] = usersMap.getValueAt(index);
}
for (int index = 0; index < numberOfNotifications; index++)
{
Notification* notification = notificationsMap.getValueAt(index);
const std::string& recipientUserId = notification->getRecipientUserId();
int userIndex = users.find(recipientUserId);
if (userIndex == -1)
{
throw std::runtime_error("Invalid recipient user ID");
}
User* user = users.getValueAt(userIndex);
user->addNotification(notification);
}
}
/*
Function: saveUsers
Description: Saves users and their notifications from the datastore to persistent storage.
Collects notifications from all users into a single map before saving.
Parameters:
- None
Returns:
- void
*/
void UserManagementService::saveUsers()
{
util::FileManager<User> userFileManager(config::file::USER_FILE);
util::FileManager<Notification> notificationFileManager(config::file::NOTIFICATION_FILE);
auto& users = m_dataStore.getUsers();
util::Map<std::string, Notification*> notifications;
for (int userIndex = 0; userIndex < users.getSize(); userIndex++)
{
User* user = users.getValueAt(userIndex);
auto& userNotifications = user->getNotifications();
for (int notificationIndex = 0; notificationIndex < userNotifications.getSize(); notificationIndex++)
{
notifications[userNotifications.getKeyAt(notificationIndex)] =
userNotifications.getValueAt(notificationIndex);
}
}
userFileManager.save(users);
notificationFileManager.save(notifications);
}
/*
@@ -238,9 +270,7 @@ Return type: util::Map<std::string, User*>
*/
util::Map<std::string, User*> UserManagementService::getUsers()
{
DataStoreLockGuard lock(m_dataStore);
auto users = util::getObjects(m_dataStore.getUsers());
return users;
return m_dataStore.getUsers();
}
/*
@@ -251,12 +281,10 @@ Return type: User*
*/
User* UserManagementService::getUser(const std::string& userID)
{
DataStoreLockGuard lock(m_dataStore);
auto& trackedUsersMap = m_dataStore.getUsers();
int index = trackedUsersMap.find(userID);
int index = m_dataStore.getUsers().find(userID);
if (index != -1)
{
return trackedUsersMap.getValueAt(index).data;
return m_dataStore.getUsers().getValueAt(index);
}
return nullptr;
}
@@ -269,65 +297,38 @@ Return type: void
*/
void UserManagementService::removeUser(const std::string& userID)
{
AuthenticationManagementService::ensureAuthorization();
InventoryManagementService inventoryManagementService;
PaymentManagementService paymentManagementService;
ServiceManagementService serviceManagementService;
std::string removedUserID;
int index = m_dataStore.getUsers().find(userID);
if (index != -1)
{
DataStoreLockGuard lock(m_dataStore);
auto& trackedUsersMap = m_dataStore.getUsers();
int index = trackedUsersMap.find(userID);
if (index != -1)
User* user = m_dataStore.getUsers().getValueAt(index);
if (user != nullptr)
{
User* user = trackedUsersMap.getValueAt(index).data;
if (user != nullptr)
if (user->getUserType() == util::UserType::CUSTOMER)
{
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();
serviceManagementService.cancelCustomerServiceBookings(userID);
}
if (user->getUserType() == util::UserType::TECHNICIAN)
{
serviceManagementService.cancelTechnicianJobs(userID);
}
user->setState(util::State::INACTIVE);
inventoryManagementService.detach(user);
paymentManagementService.detach(user);
serviceManagementService.detach(user);
}
}
if (!removedUserID.empty())
{
EventManager::sendUserDisabledEvent(removedUserID);
}
}
/*
Function: getUsers
Description: Retrieves all active users of the specified type from
the DataStore.
Parameters:
- type: The user type to filter by
(ADMIN, CUSTOMER, or TECHNICIAN).
Returns:
- util::Map<std::string, User*>:
Collection of active users matching the specified type,
keyed by user ID.
*/
util::Map<std::string, User*> UserManagementService::getUsers(util::UserType type)
util::Map<std::string, User*> UserManagementService::getUsers(util::UserType type)
{
DataStoreLockGuard lock(m_dataStore);
auto& trackedUsersMap = m_dataStore.getUsers();
util::Map<std::string, User*> currentUsers = util::getObjects(trackedUsersMap);
util::Map<std::string, User*>& currentUsers = m_dataStore.getUsers();
util::Map<std::string, User*> filteredUsersMap;
for (int index = 0; index < currentUsers.getSize(); index++)
for (int iterator = 0; iterator < currentUsers.getSize(); iterator++)
{
User* currentUser = currentUsers.getValueAt(index);
User* currentUser = currentUsers.getValueAt(iterator);
if (currentUser && currentUser->getState() == util::State::ACTIVE && currentUser->getUserType() == type)
{
filteredUsersMap.insert(currentUser->getId(), currentUser);
@@ -6,7 +6,6 @@ Description: Header file declaring the UserManagementService class, which manage
Author: Trenser
Date:19-May-2026
*/
#pragma once
#include <string>
#include "Map.h"
@@ -31,4 +30,6 @@ public:
util::Vector<Notification*> getUserNotifications(const std::string& userID);
void deleteNotification(const std::string& notificationID, const std::string& userID);
void ensureAdminExists();
void loadUsers();
void saveUsers();
};
@@ -28,19 +28,16 @@ namespace config
namespace file
{
const size_t INITIAL_CAPACITY = 100;
const size_t GROWTH_FACTOR = 2;
constexpr const char* DIRECTORY = "files/";
constexpr const char* INVENTORYITEM_FILE = "files/InventoryItem.dat";
constexpr const char* USER_FILE = "files/User.dat";
constexpr const char* NOTIFICATION_FILE = "files/Notification.dat";
constexpr const char* SERVICE_FILE = "files/Service.dat";
constexpr const char* COMBOPACKAGE_FILE = "files/ComboPackage.dat";
constexpr const char* SERVICEBOOKING_FILE = "files/ServiceBooking.dat";
constexpr const char* JOBCARD_FILE = "files/JobCard.dat";
constexpr const char* INVOICE_FILE = "files/Invoice.dat";
constexpr const char* SERVICEMANAGEMENTOBSERVERS = "files/ServiceManagementObservers.dat";
constexpr const char* PAYMENTMANAGEMENTOBSERVERS = "files/PaymentManagementObservers.dat";
constexpr const char* INVENTORYMANAGEMENTOBSERVERS = "files/InventoryManagementObservers.dat";
constexpr const char* INVENTORYITEM_FILE = "files/InventoryItem.csv";
constexpr const char* USER_FILE = "files/User.csv";
constexpr const char* NOTIFICATION_FILE = "files/Notification.csv";
constexpr const char* SERVICE_FILE = "files/Service.csv";
constexpr const char* COMBOPACKAGE_FILE = "files/ComboPackage.csv";
constexpr const char* SERVICEBOOKING_FILE = "files/ServiceBooking.csv";
constexpr const char* JOBCARD_FILE = "files/JobCard.csv";
constexpr const char* INVOICE_FILE = "files/Invoice.csv";
constexpr const char* SERVICEMANAGEMENTOBSERVERS = "files/ServiceManagementObservers.csv";
constexpr const char* PAYMENTMANAGEMENTOBSERVERS = "files/PaymentManagementObservers.csv";
constexpr const char* INVENTORYMANAGEMENTOBSERVERS = "files/InventoryManagementObservers.csv";
}
}
@@ -12,28 +12,28 @@ Date: 19-May-2026
namespace util
{
enum class UserType : int
enum class UserType
{
ADMIN,
TECHNICIAN,
CUSTOMER
};
enum class PaymentMode : int
enum class PaymentMode
{
ONLINE,
OFFLINE,
NOTSET
};
enum class PaymentStatus : int
enum class PaymentStatus
{
PENDING,
COMPLETED,
PAID
};
enum class ServiceJobStatus : int
enum class ServiceJobStatus
{
PENDING,
STARTED,
@@ -42,7 +42,7 @@ namespace util
CANCELLED
};
enum class State : int
enum class State
{
ACTIVE,
INACTIVE
@@ -43,4 +43,67 @@ namespace util
position++;
}
}
}
/*
Function: loadRecords
Description: Loads records from a given file path into a vector of strings.
Skips the header line if present. Creates the file if it does not exist.
Parameters:
- filePath: const std::string&, path to the file
Returns:
- util::Vector<std::string>: Vector containing all records (excluding header)
Throws:
- None (creates file if missing)
*/
inline util::Vector<std::string> loadRecords(const std::string& filePath)
{
util::Vector<std::string> records;
std::ifstream file(filePath);
if (!file.is_open())
{
ensureDirectoryExists(filePath);
std::ofstream newFile(filePath);
newFile.close();
file.open(filePath);
}
std::string line;
bool isHeader = true;
while (std::getline(file, line))
{
if (isHeader)
{
isHeader = false;
continue;
}
records.push_back(line);
}
return records;
}
/*
Function: saveRecords
Description: Saves records to a given file path. Overwrites existing content
and writes a header line followed by all records.
Parameters:
- filePath: const std::string&, path to the file
- records: const util::Vector<std::string>&, vector of records to save
Returns:
- void
Throws:
- std::runtime_error if the file cannot be opened for writing
*/
inline void saveRecords(const std::string& filePath, const util::Vector<std::string>& records)
{
std::ofstream file(filePath, std::ios::trunc);
if (!file.is_open())
{
throw std::runtime_error("Failed to open file " + filePath);
}
file << "Values" << '\n';
int numberOfRecords = records.getSize();
for (int index = 0; index < numberOfRecords; index++)
{
file << records[index] << '\n';
}
}
}
@@ -0,0 +1,119 @@
/*
File: FileManager.h
Description: Declares and implements a generic FileManager template class for
loading and saving objects to and from files. Uses serialization
and deserialization methods defined in the object type T.
Provides persistence support for system entities such as Users,
Services, InventoryItems, etc.
Author: Trenser
Date: 22-May-2026
*/
#pragma once
#include <stdexcept>
#include <string>
#include <fstream>
#include "Vector.h"
#include "Map.h"
#include "FileHelper.h"
namespace util
{
template <typename T> using objects = util::Map<std::string, T*>;
template <typename T>
class FileManager
{
private:
std::string m_filePath;
public:
FileManager() : m_filePath("") {}
FileManager(const std::string& filePath) : m_filePath(filePath) {}
objects<T> load();
void save(const objects<T>&);
};
/*
Function: load
Description: Loads records from the file into a map of objects.
Skips the header line, deserializes each record into an object of type T,
and stores them in a map keyed by object ID.
Parameters:
- None
Returns:
- util::Map<std::string, T*> containing deserialized objects
Throws:
- std::runtime_error if deserialization fails for any record
*/
template <typename T>
objects<T> FileManager<T>::load()
{
objects<T> records;
std::ifstream file(m_filePath);
if (!file.is_open())
{
ensureDirectoryExists(m_filePath);
std::ofstream newFile(m_filePath);
newFile.close();
file.open(m_filePath);
}
util::Vector<std::string> lines;
std::string line;
while (std::getline(file, line))
{
lines.push_back(line);
}
int numberOfLines = lines.getSize();
bool isHeader = true;
for (int lineIndex = 0; lineIndex < numberOfLines; lineIndex++)
{
const auto& record = lines[lineIndex];
if (isHeader)
{
isHeader = false;
continue;
}
auto object = T::deserialize(record);
if (!object)
{
throw std::runtime_error("Failed to deserialize record");
}
records[object->getId()] = object;
}
return records;
}
/*
Function: save
Description: Saves records to the file. Serializes each object of type T into a string,
writes a header line, and then writes all serialized records to the file.
Parameters:
- records: const util::Map<std::string, T*>&, map of objects to save
Returns:
- void
Throws:
- std::runtime_error if the file cannot be opened for writing
*/
template <typename T>
void FileManager<T>::save(const objects<T>& records)
{
util::Vector<std::string> lines;
lines.push_back(T::getHeaders());
int numberOfRecords = records.getSize();
for (int recordIndex = 0; recordIndex < numberOfRecords; recordIndex++)
{
const auto& record = records.getValueAt(recordIndex);
lines.push_back(record->serialize());
}
std::ofstream file(m_filePath, std::ios::trunc);
if (!file.is_open())
{
throw std::runtime_error("Failed to open file " + m_filePath);
}
int numberOfLines = lines.getSize();
for (int lineIndex = 0; lineIndex < numberOfLines; lineIndex++)
{
file << lines[lineIndex] << '\n';
}
}
}
@@ -10,7 +10,6 @@
#include <limits>
#include <string>
#include <stdexcept>
#include <conio.h>
namespace util
{
@@ -28,7 +27,7 @@ namespace util
if (!(std::cin >> value))
{
std::cin.clear();
std::cin.ignore((std::numeric_limits<std::streamsize>::max)(), '\n');
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
throw std::runtime_error("Invalid console input");
}
}
@@ -55,48 +54,6 @@ namespace util
value = cleanedValue;
}
/*
* Function: readPassword
* Description: Reads a password from console without echoing characters;
* displays '*' for each character typed, handles backspace,
* and cleans commas from the result.
* Parameters:
* value - reference to a string where the password will be stored
* Returns:
* void - no return value
*/
inline void readPassword(std::string& value)
{
value.clear();
char currentCharacter;
while ((currentCharacter = _getch()) != '\r')
{
if (currentCharacter == '\b')
{
if (!value.empty())
{
value.pop_back();
std::cout << "\b \b";
}
}
else
{
value += currentCharacter;
std::cout << '*';
}
}
std::cout << std::endl;
std::string cleanedValue;
for (int iterator = 0; iterator < value.length(); iterator++)
{
if (value[iterator] != ',')
{
cleanedValue += value[iterator];
}
}
value = cleanedValue;
}
/*
* Function: pressEnter
* Description: Pauses execution until the user presses Enter.
@@ -54,79 +54,49 @@ namespace util
return cost;
}
template<typename TObject>
Map<std::string, TObject*> getObjects(const Map<std::string, TrackedRecord<TObject>>& trackedRecords);
template<typename TObject>
Map<std::string, const TObject*> getConstObjects(const Map<std::string, TrackedRecord<TObject>>& trackedRecords);
template<typename TObject>
TrackedRecord<TObject> createNewRecord(TObject* object);
}
/*
Function: getObjects
Description: Extracts the object pointers from a tracked-record
collection and returns them as a map keyed by the
same identifiers.
Parameters:
- trackedRecords: Collection of tracked records.
Returns:
- Map<std::string, TObject*>: Collection of object pointers.
*/
template<typename TObject>
util::Map<std::string, TObject*> util::getObjects(const util::Map<std::string, TrackedRecord<TObject>>& trackedRecords)
{
util::Map<std::string, TObject*> objects;
for (int index = 0; index < trackedRecords.getSize(); ++index)
/*
Function: loadObservers
Description: Loads observer IDs from a file and attaches the corresponding users
to the notification management service. Validates that each observer ID
exists in the datastore before attaching.
Parameters:
- filePath: const std::string&, path to the file containing observer IDs
- service: NotificationManagementService*, pointer to the notification service
- dataStore: DataStore&, reference to the datastore containing users
Returns:
- void
Throws:
- std::runtime_error if an observer ID is invalid (not found in datastore)
*/
inline void loadObservers(const std::string& filePath, NotificationManagementService* service, DataStore& dataStore)
{
const std::string& key = trackedRecords.getKeyAt(index);
TObject* object = trackedRecords.getValueAt(index).data;
objects.insert(key, object);
auto observerIDs = util::loadRecords(filePath);
auto& users = dataStore.getUsers();
for (int index = 0; index < observerIDs.getSize(); index++)
{
const std::string& observerID = observerIDs[index];
int userIndex = users.find(observerID);
if (userIndex == -1)
{
throw std::runtime_error("Invalid Observer ID");
}
service->attach(users.getValueAt(userIndex));
}
}
return objects;
}
/*
Function: getConstObjects
Description: Extracts the object pointers from a tracked-record
collection and returns them as a read-only map
keyed by the same identifiers.
Parameters:
- trackedRecords: Collection of tracked records.
Returns:
- Map<std::string, const TObject*>:
Collection of read-only object pointers.
*/
template<typename TObject>
util::Map<std::string, const TObject*> util::getConstObjects(
const util::Map<std::string, TrackedRecord<TObject>>& trackedRecords)
{
util::Map<std::string, const TObject*> objects;
for (int index = 0; index < trackedRecords.getSize(); ++index)
/*
Function: saveObservers
Description: Saves the current observer IDs from the notification management service
to a file for persistence.
Parameters:
- filePath: const std::string&, path to the file where observer IDs will be saved
- service: NotificationManagementService*, pointer to the notification service
Returns:
- void
*/
inline void saveObservers(const std::string& filePath, NotificationManagementService* service)
{
const std::string& key = trackedRecords.getKeyAt(index);
const TObject* object = trackedRecords.getValueAt(index).data;
objects.insert(key, object);
auto observerIDs = service->getObserverIDs();
util::saveRecords(filePath, observerIDs);
}
return objects;
}
/*
Function: createNewRecord
Description: Creates a tracked record for a newly created
object. The record is initialized with
NEW_RECORD state.
Parameters:
- object: Pointer to the newly created object.
Returns:
- TrackedRecord<TObject>: Initialized tracked record.
*/
template<typename TObject>
TrackedRecord<TObject> util::createNewRecord(TObject* object)
{
TrackedRecord<TObject> record;
record.data = object;
record.state = RecordState::NEW_RECORD;
return record;
}
@@ -30,16 +30,10 @@ Return type: void
*/
void AdminMenu::showMenu()
{
startEventListener();
while (true)
{
try
{
if (!m_isMenuActive)
{
logout();
break;
}
int choice;
util::clear();
std::cout << "Admin Menu"
@@ -74,7 +68,6 @@ void AdminMenu::showMenu()
util::pressEnter();
}
}
stopEventListener();
}
/*
@@ -85,11 +78,6 @@ 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:
@@ -153,25 +141,6 @@ 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();
const User* authenticatedUser = m_controller.getAuthenticatedUser();
std::string name;
if (authenticatedUser)
{
name = authenticatedUser->getName();
}
displayNewNotification(notifications, name);
}
/*
Function: logout
Description: Logs out the currently authenticated admin user.
@@ -338,23 +307,6 @@ void AdminMenu::removeInventoryItem()
std::string selectedItemId = selectedItem->getId();
m_controller.removeInventoryItem(selectedItemId);
std::cout << "Item " << selectedItem->getPartName() << " removed successfully." << std::endl;
const util::Map<std::string, const Service*>& listOfService = m_controller.getServices();
for (int serviceIndex = 0; serviceIndex < listOfService.getSize(); serviceIndex++)
{
const Service* service = listOfService.getValueAt(serviceIndex);
if (!service)
{
continue;
}
const util::Map<std::string, InventoryItem*>& requiredItems = service->getRequiredInventoryItems();
if (requiredItems.find(selectedItemId) != -1)
{
m_controller.removeService(service->getId());
std::cout << "Service " << service->getName()
<< " removed as the item "
<< selectedItem->getPartName() << " required for the service has been removed." << std::endl;
}
}
}
}
util::pressEnter();
@@ -9,13 +9,12 @@ Date:19-May-2026
#pragma once
#include "Controller.h"
#include "Menu.h"
class AdminMenu : public Menu
class AdminMenu
{
private:
Controller m_controller;
bool handleOperation(int choice);
void handleNotificationEvent() override;
public:
void showMenu();
void logout();
@@ -30,19 +30,13 @@ Description: Displays the customer menu and handles user input until logout is s
Parameter: None
Return type: void
*/
void CustomerMenu::showMenu()
{
startEventListener();
while (true)
{
try
{
if (!m_isMenuActive)
{
logout();
break;
}
int choice;
util::clear();
std::cout << "Customer Menu"
@@ -51,12 +45,11 @@ void CustomerMenu::showMenu()
<< "\n3. Update Profile"
<< "\n4. Change Password"
<< "\n5. View Service History"
<< "\n6. Cancel Service Booking"
<< "\n7. Complete Payments"
<< "\n8. View Invoices"
<< "\n9. View Notifications"
<< "\n10. Configure Notifications"
<< "\n11. Logout"
<< "\n6. Complete Payments"
<< "\n7. View Invoices"
<< "\n8. View Notifications"
<< "\n9. Configure Notifications"
<< "\n10. Logout"
<< "\nEnter a choice: ";
util::read(choice);
if (!handleOperation(choice))
@@ -70,7 +63,6 @@ void CustomerMenu::showMenu()
util::pressEnter();
}
}
stopEventListener();
}
/*
@@ -81,11 +73,6 @@ 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:
@@ -104,21 +91,18 @@ bool CustomerMenu::handleOperation(int choice)
viewServiceHistory();
break;
case 6:
cancelServiceBooking();
break;
case 7:
completePayments();
break;
case 8:
case 7:
viewInvoices();
break;
case 9:
case 8:
viewNotifications();
break;
case 10:
case 9:
configureNotifications();
break;
case 11:
case 10:
logout();
return false;
default:
@@ -128,25 +112,6 @@ 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();
const User* authenticatedUser = m_controller.getAuthenticatedUser();
std::string name;
if (authenticatedUser)
{
name = authenticatedUser->getName();
}
displayNewNotification(notifications, name);
}
/*
Function: logout
Description: Logs out the currently authenticated customer user.
@@ -337,46 +302,6 @@ void CustomerMenu::viewServiceHistory()
util::pressEnter();
}
/*
Function: cancelServiceBooking
Description: Allows the customer to cancel a pending service booking.
Displays the list of active bookings, lets the user select one,
and removes it from the system. If no bookings are available,
an appropriate message is shown.
Parameter: None
Return type: void
*/
void CustomerMenu::cancelServiceBooking()
{
util::clear();
std::cout << "Cancel Service Booking\n";
const User* currentUser = m_controller.getAuthenticatedUser();
std::string currentUserID = currentUser->getId();
util::Map<std::string, const ServiceBooking*> serviceBookingsByCurrentUser = m_controller.getServiceBookingsByUser(currentUserID);
util::Map<int, const ServiceBooking*> serviceBookingsMap;
auto currentPendingServiceBookings = filterActiveServiceBookings(serviceBookingsByCurrentUser);
int bookingsSize = currentPendingServiceBookings.getSize();
if (listServiceBookings(currentPendingServiceBookings, bookingsSize, serviceBookingsMap))
{
const ServiceBooking* selectedService = selectPendingServiceBookings(serviceBookingsMap);
if (selectedService)
{
m_controller.removeServiceBooking(selectedService->getId());
std::cout << "Cancelled Service booking of id " + selectedService->getId() << std::endl << std::endl;
}
else
{
std::cout << "Invalid service booking index.\n\n";
return;
}
}
else
{
std::cout << "No pending service bookings available.\n\n";
}
util::pressEnter();
}
/*
Function: completePayments
Description: Allows the customer to complete pending payments for invoices.
@@ -9,14 +9,13 @@ Date:19-May-2026
*/
#pragma once
#include "Menu.h"
#include "Controller.h"
class CustomerMenu : public Menu
class CustomerMenu
{
private:
Controller m_controller;
bool handleOperation(int choice);
void handleNotificationEvent();
public:
void showMenu();
void logout();
@@ -28,6 +27,5 @@ public:
void completePayments();
void viewInvoices();
void viewNotifications();
void cancelServiceBooking();
void configureNotifications();
};
};
@@ -1,150 +0,0 @@
/*
File: Menu.cpp
Description: Implementation file containing common menu event listener
functionality, account disable handling, and notification
event dispatching for all menu types.
Author: Trenser
Date:16-Jun-2026
*/
#include "Menu.h"
/*
Function: Menu
Description: Constructs a Menu object and initializes event handles
and menu state.
Parameter: None
Return type: None
*/
Menu::Menu()
:
m_isMenuActive(false),
m_accountDisabledEvent(NULL),
m_notificationAvailableEvent(NULL),
m_shutdownEvent(NULL) {}
/*
Function: ~Menu
Description: Destroys the Menu object and performs event listener
cleanup.
Parameter: None
Return type: None
*/
Menu::~Menu()
{
stopEventListener();
}
/*
Function: startEventListener
Description: Creates menu event handles, registers them with the
authentication service, and starts the event listener
thread.
Parameter: None
Return type: void
*/
void Menu::startEventListener()
{
if (m_isMenuActive.load())
{
return;
}
m_isMenuActive.store(true);
m_accountDisabledEvent = CreateEventA(NULL, FALSE, FALSE, NULL);
m_notificationAvailableEvent = CreateEventA(NULL, FALSE, FALSE, NULL);
m_shutdownEvent = CreateEventA(NULL, FALSE, FALSE, NULL);
m_controller.registerEvents(m_accountDisabledEvent, m_notificationAvailableEvent);
m_eventListenerThread = std::thread(&Menu::eventListenerLoop, this);
}
/*
Function: eventListenerLoop
Description: Waits for account disabled, notification available,
and shutdown events and dispatches them to the
appropriate handlers.
Parameter: None
Return type: void
*/
void Menu::eventListenerLoop()
{
HANDLE handles[3];
handles[0] = m_accountDisabledEvent;
handles[1] = m_notificationAvailableEvent;
handles[2] = m_shutdownEvent;
while (m_isMenuActive.load())
{
DWORD result = WaitForMultipleObjects(3, handles, FALSE, INFINITE);
switch (result)
{
case WAIT_OBJECT_0:
handleAccountDisabledEvent();
break;
case WAIT_OBJECT_0 + 1:
handleNotificationEvent();
break;
case WAIT_OBJECT_0 + 2:
return;
}
}
}
/*
Function: stopEventListener
Description: Stops the event listener thread and releases all
associated event handles.
Parameter: None
Return type: void
*/
void Menu::stopEventListener()
{
m_isMenuActive.store(false);
if (m_shutdownEvent)
{
SetEvent(m_shutdownEvent);
}
if (m_eventListenerThread.joinable())
{
m_eventListenerThread.join();
}
if (m_accountDisabledEvent)
{
CloseHandle(m_accountDisabledEvent);
}
if (m_notificationAvailableEvent)
{
CloseHandle(m_notificationAvailableEvent);
}
if (m_shutdownEvent)
{
CloseHandle(m_shutdownEvent);
}
m_accountDisabledEvent = NULL;
m_notificationAvailableEvent = NULL;
m_shutdownEvent = NULL;
}
/*
Function: handleAccountDisabledEvent
Description: Handles an account disabled event by marking the menu
inactive and notifying the user.
Parameter: None
Return type: void
*/
void Menu::handleAccountDisabledEvent()
{
m_isMenuActive.store(false);
const User* authenticatedUser = m_controller.getAuthenticatedUser();
std::string messageTitle = "Account Disabled";
if (authenticatedUser)
{
messageTitle += " - " + authenticatedUser->getName();
}
MessageBoxA(
GetConsoleWindow(),
"Your account has been disabled.",
messageTitle.c_str(),
MB_OK |
MB_ICONWARNING |
MB_SETFOREGROUND |
MB_TOPMOST);
}
@@ -1,33 +0,0 @@
/*
File: Menu.h
Description: Base class providing common event listener functionality
for all menu implementations.
Author: Trenser
Date:16-Jun-2026
*/
#pragma once
#include <windows.h>
#include <atomic>
#include <thread>
#include "Controller.h"
class Menu
{
protected:
Controller m_controller;
std::atomic<bool> m_isMenuActive;
HANDLE m_accountDisabledEvent;
HANDLE m_notificationAvailableEvent;
HANDLE m_shutdownEvent;
std::thread m_eventListenerThread;
void startEventListener();
void stopEventListener();
void eventListenerLoop();
void handleAccountDisabledEvent();
virtual void handleNotificationEvent() = 0;
public:
Menu();
virtual ~Menu();
};
@@ -28,7 +28,6 @@ Date: 21-May-2026
#include "Utility.h"
#include "Validator.h"
#include "Vector.h"
#include "StringHelper.h"
/*
Function: displayAllServices
@@ -915,7 +914,7 @@ inline void changePasswordHelper(Controller& controller)
util::clear();
std::cout << "Change Password\n";
std::cout << "Enter new password: ";
util::readPassword(newPassword);
util::read(newPassword);
if (!util::isPasswordValid(newPassword))
{
std::cout << "Error: Password is not strong enough!\n";
@@ -929,7 +928,7 @@ inline void changePasswordHelper(Controller& controller)
continue;
}
std::cout << "Confirm new password: ";
util::readPassword(confirmedPassword);
util::read(confirmedPassword);
if (confirmedPassword != newPassword)
{
std::cout << "Passwords are different. Try again\n";
@@ -1057,21 +1056,6 @@ inline const Service* selectServiceFromServices(const util::Map<std::string, con
{
continue;
}
bool hasDepletedItem = false;
const util::Map<std::string, InventoryItem*>& requiredItems = currentService->getRequiredInventoryItems();
for (int itemIndex = 0; itemIndex < requiredItems.getSize(); itemIndex++)
{
const InventoryItem* item = requiredItems.getValueAt(itemIndex);
if (!item || item->getQuantity() < 1)
{
hasDepletedItem = true;
break;
}
}
if (hasDepletedItem)
{
continue;
}
activeServicesMap.insert(currentIndex, currentService);
double partsCost = util::calculatePartsCost(currentService);
std::cout << std::left
@@ -1423,47 +1407,4 @@ 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
const std::string& - The name of the user currently authenticated with the system
Return type: void
*/
inline void displayNewNotification(util::Vector<const Notification*> notifications, const std::string& name)
{
const Notification* notification = nullptr;
size_t numberOfNotifications = notifications.getSize();
for (int index = 0; index < numberOfNotifications; index++)
{
if (!notification)
{
notification = notifications[index];
}
else
{
if (util::extractNumber(notification->getId()) < util::extractNumber(notifications[index]->getId()))
{
notification = notifications[index];
}
}
}
if (notification)
{
std::string messageTitle = notification->getTitle();
if (!name.empty())
{
messageTitle += " - " + name;
}
MessageBoxA(
GetConsoleWindow(),
notification->getMessage().c_str(),
messageTitle.c_str(),
MB_OK |
MB_ICONINFORMATION);
}
}
}
@@ -27,16 +27,10 @@ Returns:
*/
void TechnicianMenu::showMenu()
{
startEventListener();
while (true)
{
try
{
if (!m_isMenuActive)
{
logout();
break;
}
int choice;
util::clear();
std::cout << "Technician Menu"
@@ -58,7 +52,6 @@ void TechnicianMenu::showMenu()
util::pressEnter();
}
}
stopEventListener();
}
/*
@@ -69,11 +62,6 @@ 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:
@@ -98,25 +86,6 @@ 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();
const User* authenticatedUser = m_controller.getAuthenticatedUser();
std::string name;
if (authenticatedUser)
{
name = authenticatedUser->getName();
}
displayNewNotification(notifications, name);
}
/*
Function: displayJobs
Description: Displays all Jobs assigned to a Technician
@@ -9,13 +9,12 @@ Date:19-May-2026
#pragma once
#include "Controller.h"
#include "Menu.h"
class TechnicianMenu : public Menu
class TechnicianMenu
{
private:
Controller m_controller;
bool handleOperation(int choice);
void handleNotificationEvent();
public:
void showMenu();
void displayJobs();
@@ -23,4 +22,4 @@ public:
void viewNotifications();
void logout();
void changePassword();
};
};
@@ -27,11 +27,8 @@ void UserInterface::run()
{
try
{
if (!m_controller.initialize())
{
std::cout << "Error: Failed to initialize the system!";
return;
}
m_controller.loadSystemData();
m_controller.runSystemChecks();
bool isMenuActive = true;
while (isMenuActive)
{
@@ -52,7 +49,7 @@ void UserInterface::run()
util::pressEnter();
}
}
m_controller.shutdown();
m_controller.saveSystemData();
}
catch (const std::invalid_argument& exception)
{
@@ -109,7 +106,7 @@ void UserInterface::login()
std::cout << "Enter username: ";
util::read(username);
std::cout << "Enter password: ";
util::readPassword(password);
util::read(password);
if (m_controller.login(username, password))
{
const User* authenticatedUser = m_controller.getAuthenticatedUser();
@@ -170,7 +167,7 @@ void UserInterface::registerCustomer()
return;
}
std::cout << "Enter password: ";
util::readPassword(password);
util::read(password);
if (!util::isPasswordValid(password))
{
std::cout << "Error: Password is invalid!";
@@ -188,4 +185,4 @@ void UserInterface::registerCustomer()
m_controller.createCustomer(username, name, password, email, phone);
std::cout << "Registration is successful";
util::pressEnter();
}
}