Merged PR 1171: Service Management Refactoring -1927

User Story #1955
User Story #1956

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

Related work items: #1927, #1955, #1956
This commit is contained in:
Jissin Mathew
2026-06-15 15:24:40 +05:30
committed by Joel Thomas
11 changed files with 390 additions and 316 deletions
@@ -12,6 +12,8 @@ Date: 19-May-2026
#include "Config.h" #include "Config.h"
#include "SerializedRecords.h" #include "SerializedRecords.h"
#include "FileHelper.h" #include "FileHelper.h"
#include "ServiceBooking.h"
#include "JobCard.h"
/* /*
Function: DataStore Function: DataStore
@@ -252,6 +254,28 @@ Returns:
*/ */
util::Map<std::string, TrackedRecord<Service>>& DataStore::getServices() util::Map<std::string, TrackedRecord<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_serviceCache;
} }
@@ -265,6 +289,28 @@ Returns:
*/ */
util::Map<std::string, TrackedRecord<ComboPackage>>& DataStore::getComboPackages() util::Map<std::string, TrackedRecord<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; return m_comboPackageCache;
} }
@@ -291,6 +337,49 @@ Returns:
*/ */
util::Map<std::string, TrackedRecord<ServiceBooking>>& DataStore::getServiceBookings() util::Map<std::string, TrackedRecord<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_serviceBookingCache;
} }
@@ -304,9 +393,51 @@ Returns:
*/ */
util::Map<std::string, TrackedRecord<JobCard>>& DataStore::getJobCards() util::Map<std::string, TrackedRecord<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_jobCardCache;
} }
/* /*
Function: getInvoices Function: getInvoices
Description: Retrieves all invoice records from the datastore. Description: Retrieves all invoice records from the datastore.
@@ -426,6 +557,7 @@ Returns:
*/ */
void DataStore::saveServices() void DataStore::saveServices()
{ {
saveRecords<Service, SerializedService>(m_services, m_serviceCache);
} }
/* /*
@@ -438,6 +570,7 @@ Returns:
*/ */
void DataStore::saveComboPackages() void DataStore::saveComboPackages()
{ {
saveRecords<ComboPackage, SerializedComboPackage>(m_comboPackages, m_comboPackageCache);
} }
/* /*
@@ -462,6 +595,7 @@ Returns:
*/ */
void DataStore::saveServiceBookings() void DataStore::saveServiceBookings()
{ {
saveRecords<ServiceBooking, SerializedServiceBooking>(m_serviceBookings, m_serviceBookingCache);
} }
/* /*
@@ -474,6 +608,7 @@ Returns:
*/ */
void DataStore::saveJobCards() void DataStore::saveJobCards()
{ {
saveRecords<JobCard, SerializedJobCard>(m_jobCards, m_jobCardCache);
} }
/* /*
@@ -11,6 +11,7 @@ Date: 19-May-2026
#include "Map.h" #include "Map.h"
#include "MappingInfo.h" #include "MappingInfo.h"
#include "TrackedRecord.h" #include "TrackedRecord.h"
#include "SerializedRecords.h"
#include "SharedMemory.h" #include "SharedMemory.h"
#include "User.h" #include "User.h"
#include "Notification.h" #include "Notification.h"
@@ -9,6 +9,7 @@ Date: 19-May-2026
#include <sstream> #include <sstream>
#include <stdexcept> #include <stdexcept>
#include "SerializedRecords.h"
#include "ComboPackage.h" #include "ComboPackage.h"
#include "Service.h" #include "Service.h"
#include "Factory.h" #include "Factory.h"
@@ -28,7 +29,8 @@ Returns:
ComboPackage::ComboPackage() ComboPackage::ComboPackage()
: m_id("CMP" + std::to_string(++m_uid)), : m_id("CMP" + std::to_string(++m_uid)),
m_status(util::State::ACTIVE), m_status(util::State::ACTIVE),
m_discountPercentage(0.0) {} m_discountPercentage(0.0) {
}
/* /*
Function: ComboPackage Function: ComboPackage
@@ -270,72 +272,38 @@ static util::Vector<std::string> getServiceIDsAsVector(const std::string& servic
/* /*
Function: serialize Function: serialize
Description: Serializes the combo package into a CSV-formatted string. Description: Serializes the ComboPackage object into a SerializedComboPackage record.
Parameters: Parameters:
- None - None
Returns: Returns:
- std::string: Serialized combo package record - SerializedComboPackage: Serialized representation of the combo package
*/ */
std::string ComboPackage::serialize() const SerializedComboPackage ComboPackage::serialize() const
{ {
std::ostringstream serializedComboPackage; SerializedComboPackage serialized = {};
serializedComboPackage << m_id << ',' strcpy_s(serialized.id, sizeof(serialized.id), m_id.c_str());
<< m_packageName << ',' strcpy_s(serialized.packageName, sizeof(serialized.packageName), m_packageName.c_str());
<< m_discountPercentage << ',' strcpy_s(serialized.serviceIDs, sizeof(serialized.serviceIDs), getServiceIDsAsString(m_serviceIDs).c_str());
<< getServiceIDsAsString(m_serviceIDs) << ',' serialized.discountPercentage = m_discountPercentage;
<< util::getStateString(m_status); serialized.status = m_status;
return serializedComboPackage.str(); return serialized;
} }
/* /*
Function: deserialize Function: deserialize
Description: Deserializes a CSV-formatted string into a ComboPackage object. Description: Deserializes a SerializedComboPackage record into a ComboPackage object.
Parameters: Parameters:
- record: const std::string&, serialized combo package record - serializedComboPackage: const SerializedComboPackage&, serialized combo package record
Returns: Returns:
- ComboPackage*: Pointer to the deserialized ComboPackage object - ComboPackage*: Pointer to the deserialized ComboPackage object
Throws:
- std::runtime_error if data is invalid
*/ */
ComboPackage* ComboPackage::deserialize(const std::string& record) ComboPackage* ComboPackage::deserialize(const SerializedComboPackage& serializedComboPackage)
{ {
std::string id, packageName; util::Vector<std::string> serviceIDs = getServiceIDsAsVector(serializedComboPackage.serviceIDs);
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>( return Factory::getObject<ComboPackage>(
id, serializedComboPackage.id,
packageName, serializedComboPackage.packageName,
discountPercentage, serializedComboPackage.discountPercentage,
serviceIDs, serviceIDs,
status serializedComboPackage.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";
}
@@ -39,7 +39,6 @@ public:
void setDiscountPercentage(double discountPercentage); void setDiscountPercentage(double discountPercentage);
void setServices(const util::Map<std::string, Service*>& services); void setServices(const util::Map<std::string, Service*>& services);
void setState(util::State status); void setState(util::State status);
std::string serialize() const; SerializedComboPackage serialize() const;
static ComboPackage* deserialize(const std::string&); static ComboPackage* deserialize(const SerializedComboPackage&);
static std::string getHeaders();
}; };
@@ -9,6 +9,7 @@ Date:19-May-2026
#include <sstream> #include <sstream>
#include <stdexcept> #include <stdexcept>
#include "SerializedRecords.h"
#include "JobCard.h" #include "JobCard.h"
#include "Factory.h" #include "Factory.h"
#include "StringHelper.h" #include "StringHelper.h"
@@ -28,7 +29,8 @@ JobCard::JobCard()
m_booking(nullptr), m_booking(nullptr),
m_service(nullptr), m_service(nullptr),
m_technician(nullptr), m_technician(nullptr),
m_status(util::ServiceJobStatus()) {} m_status(util::ServiceJobStatus()) {
}
/* /*
Function: JobCard Function: JobCard
@@ -65,7 +67,8 @@ JobCard::JobCard(const std::string& bookingId,
m_technician(technician), m_technician(technician),
m_assignedDate(assignedDate), m_assignedDate(assignedDate),
m_status(status), m_status(status),
m_completionDate(completionDate) {} m_completionDate(completionDate) {
}
/* /*
Function: JobCard (parameterized constructor with ID) Function: JobCard (parameterized constructor with ID)
@@ -351,79 +354,41 @@ void JobCard::setCompletionDate(const util::Timestamp& completionDate)
/* /*
Function: serialize Function: serialize
Description: Serializes the job card into a CSV-formatted string. Description: Serializes the JobCard object into a SerializedJobCard record.
Parameters: Parameters:
- None - None
Returns: Returns:
- std::string: Serialized job card record - SerializedJobCard: Serialized representation of the job card
*/ */
std::string JobCard::serialize() const SerializedJobCard JobCard::serialize() const
{ {
std::ostringstream serializedJobCard; SerializedJobCard serialized = {};
serializedJobCard << m_id << ',' strcpy_s(serialized.id, sizeof(serialized.id), m_id.c_str());
<< m_bookingId << ',' strcpy_s(serialized.bookingId, sizeof(serialized.bookingId), m_bookingId.c_str());
<< m_serviceId << ',' strcpy_s(serialized.serviceId, sizeof(serialized.serviceId), m_serviceId.c_str());
<< m_technicianId << ',' strcpy_s(serialized.technicianId, sizeof(serialized.technicianId), m_technicianId.c_str());
<< m_assignedDate.toString() << ',' serialized.assignedDate = m_assignedDate;
<< util::getServiceJobStatusString(m_status) << ',' serialized.status = m_status;
<< m_completionDate.toString(); serialized.completionDate = m_completionDate;
return serializedJobCard.str(); return serialized;
} }
/* /*
Function: deserialize Function: deserialize
Description: Deserializes a CSV-formatted string into a JobCard object. Description: Deserializes a SerializedJobCard record into a JobCard object.
Parameters: Parameters:
- record: const std::string&, serialized job card record - serializedJobCard: const SerializedJobCard&, serialized job card record
Returns: Returns:
- JobCard*: Pointer to the deserialized JobCard object - JobCard*: Pointer to the deserialized JobCard object
Throws:
- std::runtime_error if timestamp parsing fails
*/ */
JobCard* JobCard::deserialize(const std::string& record) JobCard* JobCard::deserialize(const SerializedJobCard& serializedJobCard)
{ {
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>( return Factory::getObject<JobCard>(
id, serializedJobCard.id,
bookingId, serializedJobCard.bookingId,
serviceId, serializedJobCard.serviceId,
technicianId, serializedJobCard.technicianId,
assignedDate, serializedJobCard.assignedDate,
status, serializedJobCard.status,
completionDate serializedJobCard.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,6 +15,7 @@ Date:19-May-2026
class ServiceBooking; class ServiceBooking;
class Service; class Service;
class User; class User;
struct SerializedJobCard;
class JobCard class JobCard
{ {
@@ -34,11 +35,11 @@ public:
JobCard(); JobCard();
JobCard(const std::string& bookingId, JobCard(const std::string& bookingId,
ServiceBooking* booking, ServiceBooking* booking,
Service* service, Service* service,
const std::string& serviceId, const std::string& serviceId,
const std::string& technicianId, const std::string& technicianId,
User* technician, User* technician,
const util::Timestamp& assignedDate, const util::Timestamp& assignedDate,
util::ServiceJobStatus status, util::ServiceJobStatus status,
const util::Timestamp& completionDate const util::Timestamp& completionDate
); );
@@ -70,7 +71,6 @@ public:
void setAssignedDate(const util::Timestamp& assignedDate); void setAssignedDate(const util::Timestamp& assignedDate);
void setStatus(util::ServiceJobStatus status); void setStatus(util::ServiceJobStatus status);
void setCompletionDate(const util::Timestamp& completionDate); void setCompletionDate(const util::Timestamp& completionDate);
std::string serialize() const; SerializedJobCard serialize() const;
static JobCard* deserialize(const std::string&); static JobCard* deserialize(const SerializedJobCard&);
static std::string getHeaders();
}; };
@@ -8,6 +8,7 @@ Date: 19-May-2026
*/ */
#include <sstream> #include <sstream>
#include "SerializedRecords.h"
#include "Service.h" #include "Service.h"
#include "InventoryItem.h" #include "InventoryItem.h"
#include "StringHelper.h" #include "StringHelper.h"
@@ -27,7 +28,8 @@ Returns:
Service::Service() Service::Service()
: m_id("SRV" + std::to_string(++m_uid)), : m_id("SRV" + std::to_string(++m_uid)),
m_status(util::State::ACTIVE), m_status(util::State::ACTIVE),
m_laborCost(0.0) {} m_laborCost(0.0) {
}
/* /*
Function: Service Function: Service
@@ -44,7 +46,7 @@ Service::Service(const std::string& name, const util::Map<std::string, Inventory
m_name(name), m_name(name),
m_requiredInventoryItems(requiredInventoryItems), m_requiredInventoryItems(requiredInventoryItems),
m_status(util::State::ACTIVE), m_status(util::State::ACTIVE),
m_laborCost(laborCost) m_laborCost(laborCost)
{ {
int numberOfInventoryItems = m_requiredInventoryItems.getSize(); int numberOfInventoryItems = m_requiredInventoryItems.getSize();
auto inventoryItemPointers = m_requiredInventoryItems.getValues(); auto inventoryItemPointers = m_requiredInventoryItems.getValues();
@@ -266,72 +268,38 @@ static util::Vector<std::string> getInventoryItemIDsAsVector(const std::string&
/* /*
Function: serialize Function: serialize
Description: Serializes the service into a CSV-formatted string. Description: Serializes the Service object into a SerializedService record.
Parameters: Parameters:
- None - None
Returns: Returns:
- std::string: Serialized service record - SerializedService: Serialized representation of the service
*/ */
std::string Service::serialize() const SerializedService Service::serialize() const
{ {
std::ostringstream serializedService; SerializedService serialized = {};
serializedService << m_id << ',' strcpy_s(serialized.id, sizeof(serialized.id), m_id.c_str());
<< m_name << ',' strcpy_s(serialized.name, sizeof(serialized.name), m_name.c_str());
<< getInventoryItemIDsAsString(m_requiredInventoryItemIDs) << ',' strcpy_s(serialized.inventoryItemIDs, sizeof(serialized.inventoryItemIDs), getInventoryItemIDsAsString(m_requiredInventoryItemIDs).c_str());
<< m_laborCost << ',' serialized.laborCost = m_laborCost;
<< util::getStateString(m_status); serialized.status = m_status;
return serializedService.str(); return serialized;
} }
/* /*
Function: deserialize Function: deserialize
Description: Deserializes a CSV-formatted string into a Service object. Description: Deserializes a SerializedService record into a Service object.
Parameters: Parameters:
- record: const std::string&, serialized service record - serializedService: const SerializedService&, serialized service record
Returns: Returns:
- Service*: Pointer to the deserialized Service object - Service*: Pointer to the deserialized Service object
Throws:
- std::runtime_error if labor cost parsing fails
*/ */
Service* Service::deserialize(const std::string& record) Service* Service::deserialize(const SerializedService& serializedService)
{ {
std::string id, name; util::Vector<std::string> inventoryItemIDs = getInventoryItemIDsAsVector(serializedService.inventoryItemIDs);
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>( return Factory::getObject<Service>(
id, serializedService.id,
name, serializedService.name,
inventoryItemIDs, inventoryItemIDs,
laborCost, serializedService.laborCost,
status serializedService.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,7 +6,6 @@ Author: Trenser
Date: 19-May-2026 Date: 19-May-2026
*/ */
#pragma once #pragma once
#include <string> #include <string>
#include "Map.h" #include "Map.h"
@@ -14,6 +13,7 @@ Date: 19-May-2026
#include "Enums.h" #include "Enums.h"
class InventoryItem; class InventoryItem;
struct SerializedService;
class Service class Service
{ {
@@ -40,7 +40,6 @@ public:
void setRequiredInventoryItems(const util::Map<std::string, InventoryItem*>& requiredInventoryItems); void setRequiredInventoryItems(const util::Map<std::string, InventoryItem*>& requiredInventoryItems);
void setLaborCost(double laborCost); void setLaborCost(double laborCost);
void setState(util::State status); void setState(util::State status);
std::string serialize() const; SerializedService serialize() const;
static Service* deserialize(const std::string&); static Service* deserialize(const SerializedService&);
static std::string getHeaders();
}; };
@@ -6,8 +6,10 @@ Description: Implementation file containing the method definitions of the
Author: Trenser Author: Trenser
Date:19-May-2026 Date:19-May-2026
*/ */
#include <stdexcept> #include <stdexcept>
#include <sstream> #include <sstream>
#include "SerializedRecords.h"
#include "ServiceBooking.h" #include "ServiceBooking.h"
#include "Service.h" #include "Service.h"
#include "Enums.h" #include "Enums.h"
@@ -28,7 +30,8 @@ ServiceBooking::ServiceBooking()
m_customer(nullptr), m_customer(nullptr),
m_assignedTechnician(nullptr), m_assignedTechnician(nullptr),
m_status(util::ServiceJobStatus::PENDING), m_status(util::ServiceJobStatus::PENDING),
m_discountPercentage(0.0) {} m_discountPercentage(0.0) {
}
/* /*
Function: ServiceBooking Function: ServiceBooking
@@ -437,84 +440,46 @@ static util::Vector<std::string> getServiceIDsAsVector(const std::string& servic
/* /*
Function: serialize Function: serialize
Description: Serializes the service booking into a CSV-formatted string. Description: Serializes the ServiceBooking object into a SerializedServiceBooking record.
Parameters: Parameters:
- None - None
Returns: Returns:
- std::string: Serialized booking record - SerializedServiceBooking: Serialized representation of the service booking
*/ */
std::string ServiceBooking::serialize() const SerializedServiceBooking ServiceBooking::serialize() const
{ {
std::ostringstream serializedBooking; SerializedServiceBooking serialized = {};
serializedBooking << m_id << ',' strcpy_s(serialized.id, sizeof(serialized.id), m_id.c_str());
<< util::getServiceJobStatusString(m_status) << ',' strcpy_s(serialized.serviceIDs, sizeof(serialized.serviceIDs), getServiceIDsAsString(m_serviceIDs).c_str());
<< getServiceIDsAsString(m_serviceIDs) << ',' strcpy_s(serialized.customerId, sizeof(serialized.customerId), m_customerId.c_str());
<< m_customerId << ',' strcpy_s(serialized.vehicleNumber, sizeof(serialized.vehicleNumber), m_vehicleNumber.c_str());
<< m_vehicleNumber << ',' strcpy_s(serialized.vehicleBrand, sizeof(serialized.vehicleBrand), m_vehicleBrand.c_str());
<< m_vehicleBrand << ',' strcpy_s(serialized.vehicleModel, sizeof(serialized.vehicleModel), m_vehicleModel.c_str());
<< m_vehicleModel << ',' strcpy_s(serialized.assignedTechnicianId, sizeof(serialized.assignedTechnicianId), m_assignedTechnicianId.c_str());
<< m_assignedTechnicianId << ',' serialized.status = m_status;
<< m_discountPercentage << ','; serialized.discountPercentage = m_discountPercentage;
return serializedBooking.str(); return serialized;
} }
/* /*
Function: deserialize Function: deserialize
Description: Deserializes a CSV-formatted string into a ServiceBooking object. Description: Deserializes a SerializedServiceBooking record into a ServiceBooking object.
Parameters: Parameters:
- record: const std::string&, serialized booking record - serializedServiceBooking: const SerializedServiceBooking&, serialized service booking record
Returns: Returns:
- ServiceBooking*: Pointer to the deserialized ServiceBooking object - ServiceBooking*: Pointer to the deserialized ServiceBooking object
Throws:
- std::runtime_error if discount percentage parsing fails
*/ */
ServiceBooking* ServiceBooking::deserialize(const std::string& record) ServiceBooking* ServiceBooking::deserialize(const SerializedServiceBooking& serializedServiceBooking)
{ {
std::string id, customerId, vehicleNumber, vehicleBrand, vehicleModel, assignedTechnicianId; util::Vector<std::string> serviceIDs = getServiceIDsAsVector(serializedServiceBooking.serviceIDs);
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>( return Factory::getObject<ServiceBooking>(
id, serializedServiceBooking.id,
status, serializedServiceBooking.status,
serviceIDs, serviceIDs,
customerId, serializedServiceBooking.customerId,
vehicleNumber, serializedServiceBooking.vehicleNumber,
vehicleBrand, serializedServiceBooking.vehicleBrand,
vehicleModel, serializedServiceBooking.vehicleModel,
assignedTechnicianId, serializedServiceBooking.assignedTechnicianId,
discountPercentage serializedServiceBooking.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,6 +6,7 @@ Description: Header file declaring the ServiceBooking class, which represents
Author: Trenser Author: Trenser
Date:19-May-2026 Date:19-May-2026
*/ */
#pragma once #pragma once
#include <string> #include <string>
#include "Map.h" #include "Map.h"
@@ -14,6 +15,7 @@ Date:19-May-2026
class Service; class Service;
class User; class User;
struct SerializedServiceBooking;
class ServiceBooking class ServiceBooking
{ {
@@ -78,7 +80,6 @@ public:
void setAssignedTechnicianId(const std::string& assignedTechnicianId); void setAssignedTechnicianId(const std::string& assignedTechnicianId);
void setAssignedTechnician(User* assignedTechnician); void setAssignedTechnician(User* assignedTechnician);
void setDiscountPercentage(double discountPercentage); void setDiscountPercentage(double discountPercentage);
std::string serialize() const; SerializedServiceBooking serialize() const;
static ServiceBooking* deserialize(const std::string&); static ServiceBooking* deserialize(const SerializedServiceBooking&);
static std::string getHeaders();
}; };
@@ -24,6 +24,7 @@ Date:19-May-2026
#include "Timestamp.h" #include "Timestamp.h"
#include "User.h" #include "User.h"
#include "UserManagementService.h" #include "UserManagementService.h"
#include "DataStoreLockGuard.h"
#include "Utility.h" #include "Utility.h"
#include "DataStoreLockGuard.h" #include "DataStoreLockGuard.h"
@@ -46,18 +47,19 @@ void ServiceManagementService::purchaseService(const util::Vector<std::string>&
{ {
throw std::runtime_error("No user is currently logged in!"); throw std::runtime_error("No user is currently logged in!");
} }
auto& servicesMap = m_dataStore.getServices(); DataStoreLockGuard lock(m_dataStore);
auto& serviceBookingMap = m_dataStore.getServiceBookings(); auto& trackedServicesMap = m_dataStore.getServices();
auto& trackedServiceBookingMap = m_dataStore.getServiceBookings();
util::Map<std::string, Service*> selectedServices; util::Map<std::string, Service*> selectedServices;
int selectedServicesCount = serviceIDs.getSize(); int selectedServicesCount = serviceIDs.getSize();
for (int index = 0; index < selectedServicesCount; index++) for (int index = 0; index < selectedServicesCount; index++)
{ {
int serviceIndex = servicesMap.find(serviceIDs[index]); int serviceIndex = trackedServicesMap.find(serviceIDs[index]);
if (serviceIndex == -1) if (serviceIndex == -1)
{ {
throw std::runtime_error("Service not found!"); throw std::runtime_error("Service not found!");
} }
Service* service = servicesMap.getValueAt(serviceIndex); Service* service = trackedServicesMap.getValueAt(serviceIndex).data;
selectedServices[service->getId()] = service; selectedServices[service->getId()] = service;
} }
ServiceBooking* serviceBooking = Factory::getObject<ServiceBooking>(util::ServiceJobStatus::PENDING, selectedServices, authenticatedUser->getId(), authenticatedUser, vehicleNumber, vehicleBrand, vehicleModel, 0); ServiceBooking* serviceBooking = Factory::getObject<ServiceBooking>(util::ServiceJobStatus::PENDING, selectedServices, authenticatedUser->getId(), authenticatedUser, vehicleNumber, vehicleBrand, vehicleModel, 0);
@@ -65,7 +67,7 @@ void ServiceManagementService::purchaseService(const util::Vector<std::string>&
{ {
throw std::runtime_error("Failed to create service booking"); throw std::runtime_error("Failed to create service booking");
} }
serviceBookingMap[serviceBooking->getId()] = serviceBooking; trackedServiceBookingMap[serviceBooking->getId()] = util::createNewRecord(serviceBooking);
std::string title = "Service Booking succeeded"; std::string title = "Service Booking succeeded";
std::string message = "Your service booking has been successfully placed with ID " + serviceBooking->getId(); std::string message = "Your service booking has been successfully placed with ID " + serviceBooking->getId();
sendNotification(authenticatedUser, title, message); sendNotification(authenticatedUser, title, message);
@@ -90,21 +92,22 @@ void ServiceManagementService::purchaseComboPackage(const std::string& comboPack
{ {
throw std::runtime_error("No user is currently logged in!"); throw std::runtime_error("No user is currently logged in!");
} }
auto& comboPackagesMap = m_dataStore.getComboPackages(); DataStoreLockGuard lock(m_dataStore);
auto& serviceBookingMap = m_dataStore.getServiceBookings(); auto& trackedComboPackagesMap = m_dataStore.getComboPackages();
int comboPackageIndex = comboPackagesMap.find(comboPackageID); auto& trackedServiceBookingMap = m_dataStore.getServiceBookings();
int comboPackageIndex = trackedComboPackagesMap.find(comboPackageID);
if (comboPackageIndex == -1) if (comboPackageIndex == -1)
{ {
throw std::runtime_error("Combo Package not found!"); throw std::runtime_error("Combo Package not found!");
} }
const ComboPackage* comboPackage = comboPackagesMap[comboPackageID]; const ComboPackage* comboPackage = trackedComboPackagesMap[comboPackageID].data;
util::Map<std::string, Service*> selectedServices = comboPackage->getServices(); util::Map<std::string, Service*> selectedServices = comboPackage->getServices();
ServiceBooking* serviceBooking = Factory::getObject<ServiceBooking>(util::ServiceJobStatus::PENDING, selectedServices, authenticatedUser->getId(), authenticatedUser, vehicleNumber, vehicleBrand, vehicleModel, comboPackage->getDiscountPercentage()); ServiceBooking* serviceBooking = Factory::getObject<ServiceBooking>(util::ServiceJobStatus::PENDING, selectedServices, authenticatedUser->getId(), authenticatedUser, vehicleNumber, vehicleBrand, vehicleModel, comboPackage->getDiscountPercentage());
if (serviceBooking == nullptr) if (serviceBooking == nullptr)
{ {
throw std::runtime_error("Failed to create combo package service booking"); throw std::runtime_error("Failed to create combo package service booking");
} }
serviceBookingMap[serviceBooking->getId()] = serviceBooking; trackedServiceBookingMap[serviceBooking->getId()] = util::createNewRecord(serviceBooking);
std::string title = "Combo Package Service Booking succeeded"; std::string title = "Combo Package Service Booking succeeded";
std::string message = "Your service booking for the combo package has been successfully placed with ID " + serviceBooking->getId(); std::string message = "Your service booking for the combo package has been successfully placed with ID " + serviceBooking->getId();
sendNotification(authenticatedUser, title, message); sendNotification(authenticatedUser, title, message);
@@ -205,7 +208,7 @@ Description: Restores inventory quantities for all required items in the service
Parameter: ServiceBooking* booking - Pointer to the booking whose inventory items need to be restored Parameter: ServiceBooking* booking - Pointer to the booking whose inventory items need to be restored
Return type: void Return type: void
*/ */
static void restoreInventory(ServiceBooking* booking) static void restoreInventory(ServiceBooking* booking, util::Map<std::string, TrackedRecord<InventoryItem>>& trackedInventoryItems)
{ {
const int INCREMENT_VALUE = 1; const int INCREMENT_VALUE = 1;
if (!booking) if (!booking)
@@ -224,9 +227,17 @@ static void restoreInventory(ServiceBooking* booking)
for (int InventoryIterator = 0; InventoryIterator < items.getSize(); ++InventoryIterator) for (int InventoryIterator = 0; InventoryIterator < items.getSize(); ++InventoryIterator)
{ {
InventoryItem* item = items.getValueAt(InventoryIterator); InventoryItem* item = items.getValueAt(InventoryIterator);
const std::string& currentItemId = item->getId();
int itemIndex = trackedInventoryItems.find(currentItemId);
if (itemIndex == -1)
{
continue;
}
auto& currentTrackedInventoryItem = trackedInventoryItems.getValueAt(itemIndex);
if (item) if (item)
{ {
item->setQuantity(item->getQuantity() + INCREMENT_VALUE); item->setQuantity(item->getQuantity() + INCREMENT_VALUE);
currentTrackedInventoryItem.state = RecordState::MODIFIED;
} }
} }
} }
@@ -244,23 +255,28 @@ Parameters:
util::UserType userType - Type of user initiating cancellation (CUSTOMER or TECHNICIAN) util::UserType userType - Type of user initiating cancellation (CUSTOMER or TECHNICIAN)
Return type: void Return type: void
*/ */
static void processBookingCancellation(ServiceBooking* booking, static void processBookingCancellation(TrackedRecord<ServiceBooking>& trackedBooking,
util::Map<std::string, JobCard*>& jobs, util::Map<std::string, TrackedRecord<JobCard>>& jobs,
ServiceManagementService& currentService, ServiceManagementService& currentService,
util::UserType userType) util::UserType userType,
util::Map<std::string, TrackedRecord<InventoryItem>>& trackedInventoryItems)
{ {
ServiceBooking* booking = trackedBooking.data;
if (!booking) if (!booking)
{ {
return; return;
} }
const std::string& bookingId = booking->getId();
for (int jobIterator = 0; jobIterator < jobs.getSize(); ++jobIterator) for (int jobIterator = 0; jobIterator < jobs.getSize(); ++jobIterator)
{ {
JobCard* jobCard = jobs.getValueAt(jobIterator); auto& trackedJobCard = jobs.getValueAt(jobIterator);
JobCard* jobCard = trackedJobCard.data;
if (!jobCard || jobCard->getBookingId() != booking->getId() || jobCard->getStatus() == util::ServiceJobStatus::CANCELLED) if (!jobCard || jobCard->getBookingId() != booking->getId() || jobCard->getStatus() == util::ServiceJobStatus::CANCELLED)
{ {
continue; continue;
} }
jobCard->setStatus(util::ServiceJobStatus::CANCELLED); jobCard->setStatus(util::ServiceJobStatus::CANCELLED);
trackedJobCard.state = RecordState::MODIFIED;
if (userType == util::UserType::CUSTOMER) if (userType == util::UserType::CUSTOMER)
{ {
if (User* technician = booking->getAssignedTechnician()) if (User* technician = booking->getAssignedTechnician())
@@ -293,7 +309,8 @@ static void processBookingCancellation(ServiceBooking* booking,
} }
booking->setAssignedTechnician(nullptr); booking->setAssignedTechnician(nullptr);
booking->setAssignedTechnicianId(""); booking->setAssignedTechnicianId("");
restoreInventory(booking); trackedBooking.state = RecordState::MODIFIED;
restoreInventory(booking, trackedInventoryItems);
} }
/* /*
@@ -306,22 +323,25 @@ Return type: void
*/ */
void ServiceManagementService::cancelCustomerServiceBookings(const std::string& customerID) void ServiceManagementService::cancelCustomerServiceBookings(const std::string& customerID)
{ {
auto& users = m_dataStore.getUsers(); DataStoreLockGuard lock(m_dataStore);
int userIndex = users.find(customerID); auto& trackedUsers = m_dataStore.getUsers();
int userIndex = trackedUsers.find(customerID);
if (userIndex == -1) if (userIndex == -1)
{ {
throw std::runtime_error("User not found: " + customerID); throw std::runtime_error("User not found: " + customerID);
} }
User* customer = users.getValueAt(userIndex); User* customer = trackedUsers.getValueAt(userIndex).data;
if (!customer) if (!customer)
{ {
throw std::runtime_error("User not found: " + customerID); throw std::runtime_error("User not found: " + customerID);
} }
auto& bookings = m_dataStore.getServiceBookings(); auto& trackedBookings = m_dataStore.getServiceBookings();
auto& jobs = m_dataStore.getJobCards(); auto& trackedJobs = m_dataStore.getJobCards();
for (int iteratorOne = 0; iteratorOne < bookings.getSize(); iteratorOne++) auto& trackedInventoryItems = m_dataStore.getInventoryItems();
for (int iteratorOne = 0; iteratorOne < trackedBookings.getSize(); iteratorOne++)
{ {
ServiceBooking* booking = bookings.getValueAt(iteratorOne); auto& trackedBooking = trackedBookings.getValueAt(iteratorOne);
ServiceBooking* booking = trackedBooking.data;
if (!booking) if (!booking)
{ {
continue; continue;
@@ -336,8 +356,12 @@ void ServiceManagementService::cancelCustomerServiceBookings(const std::string&
{ {
continue; continue;
} }
processBookingCancellation(booking, jobs, *this, util::UserType::CUSTOMER); processBookingCancellation(trackedBooking, trackedJobs, *this, util::UserType::CUSTOMER, trackedInventoryItems);
} }
m_dataStore.saveUsers();
m_dataStore.saveServiceBookings();
m_dataStore.saveJobCards();
m_dataStore.saveInventoryItems();
} }
/* /*
@@ -349,22 +373,25 @@ Return type: void
*/ */
void ServiceManagementService::cancelTechnicianJobs(const std::string& technicianID) void ServiceManagementService::cancelTechnicianJobs(const std::string& technicianID)
{ {
auto& users = m_dataStore.getUsers(); DataStoreLockGuard lock(m_dataStore);
int userIndex = users.find(technicianID); auto& trackedUsers = m_dataStore.getUsers();
int userIndex = trackedUsers.find(technicianID);
if (userIndex == -1) if (userIndex == -1)
{ {
throw std::runtime_error("User not found: " + technicianID); throw std::runtime_error("User not found: " + technicianID);
} }
User* technician = users.getValueAt(userIndex); User* technician = trackedUsers.getValueAt(userIndex).data;
if (!technician) if (!technician)
{ {
throw std::runtime_error("User not found: " + technicianID); throw std::runtime_error("User not found: " + technicianID);
} }
auto& bookings = m_dataStore.getServiceBookings(); auto& trackedBookings = m_dataStore.getServiceBookings();
auto& jobs = m_dataStore.getJobCards(); auto& trackedJobs = m_dataStore.getJobCards();
for (int iteratorOne = 0; iteratorOne < bookings.getSize(); iteratorOne++) auto& trackedInventoryItems = m_dataStore.getInventoryItems();
for (int iteratorOne = 0; iteratorOne < trackedBookings.getSize(); iteratorOne++)
{ {
ServiceBooking* booking = bookings.getValueAt(iteratorOne); auto& trackedBooking = trackedBookings.getValueAt(iteratorOne);
ServiceBooking* booking = trackedBooking.data;
if (!booking) if (!booking)
{ {
continue; continue;
@@ -385,8 +412,12 @@ void ServiceManagementService::cancelTechnicianJobs(const std::string& technicia
{ {
continue; continue;
} }
processBookingCancellation(booking, jobs, *this, util::UserType::TECHNICIAN); processBookingCancellation(trackedBooking, trackedJobs, *this, util::UserType::TECHNICIAN, trackedInventoryItems);
} }
m_dataStore.saveUsers();
m_dataStore.saveInventoryItems();
m_dataStore.saveServiceBookings();
m_dataStore.saveJobCards();
} }
/* /*
@@ -400,6 +431,7 @@ Return type: void
*/ */
void ServiceManagementService::createComboPackage(const std::string& packageName, const util::Vector<std::string>& serviceIDsInNewCombo, double discountPercentage) void ServiceManagementService::createComboPackage(const std::string& packageName, const util::Vector<std::string>& serviceIDsInNewCombo, double discountPercentage)
{ {
DataStoreLockGuard lock(m_dataStore);
if (packageName.empty()) if (packageName.empty())
{ {
throw std::invalid_argument("The Combo Package Name cannot be empty.\n"); throw std::invalid_argument("The Combo Package Name cannot be empty.\n");
@@ -412,19 +444,19 @@ void ServiceManagementService::createComboPackage(const std::string& packageName
{ {
throw std::invalid_argument("Discount percentage must be between 0 and 100."); throw std::invalid_argument("Discount percentage must be between 0 and 100.");
} }
auto& servicesMap = m_dataStore.getServices(); auto& trackedServicesMap = m_dataStore.getServices();
for (int index = 0; index < serviceIDsInNewCombo.getSize(); index++) for (int index = 0; index < serviceIDsInNewCombo.getSize(); index++)
{ {
const std::string serviceid = serviceIDsInNewCombo[index]; const std::string& serviceid = serviceIDsInNewCombo[index];
if (servicesMap.find(serviceid) == -1) if (trackedServicesMap.find(serviceid) == -1)
{ {
throw std::runtime_error("Service ID not found: " + serviceid); throw std::runtime_error("Service ID not found: " + serviceid);
} }
} }
auto& comboPackageMap = m_dataStore.getComboPackages(); auto& trackedComboPackageMap = m_dataStore.getComboPackages();
for (int iterator = 0; iterator < comboPackageMap.getSize(); iterator++) for (int iterator = 0; iterator < trackedComboPackageMap.getSize(); iterator++)
{ {
ComboPackage* existingCombos = comboPackageMap.getValueAt(iterator); ComboPackage* existingCombos = trackedComboPackageMap.getValueAt(iterator).data;
const util::Map<std::string, Service*>& servicesInsideExistingCombos = existingCombos->getServices(); const util::Map<std::string, Service*>& servicesInsideExistingCombos = existingCombos->getServices();
if (servicesInsideExistingCombos.getSize() == serviceIDsInNewCombo.getSize()) if (servicesInsideExistingCombos.getSize() == serviceIDsInNewCombo.getSize())
{ {
@@ -448,15 +480,16 @@ void ServiceManagementService::createComboPackage(const std::string& packageName
for (int iteratorOne = 0; iteratorOne < serviceIDsInNewCombo.getSize(); iteratorOne++) for (int iteratorOne = 0; iteratorOne < serviceIDsInNewCombo.getSize(); iteratorOne++)
{ {
const std::string& serviceId = serviceIDsInNewCombo[iteratorOne]; const std::string& serviceId = serviceIDsInNewCombo[iteratorOne];
int serviceIndex = servicesMap.find(serviceId); int serviceIndex = trackedServicesMap.find(serviceId);
if (serviceIndex == -1) if (serviceIndex == -1)
{ {
throw std::runtime_error("Service ID not found: " + serviceId); throw std::runtime_error("Service ID not found: " + serviceId);
} }
selectedServices.insert(serviceId, servicesMap.getValueAt(serviceIndex)); selectedServices.insert(serviceId, trackedServicesMap.getValueAt(serviceIndex).data);
} }
ComboPackage* newComboPackage = Factory::getObject<ComboPackage>(packageName, discountPercentage, selectedServices); ComboPackage* newComboPackage = Factory::getObject<ComboPackage>(packageName, discountPercentage, selectedServices);
comboPackageMap.insert(newComboPackage->getId(), newComboPackage); trackedComboPackageMap.insert(newComboPackage->getId(), util::createNewRecord(newComboPackage));
m_dataStore.saveComboPackages();
} }
/* /*
@@ -467,7 +500,10 @@ Return type: util::Map<std::string, ComboPackage*>
*/ */
util::Map<std::string, ComboPackage*> ServiceManagementService::getComboPackages() util::Map<std::string, ComboPackage*> ServiceManagementService::getComboPackages()
{ {
return m_dataStore.getComboPackages(); DataStoreLockGuard lock(m_dataStore);
util::Map<std::string, ComboPackage*> comboPackages;
comboPackages = util::getObjects(m_dataStore.getComboPackages());
return comboPackages;
} }
/* /*
@@ -478,14 +514,17 @@ Return type: void
*/ */
void ServiceManagementService::removeComboPackage(const std::string& comboPackageID) void ServiceManagementService::removeComboPackage(const std::string& comboPackageID)
{ {
DataStoreLockGuard lock(m_dataStore);
bool removed = false; bool removed = false;
util::Map<std::string, ComboPackage*>& currentComboPackages = m_dataStore.getComboPackages(); auto& trackedComboPackages = m_dataStore.getComboPackages();
for (int iterator = 0; iterator < currentComboPackages.getSize(); iterator++) for (int iterator = 0; iterator < trackedComboPackages.getSize(); iterator++)
{ {
ComboPackage* currentComboPackage = currentComboPackages.getValueAt(iterator); auto& comboPackage = trackedComboPackages.getValueAt(iterator);
ComboPackage* currentComboPackage = comboPackage.data;
if (currentComboPackage && currentComboPackage->getId() == comboPackageID) if (currentComboPackage && currentComboPackage->getId() == comboPackageID)
{ {
currentComboPackage->setState(util::State::INACTIVE); currentComboPackage->setState(util::State::INACTIVE);
comboPackage.state = RecordState::MODIFIED;
removed = true; removed = true;
break; break;
} }
@@ -494,6 +533,7 @@ void ServiceManagementService::removeComboPackage(const std::string& comboPackag
{ {
throw std::runtime_error("Combo package with ID '" + comboPackageID + "' not found."); throw std::runtime_error("Combo package with ID '" + comboPackageID + "' not found.");
} }
m_dataStore.saveComboPackages();
} }
/* /*
@@ -506,7 +546,10 @@ Returns:
*/ */
util::Map<std::string, ServiceBooking*> ServiceManagementService::getServiceBookings() util::Map<std::string, ServiceBooking*> ServiceManagementService::getServiceBookings()
{ {
return m_dataStore.getServiceBookings(); DataStoreLockGuard lock(m_dataStore);
util::Map<std::string, ServiceBooking*> serviceBookings;
serviceBookings = util::getObjects(m_dataStore.getServiceBookings());
return serviceBookings;
} }
/* /*
@@ -545,9 +588,10 @@ Throws:
*/ */
void ServiceManagementService::createJobCard(const std::string& bookingID, const std::string& technicianID, const std::string& serviceID) void ServiceManagementService::createJobCard(const std::string& bookingID, const std::string& technicianID, const std::string& serviceID)
{ {
DataStoreLockGuard lock(m_dataStore);
UserManagementService m_userManagementService; UserManagementService m_userManagementService;
ServiceBooking* currentBooking = getServiceBooking(bookingID); ServiceBooking* currentBooking = getServiceBooking(bookingID);
auto& currentJobCards = m_dataStore.getJobCards(); auto& currentTrackedJobCards = m_dataStore.getJobCards();
if (currentBooking == nullptr) if (currentBooking == nullptr)
{ {
throw std::runtime_error("Service Booking not available"); throw std::runtime_error("Service Booking not available");
@@ -593,7 +637,7 @@ void ServiceManagementService::createJobCard(const std::string& bookingID, const
JobCard* jobCard = Factory::getObject<JobCard>(bookingID, currentBooking, currentService, serviceID, technicianID, selectedTechnician, util::Timestamp(), util::ServiceJobStatus::STARTED, util::Timestamp()); JobCard* jobCard = Factory::getObject<JobCard>(bookingID, currentBooking, currentService, serviceID, technicianID, selectedTechnician, util::Timestamp(), util::ServiceJobStatus::STARTED, util::Timestamp());
if (jobCard) if (jobCard)
{ {
currentJobCards.insert(jobCard->getId(), jobCard); currentTrackedJobCards.insert(jobCard->getId(), util::createNewRecord(jobCard));
sendNotification(selectedTechnician, title, message); sendNotification(selectedTechnician, title, message);
} }
else else
@@ -603,6 +647,7 @@ void ServiceManagementService::createJobCard(const std::string& bookingID, const
title = "Technician assigned"; title = "Technician assigned";
message = "A technician has been assigned to your Service Booking with ID " + bookingID; message = "A technician has been assigned to your Service Booking with ID " + bookingID;
sendNotification(currentBooking->getCustomer(), title, message); sendNotification(currentBooking->getCustomer(), title, message);
m_dataStore.saveJobCards();
} }
/* /*
@@ -620,15 +665,17 @@ Throws:
*/ */
void ServiceManagementService::createService(const std::string& name, const util::Vector<std::string>& inventoryItemIDs, double laborCost) void ServiceManagementService::createService(const std::string& name, const util::Vector<std::string>& inventoryItemIDs, double laborCost)
{ {
DataStoreLockGuard lock(m_dataStore);
util::Map<std::string, InventoryItem*> currentServiceInventoryItems; util::Map<std::string, InventoryItem*> currentServiceInventoryItems;
auto inventoryItems = m_dataStore.getInventoryItems(); auto& trackedInventoryItems = m_dataStore.getInventoryItems();
auto& currentServices = m_dataStore.getServices();
for (int iteratorOne =0; iteratorOne < inventoryItemIDs.getSize(); iteratorOne++) for (int iteratorOne =0; iteratorOne < inventoryItemIDs.getSize(); iteratorOne++)
{ {
std::string currentItemID = inventoryItemIDs[iteratorOne]; std::string currentItemID = inventoryItemIDs[iteratorOne];
bool itemFound = false; bool itemFound = false;
for (int iteratorTwo = 0; iteratorTwo < inventoryItems.getSize(); iteratorTwo++) for (int iteratorTwo = 0; iteratorTwo < trackedInventoryItems.getSize(); iteratorTwo++)
{ {
InventoryItem* currentInventoryItem = inventoryItems.getValueAt(iteratorTwo); InventoryItem* currentInventoryItem = trackedInventoryItems.getValueAt(iteratorTwo).data;
if (currentInventoryItem && currentInventoryItem->getId() == currentItemID) if (currentInventoryItem && currentInventoryItem->getId() == currentItemID)
{ {
itemFound = true; itemFound = true;
@@ -646,12 +693,12 @@ void ServiceManagementService::createService(const std::string& name, const util
{ {
throw std::runtime_error("Unable to create new service."); throw std::runtime_error("Unable to create new service.");
} }
util::Map<std::string, Service*>& currentServices = m_dataStore.getServices();
if (currentServices.find(newService->getId()) != -1) if (currentServices.find(newService->getId()) != -1)
{ {
throw std::runtime_error("Service with this ID Already exists."); throw std::runtime_error("Service with this ID Already exists.");
} }
currentServices.insert(newService->getId(), newService); currentServices.insert(newService->getId(), util::createNewRecord(newService));
m_dataStore.saveServices();
} }
/* /*
@@ -664,7 +711,10 @@ Returns:
*/ */
util::Map<std::string, Service*> ServiceManagementService::getServices() util::Map<std::string, Service*> ServiceManagementService::getServices()
{ {
return m_dataStore.getServices(); DataStoreLockGuard lock(m_dataStore);
util::Map<std::string, Service*> services;
services = util::getObjects(m_dataStore.getServices());
return services;
} }
/* /*
@@ -679,14 +729,19 @@ Throws:
*/ */
void ServiceManagementService::removeService(const std::string& serviceID) void ServiceManagementService::removeService(const std::string& serviceID)
{ {
util::Map<std::string, Service*>& currentServices = m_dataStore.getServices(); DataStoreLockGuard lock(m_dataStore);
util::Map<std::string, ComboPackage*>& currentComboPackages = m_dataStore.getComboPackages(); auto& currentTrackedServices = m_dataStore.getServices();
if (currentServices.find(serviceID) != -1) auto& currentTrackedComboPackages = m_dataStore.getComboPackages();
if (currentTrackedServices.find(serviceID) != -1)
{ {
currentServices.getValueAt(currentServices.find(serviceID))->setState(util::State::INACTIVE); int serviceIndex, comboPackageIndex;
for (int iterator = 0; iterator < currentComboPackages.getSize(); iterator++) serviceIndex = currentTrackedServices.find(serviceID);
currentTrackedServices.getValueAt(serviceIndex).data->setState(util::State::INACTIVE);
currentTrackedServices.getValueAt(serviceIndex).state = RecordState::MODIFIED;
for (int iterator = 0; iterator < currentTrackedComboPackages.getSize(); iterator++)
{ {
ComboPackage* currentComboPackage = currentComboPackages.getValueAt(iterator); comboPackageIndex = iterator;
ComboPackage* currentComboPackage = currentTrackedComboPackages.getValueAt(iterator).data;
if (currentComboPackage && currentComboPackage->getState() == util::State::ACTIVE) if (currentComboPackage && currentComboPackage->getState() == util::State::ACTIVE)
{ {
util::Map<std::string, Service*> currentServices = currentComboPackage->getServices(); util::Map<std::string, Service*> currentServices = currentComboPackage->getServices();
@@ -696,6 +751,7 @@ void ServiceManagementService::removeService(const std::string& serviceID)
if (currentService->getId() == serviceID) if (currentService->getId() == serviceID)
{ {
currentComboPackage->setState(util::State::INACTIVE); currentComboPackage->setState(util::State::INACTIVE);
currentTrackedComboPackages.getValueAt(comboPackageIndex).state = RecordState::MODIFIED;
break; break;
} }
} }
@@ -706,6 +762,8 @@ void ServiceManagementService::removeService(const std::string& serviceID)
{ {
throw std::runtime_error("Service not found."); throw std::runtime_error("Service not found.");
} }
m_dataStore.saveServices();
m_dataStore.saveComboPackages();
} }
/* /*
@@ -744,11 +802,12 @@ Returns:
*/ */
util::Map<std::string, JobCard*> ServiceManagementService::getJobCards(const std::string& technicianID) util::Map<std::string, JobCard*> ServiceManagementService::getJobCards(const std::string& technicianID)
{ {
util::Map<std::string, JobCard*> jobCards = m_dataStore.getJobCards(); DataStoreLockGuard lock(m_dataStore);
auto& trackedJobCards = m_dataStore.getJobCards();
util::Map<std::string, JobCard*> technicianJobCards; util::Map<std::string, JobCard*> technicianJobCards;
for (int iterator = 0; iterator < jobCards.getSize(); iterator++) for (int iterator = 0; iterator < trackedJobCards.getSize(); iterator++)
{ {
JobCard* currentJobCard = jobCards.getValueAt(iterator); JobCard* currentJobCard = trackedJobCards.getValueAt(iterator).data;
if (currentJobCard->getTechnicianId() == technicianID) if (currentJobCard->getTechnicianId() == technicianID)
{ {
technicianJobCards.insert(currentJobCard->getId(), currentJobCard); technicianJobCards.insert(currentJobCard->getId(), currentJobCard);
@@ -797,6 +856,7 @@ Returns:
*/ */
void ServiceManagementService::updateJobStatus(const std::string& jobID) void ServiceManagementService::updateJobStatus(const std::string& jobID)
{ {
DataStoreLockGuard lock(m_dataStore);
AuthenticationManagementService authenticationManagementService; AuthenticationManagementService authenticationManagementService;
PaymentManagementService paymentManagementService; PaymentManagementService paymentManagementService;
bool jobStatusUpdated = false, serviceBookingCompleted; bool jobStatusUpdated = false, serviceBookingCompleted;
@@ -811,8 +871,15 @@ void ServiceManagementService::updateJobStatus(const std::string& jobID)
{ {
throw std::runtime_error("No job cards assigned to the technician."); throw std::runtime_error("No job cards assigned to the technician.");
} }
auto& trackedJobCards = m_dataStore.getJobCards();
auto& trackedServiceBookings = m_dataStore.getServiceBookings();
if (currentAssignedJobs.find(jobID) != -1) if (currentAssignedJobs.find(jobID) != -1)
{ {
int jobIndex = trackedJobCards.find(jobID);
if (jobIndex == -1)
{
throw std::runtime_error("Unable to fetch current job.");
}
currentJob = currentAssignedJobs.getValueAt(currentAssignedJobs.find(jobID)); currentJob = currentAssignedJobs.getValueAt(currentAssignedJobs.find(jobID));
if (currentJob == nullptr) if (currentJob == nullptr)
{ {
@@ -821,16 +888,20 @@ void ServiceManagementService::updateJobStatus(const std::string& jobID)
if (currentJob->getStatus() == util::ServiceJobStatus::STARTED) if (currentJob->getStatus() == util::ServiceJobStatus::STARTED)
{ {
currentJob->setStatus(util::ServiceJobStatus::IN_PROGRESS); currentJob->setStatus(util::ServiceJobStatus::IN_PROGRESS);
trackedJobCards.getValueAt(jobIndex).state = RecordState::MODIFIED;
jobStatusUpdated = true; jobStatusUpdated = true;
} }
else if (currentJob->getStatus() == util::ServiceJobStatus::IN_PROGRESS) else if (currentJob->getStatus() == util::ServiceJobStatus::IN_PROGRESS)
{ {
currentJob->setStatus(util::ServiceJobStatus::COMPLETED); currentJob->setStatus(util::ServiceJobStatus::COMPLETED);
trackedJobCards.getValueAt(jobIndex).state = RecordState::MODIFIED;
jobStatusUpdated = true; jobStatusUpdated = true;
serviceBookingCompleted = hasCompletedAllJobs(currentJob->getBookingId(), currentAssignedJobs); serviceBookingCompleted = hasCompletedAllJobs(currentJob->getBookingId(), currentAssignedJobs);
if (serviceBookingCompleted) if (serviceBookingCompleted)
{ {
const std::string& bookingId = currentJob->getBookingId();
currentJob->getBooking()->setStatus(util::ServiceJobStatus::COMPLETED); currentJob->getBooking()->setStatus(util::ServiceJobStatus::COMPLETED);
trackedServiceBookings.getValueAt(trackedServiceBookings.find(bookingId)).state = RecordState::MODIFIED;
paymentManagementService.generateInvoice(currentJob->getBooking()); paymentManagementService.generateInvoice(currentJob->getBooking());
std::string title = "Service Booking completed. Invoice Generated."; std::string title = "Service Booking completed. Invoice Generated.";
std::string message = "Services completed for the booking and invoice generated."; std::string message = "Services completed for the booking and invoice generated.";
@@ -846,4 +917,6 @@ void ServiceManagementService::updateJobStatus(const std::string& jobID)
{ {
throw std::runtime_error("Failed to update job status. Job may already be completed."); throw std::runtime_error("Failed to update job status. Job may already be completed.");
} }
m_dataStore.saveJobCards();
m_dataStore.saveServiceBookings();
} }