Compare commits

..

4 Commits

Author SHA1 Message Date
joelthomastrenser b5c8b1ee9b Implement review fixes 2026-06-15 14:46:37 +05:30
joelthomastrenser 89fc662181 Implement Service Refactoring
<UserStory> 1960: Service Refactoring </UserStory>

UserStory #1960

<Changes>
1. Replaced the Controller load/save workflow with initialize() and shutdown() methods.
2. Moved startup checks such as admin verification, low stock alerts, and payment reminders into Controller::initialize().
3. Updated UserInterface to use the new Controller initialization and shutdown flow.
4. Updated DataStore::getUsers() and DataStore::getNotifications() to load data directly from shared memory.
5. Added logic to rebuild user notification mappings when user data is loaded from shared memory.
6. Implemented user and notification persistence through DataStore::saveUsers() and DataStore::saveNotifications().
7. Updated UserManagementService to use TrackedRecord-based shared memory records.
8. Added DataStore locking and unlocking to user management operations for synchronization.
9. Updated createUser(), updateUserDetails(), removeUser(), and deleteNotification() to persist changes through shared memory.
10. Removed legacy loadUsers() and saveUsers() implementations from UserManagementService.
11. Added required header dependencies for shared memory support.
</Changes>

<Test>
N/A
</Test>

<Review>
Sreeja Reghukumar, please review
</Review>
2026-06-12 13:04:23 +05:30
joelthomastrenser 929f609f24 Implement Model Refactoring
<UserStory> 1959: Model Refactoring </UserStory>

UserStory #1959

<Changes>
1. Replaced CSV-based User serialization and deserialization with SerializedUser record-based serialization for shared memory storage.
2. Implemented User::serialize() to convert User objects into fixed-size SerializedUser structures containing user details and enum values.
3. Implemented User::deserialize() to reconstruct User objects directly from SerializedUser records.
4. Updated User class interfaces to use SerializedUser types instead of std::string serialization APIs.
5. Removed legacy CSV serialization support, including CSV parsing logic and header generation functionality.
6. Added SerializedUser dependencies through SerializedRecords.h inclusion and forward declaration support.
</Changes>

<Test>
N/A
</Test>

<Review>
Sreeja Reghukumar, please review
</Review>
2026-06-12 03:19:43 +05:30
joelthomastrenser 3b1f3301d6 Implemented datastore shared memory codebase
Changes:
- Added SharedMemory module for file-backed memory-mapped storage
- Added MappingInfo, FileHeader, RecordState and TrackedRecord infrastructure
- Replaced CSV-based serialization with binary struct serialization
- Added DataStore initialization and shutdown lifecycle management
- Added datastore mutex synchronization for multi-process access
- Added shared-memory mapping configuration for all datastore entities
- Added generic loadRecords and saveRecords template infrastructure
- Added automatic datastore directory creation and .dat file storage
- Updated configuration to use binary datastore files and initial capacities
- Added enum underlying types for serialization compatibility
- Added password masking support for login, registration and password change flows
- Added Visual Studio project configuration for shared-memory components
- Added datastore-owned record caches for runtime object management
- Updated datastore APIs to return cached tracked-record collections by reference
- Added generic cache refresh and cleanup infrastructure
- Updated save operations to persist datastore caches directly
- Added automatic cache cleanup during datastore destruction
- Prepared datastore for multi-process shared-memory persistence
2026-06-12 03:18:15 +05:30
21 changed files with 555 additions and 334 deletions
@@ -157,6 +157,7 @@
<ClInclude Include="core\patterns\Observer.h" />
<ClInclude Include="core\patterns\Subject.h" />
<ClInclude Include="datastores\DataStore.h" />
<ClInclude Include="datastores\DataStoreLockGuard.h" />
<ClInclude Include="datastores\sharedmemory\FileHeader.h" />
<ClInclude Include="datastores\sharedmemory\MappingInfo.h" />
<ClInclude Include="datastores\sharedmemory\RecordState.h" />
@@ -588,63 +588,37 @@ void Controller::configureNotifications(bool paymentNotifications, bool serviceN
}
/*
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.
Function: initialize
Description: Initializes the system and run system checks to ensure critical configurations, such as verifying admin existence.
Parameters:
- None
Returns:
- void
- bool
*/
void Controller::loadSystemData()
bool Controller::initialize()
{
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();
}
auto& dataStore = DataStore::getInstance();
/*
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::runSystemChecks()
{
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
Parameters:
- None
Returns:
- void
*/
void Controller::shutdown()
{
auto& dataStore = DataStore::getInstance();
dataStore.shutdown();
}
@@ -70,7 +70,6 @@ public:
util::Vector<const Notification*> getNotifications();
void deleteNotification(const std::string& notificationID);
void configureNotifications(bool paymentNotifications, bool serviceNotifications);
void loadSystemData();
void saveSystemData();
void runSystemChecks();
bool initialize();
void shutdown();
};
@@ -228,6 +228,22 @@ Returns:
*/
util::Map<std::string, TrackedRecord<User>>& DataStore::getUsers()
{
auto users = loadRecords<User, SerializedUser>(m_users);
refreshCache(m_userCache, users);
auto& notifications = getNotifications();
int numberOfNotifications = m_notificationCache.getSize();
for (int index = 0; index < numberOfNotifications; index++)
{
Notification* notification = notifications.getValueAt(index).data;
const std::string& recipientUserId = notification->getRecipientUserId();
int userIndex = m_userCache.find(recipientUserId);
if (userIndex == -1)
{
throw std::runtime_error("Invalid recipient user ID");
}
User* user = m_userCache.getValueAt(userIndex).data;
user->addNotification(notification);
}
return m_userCache;
}
@@ -241,6 +257,8 @@ Returns:
*/
util::Map<std::string, TrackedRecord<Notification>>& DataStore::getNotifications()
{
auto notifications = loadRecords<Notification, SerializedNotification>(m_notifications);
refreshCache(m_notificationCache, notifications);
return m_notificationCache;
}
@@ -371,6 +389,8 @@ Returns:
*/
void DataStore::saveUsers()
{
saveRecords<User, SerializedUser>(m_users, m_userCache);
saveNotifications();
}
/*
@@ -383,6 +403,7 @@ Returns:
*/
void DataStore::saveNotifications()
{
saveRecords<Notification, SerializedNotification>(m_notifications, m_notificationCache);
}
/*
@@ -195,10 +195,10 @@ void DataStore::clearCache(util::Map<std::string, TrackedRecord<TObject>>&cache)
/*
Function: refreshCache
Description: Replaces the contents of an existing cache
with a newly loaded cache. Existing cached
objects are destroyed before ownership of
the new cache contents is transferred.
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.
@@ -206,8 +206,32 @@ Returns:
- None
*/
template<typename TObject>
void DataStore::refreshCache(util::Map<std::string, TrackedRecord<TObject>>&cache, util::Map<std::string, TrackedRecord<TObject>>&refreshedCache)
void DataStore::refreshCache(util::Map<std::string, TrackedRecord<TObject>>& cache, util::Map<std::string, TrackedRecord<TObject>>& refreshedCache)
{
clearCache(cache);
cache = 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);
*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);
if (cache.find(id) == -1)
{
delete oldCache.getValueAt(index).data;
}
}
}
@@ -0,0 +1,28 @@
/*
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;
};
@@ -10,6 +10,7 @@ Created: 11-June-2026
*/
#include "SharedMemory.h"
#include "Windows.h"
#include "Config.h"
/*
@@ -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;
class 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();
};
@@ -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
{
@@ -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();
};
@@ -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
@@ -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,6 +8,7 @@ Date: 19-May-2026
*/
#include <sstream>
#include "SerializedRecords.h"
#include "User.h"
#include "Notification.h"
#include "Enums.h"
@@ -28,7 +29,8 @@ 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
@@ -51,7 +53,8 @@ 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)
@@ -324,68 +327,43 @@ void User::setState(util::State status)
/*
Function: serialize
Description: Serializes the user into a CSV-formatted string.
Description: Serializes the User object into a SerializedUser record.
Parameters:
- None
Returns:
- std::string: Serialized user record
- SerializedUser: Serialized representation of the user
*/
std::string User::serialize() const
SerializedUser User::serialize() const
{
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();
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;
}
/*
Function: deserialize
Description: Deserializes a CSV-formatted string into a User object.
Description: Deserializes a SerializedUser record into a User object.
Parameters:
- record: const std::string&, serialized user record
- serializedUser: const SerializedUser&, serialized user record
Returns:
- User*: Pointer to the deserialized User object
*/
User* User::deserialize(const std::string& record)
User* User::deserialize(const SerializedUser& serializedUser)
{
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";
return Factory::getObject<User>(
serializedUser.id,
serializedUser.username,
serializedUser.password,
serializedUser.name,
serializedUser.phone,
serializedUser.email,
serializedUser.userType,
serializedUser.status);
}
@@ -14,6 +14,7 @@ Date: 19-May-2026
#include "Enums.h"
class Notification;
struct SerializedUser;
class User : public Observer
{
@@ -51,7 +52,6 @@ public:
void addNotification(Notification* notification) override;
void setRole(util::UserType role);
void setState(util::State status);
std::string serialize() const;
static User* deserialize(const std::string&);
static std::string getHeaders();
SerializedUser serialize() const;
static User* deserialize(const SerializedUser& serializedUser);
};
@@ -20,6 +20,9 @@ Date:19-May-2026
#include "UserManagementService.h"
#include "Vector.h"
#include "Validator.h"
#include "Utility.h"
#include "TrackedRecord.h"
#include "DataStoreLockGuard.h"
/*
Function: ensureAdminExists
@@ -31,12 +34,13 @@ 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);
User* user = usersMap.getValueAt(index).data;
if (user && user->getUserType() == util::UserType::ADMIN)
{
isAdminFound = true;
@@ -73,7 +77,9 @@ void UserManagementService::createUser(const std::string& username, const std::s
InventoryManagementService inventoryManagementService;
PaymentManagementService paymentManagementService;
ServiceManagementService serviceManagementService;
auto& usersMap = m_dataStore.getUsers();
DataStoreLockGuard lock(m_dataStore);
auto& trackedUsersMap = m_dataStore.getUsers();
auto usersMap = util::getObjects(trackedUsersMap);
if (util::isUsernameDuplicate(username, usersMap))
{
throw std::runtime_error("Username already exists");
@@ -87,13 +93,14 @@ 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);
usersMap.insert(newUser->getId(), newUser);
trackedUsersMap.insert(newUser->getId(), util::createNewRecord(newUser));
paymentManagementService.attach(newUser);
serviceManagementService.attach(newUser);
if (newUser->getUserType() == util::UserType::ADMIN)
{
inventoryManagementService.attach(newUser);
}
m_dataStore.saveUsers();
}
/*
@@ -107,19 +114,24 @@ Return type: void
*/
void UserManagementService::updateUserDetails(const std::string& userID, const std::string& email, const std::string& phone)
{
auto& usersMap = m_dataStore.getUsers();
int index = usersMap.find(userID);
DataStoreLockGuard lock(m_dataStore);
auto& trackedUsersMap = m_dataStore.getUsers();
auto usersMap = util::getObjects(trackedUsersMap);
int index = trackedUsersMap.find(userID);
if (index == -1)
{
throw std::runtime_error("User does not exist!\n");
}
User* user = usersMap.getValueAt(index);
User* user = trackedUsersMap.getValueAt(index).data;
bool isModified = false;
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())
{
@@ -127,9 +139,14 @@ 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);
}
/*
@@ -144,12 +161,13 @@ Throws:
*/
util::Vector<Notification*> UserManagementService::getUserNotifications(const std::string& userID)
{
auto& usersMap = m_dataStore.getUsers();
if (usersMap.find(userID) == -1)
DataStoreLockGuard lock(m_dataStore);
auto& trackedUsersMap = m_dataStore.getUsers();
if (trackedUsersMap.find(userID) == -1)
{
throw std::runtime_error("No user found with given UserID");
}
User* user = usersMap[userID];
User* user = trackedUsersMap[userID].data;
if (user)
{
auto& notifications = user->getNotifications();
@@ -169,97 +187,41 @@ util::Vector<Notification*> UserManagementService::getUserNotifications(const st
/*
Function: deleteNotification
Description: Deletes a specific notification associated with a given user ID.
Description: Marks a specific notification associated with a given user
as inactive.
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)
{
auto& usersMap = m_dataStore.getUsers();
if (usersMap.find(userID) == -1)
DataStoreLockGuard lock(m_dataStore);
auto& trackedUsersMap = m_dataStore.getUsers();
auto& trackedNotificationsMap = m_dataStore.getNotifications();
int userIndex = trackedUsersMap.find(userID);
if (userIndex == -1)
{
throw std::runtime_error("No user found with given UserID");
}
User* user = usersMap[userID];
User* user = trackedUsersMap.getValueAt(userIndex).data;
auto& notifications = user->getNotifications();
if (notifications.find(notificationID) == -1)
{
throw std::runtime_error("No notification found with given NotificationID");
}
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);
int notificationIndex = trackedNotificationsMap.find(notificationID);
if (notificationIndex == -1)
{
throw std::runtime_error("No notification found with given NotificationID");
}
notifications[notificationID]->setState(util::State::INACTIVE);
trackedNotificationsMap.getValueAt(notificationIndex).state = RecordState::MODIFIED;
m_dataStore.saveNotifications();
}
/*
@@ -270,7 +232,9 @@ Return type: util::Map<std::string, User*>
*/
util::Map<std::string, User*> UserManagementService::getUsers()
{
return m_dataStore.getUsers();
DataStoreLockGuard lock(m_dataStore);
auto users = util::getObjects(m_dataStore.getUsers());
return users;
}
/*
@@ -281,10 +245,12 @@ Return type: User*
*/
User* UserManagementService::getUser(const std::string& userID)
{
int index = m_dataStore.getUsers().find(userID);
DataStoreLockGuard lock(m_dataStore);
auto& trackedUsersMap = m_dataStore.getUsers();
int index = trackedUsersMap.find(userID);
if (index != -1)
{
return m_dataStore.getUsers().getValueAt(index);
return trackedUsersMap.getValueAt(index).data;
}
return nullptr;
}
@@ -300,10 +266,12 @@ void UserManagementService::removeUser(const std::string& userID)
InventoryManagementService inventoryManagementService;
PaymentManagementService paymentManagementService;
ServiceManagementService serviceManagementService;
int index = m_dataStore.getUsers().find(userID);
DataStoreLockGuard lock(m_dataStore);
auto& trackedUsersMap = m_dataStore.getUsers();
int index = trackedUsersMap.find(userID);
if (index != -1)
{
User* user = m_dataStore.getUsers().getValueAt(index);
User* user = trackedUsersMap.getValueAt(index).data;
if (user != nullptr)
{
if (user->getUserType() == util::UserType::CUSTOMER)
@@ -314,21 +282,37 @@ void UserManagementService::removeUser(const std::string& userID)
{
serviceManagementService.cancelTechnicianJobs(userID);
}
user->setState(util::State::INACTIVE);
inventoryManagementService.detach(user);
paymentManagementService.detach(user);
serviceManagementService.detach(user);
user->setState(util::State::INACTIVE);
trackedUsersMap.getValueAt(index).state = RecordState::MODIFIED;
m_dataStore.saveUsers();
}
}
}
/*
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*>& currentUsers = m_dataStore.getUsers();
DataStoreLockGuard lock(m_dataStore);
auto& trackedUsersMap = m_dataStore.getUsers();
util::Map<std::string, User*> currentUsers = util::getObjects(trackedUsersMap);
util::Map<std::string, User*> filteredUsersMap;
for (int iterator = 0; iterator < currentUsers.getSize(); iterator++)
for (int index = 0; index < currentUsers.getSize(); index++)
{
User* currentUser = currentUsers.getValueAt(iterator);
User* currentUser = currentUsers.getValueAt(index);
if (currentUser && currentUser->getState() == util::State::ACTIVE && currentUser->getUserType() == type)
{
filteredUsersMap.insert(currentUser->getId(), currentUser);
@@ -31,6 +31,4 @@ 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();
};
@@ -99,4 +99,80 @@ namespace util
auto observerIDs = service->getObserverIDs();
util::saveRecords(filePath, observerIDs);
}
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)
{
const std::string& key = trackedRecords.getKeyAt(index);
TObject* object = trackedRecords.getValueAt(index).data;
objects.insert(key, object);
}
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)
{
const std::string& key = trackedRecords.getKeyAt(index);
const TObject* object = trackedRecords.getValueAt(index).data;
objects.insert(key, object);
}
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;
}
@@ -27,8 +27,11 @@ void UserInterface::run()
{
try
{
m_controller.loadSystemData();
m_controller.runSystemChecks();
if (!m_controller.initialize())
{
std::cout << "Error: Failed to initialize the system!";
return;
}
bool isMenuActive = true;
while (isMenuActive)
{
@@ -49,7 +52,7 @@ void UserInterface::run()
util::pressEnter();
}
}
m_controller.saveSystemData();
m_controller.shutdown();
}
catch (const std::invalid_argument& exception)
{