Files
Training-VehicleService-May26/Trenser.VehicleServiceSystem/Trenser.VehicleServiceSystem/services/ServiceManagementService.cpp
T

966 lines
38 KiB
C++

/*
File: ServiceManagementService.cpp
Description: Implementation file containing the method definitions of the
ServiceManagementService class, including service and combo package
purchasing logic, booking creation, and notification handling.
Author: Trenser
Date:19-May-2026
*/
#include <stdexcept>
#include "AuthenticationManagementService.h"
#include "ComboPackage.h"
#include "Config.h"
#include "DataStore.h"
#include "Enums.h"
#include "Factory.h"
#include "InventoryItem.h"
#include "JobCard.h"
#include "NotificationManagementService.h"
#include "PaymentManagementService.h"
#include "Service.h"
#include "ServiceBooking.h"
#include "ServiceManagementService.h"
#include "Timestamp.h"
#include "User.h"
#include "UserManagementService.h"
#include "DataStoreLockGuard.h"
#include "Utility.h"
#include "DataStoreLockGuard.h"
#include "EventManager.h"
/*
Function: notifyAllAdmins
Description: Sends a notification to all users with the ADMIN role.
Iterates through the provided user collection and delivers
the specified notification to each administrator using the
ServiceManagementService notification mechanism.
Parameter: const std::string& title - notification title
Parameter: const std::string& message - notification message
Parameter: const util::Map<std::string, TrackedRecord<User>>& users - collection of tracked users
Parameter: ServiceManagementService* serviceManagementService - service used to dispatch notifications
Return type: void
*/
static void notifyAllAdmins(const std::string& title, const std::string& message, const util::Map<std::string, TrackedRecord<User>>& users, ServiceManagementService* serviceManagementService)
{
int numberOfUsers = users.getSize();
for (int index = 0; index < numberOfUsers; index++)
{
User* user = users.getValueAt(index).data;
if (user->getUserType() == util::UserType::ADMIN)
{
serviceManagementService->sendNotification(user, title, message);
}
}
}
/*
Function: purchaseService
Description: Creates a new service booking for the authenticated user. Validates
service IDs, retrieves services from the DataStore, and generates a
booking. Sends a notification upon successful booking.
Parameter: const util::Vector<std::string>& serviceIDs - IDs of services to purchase
const std::string& vehicleNumber - vehicle registration number
const std::string& vehicleBrand - brand of the vehicle
const std::string& vehicleModel - model of the vehicle
Return type: void
*/
void ServiceManagementService::purchaseService(const util::Vector<std::string>& serviceIDs, const std::string& vehicleNumber, const std::string& vehicleBrand, const std::string& vehicleModel)
{
AuthenticationManagementService::ensureAuthorization();
AuthenticationManagementService m_authenticationManagementService;
auto authenticatedUser = m_authenticationManagementService.getAuthenticatedUser();
if (authenticatedUser == nullptr)
{
throw std::runtime_error("No user is currently logged in!");
}
DataStoreLockGuard lock(m_dataStore);
auto& trackedServicesMap = m_dataStore.getServices();
auto& trackedServiceBookingMap = m_dataStore.getServiceBookings();
util::Map<std::string, Service*> selectedServices;
int selectedServicesCount = serviceIDs.getSize();
for (int index = 0; index < selectedServicesCount; index++)
{
int serviceIndex = trackedServicesMap.find(serviceIDs[index]);
if (serviceIndex == -1)
{
throw std::runtime_error("Service not found!");
}
Service* service = trackedServicesMap.getValueAt(serviceIndex).data;
selectedServices[service->getId()] = service;
}
ServiceBooking* serviceBooking = Factory::getObject<ServiceBooking>(util::ServiceJobStatus::PENDING, selectedServices, authenticatedUser->getId(), authenticatedUser, vehicleNumber, vehicleBrand, vehicleModel, 0);
if (serviceBooking == nullptr)
{
throw std::runtime_error("Failed to create service booking");
}
trackedServiceBookingMap[serviceBooking->getId()] = util::createNewRecord(serviceBooking);
std::string title = "Service Booking succeeded";
std::string message = "Your service booking has been successfully placed with ID " + serviceBooking->getId();
sendNotification(authenticatedUser, title, message);
m_dataStore.saveServiceBookings();
notifyAllAdmins("New Service Order Available", "A new service order has been placed with Service Booking ID " + serviceBooking->getId(), m_dataStore.getUsers(), this);
}
/*
Function: purchaseComboPackage
Description: Creates a new service booking for a combo package. Validates the combo
package ID, retrieves services from the package, and generates a booking
with the applicable discount. Sends a notification upon successful booking.
Parameter: const std::string& comboPackageID - ID of the combo package
const std::string& vehicleNumber - vehicle registration number
const std::string& vehicleBrand - brand of the vehicle
const std::string& vehicleModel - model of the vehicle
Return type: void
*/
void ServiceManagementService::purchaseComboPackage(const std::string& comboPackageID, const std::string& vehicleNumber, const std::string& vehicleBrand, const std::string& vehicleModel)
{
AuthenticationManagementService::ensureAuthorization();
AuthenticationManagementService m_authenticationManagementService;
auto authenticatedUser = m_authenticationManagementService.getAuthenticatedUser();
if (authenticatedUser == nullptr)
{
throw std::runtime_error("No user is currently logged in!");
}
DataStoreLockGuard lock(m_dataStore);
auto& trackedComboPackagesMap = m_dataStore.getComboPackages();
auto& trackedServiceBookingMap = m_dataStore.getServiceBookings();
int comboPackageIndex = trackedComboPackagesMap.find(comboPackageID);
if (comboPackageIndex == -1)
{
throw std::runtime_error("Combo Package not found!");
}
const ComboPackage* comboPackage = trackedComboPackagesMap[comboPackageID].data;
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());
if (serviceBooking == nullptr)
{
throw std::runtime_error("Failed to create combo package service booking");
}
trackedServiceBookingMap[serviceBooking->getId()] = util::createNewRecord(serviceBooking);
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();
sendNotification(authenticatedUser, title, message);
notifyAllAdmins("New Combo Package Order Available", "A new combo package order has been placed with Service Booking ID " + serviceBooking->getId(), m_dataStore.getUsers(), this);
}
util::Map<std::string, User*> ServiceManagementService::m_observers{};
/*
Function: attach
Description: Attaches a user as an observer to the ServiceManagementService for receiving notifications.
Parameters:
- user: Pointer to the User object to be attached.
Returns:
- void
*/
void ServiceManagementService::attach(User* user)
{
DataStoreLockGuard lock(m_dataStore);
m_observers.clear();
m_observers = m_dataStore.getServiceManagementObservers();
if (user)
{
const std::string& userID = user->getId();
if (m_observers.find(userID) == -1)
{
m_observers[userID] = user;
}
}
m_dataStore.saveServiceManagementObservers(m_observers);
}
/*
Function: detach
Description: Detaches a user from the observer list of the ServiceManagementService.
Parameters:
- user: Pointer to the User object to be detached.
Returns:
- void
*/
void ServiceManagementService::detach(User* user)
{
DataStoreLockGuard lock(m_dataStore);
m_observers.clear();
m_observers = m_dataStore.getServiceManagementObservers();
if (user)
{
const std::string& userID = user->getId();
if (m_observers.find(userID) != -1)
{
m_observers.remove(userID);
}
}
m_dataStore.saveServiceManagementObservers(m_observers);
}
/*
Function: sendNotification
Description: Sends a notification to a user if they are registered as an observer.
Parameters:
- user: Pointer to the User object to receive the notification.
- title: Title of the notification.
- message: Message content of the notification.
Returns:
- void
Throws:
- std::runtime_error if notification creation fails.
*/
void ServiceManagementService::sendNotification(User* user, const std::string& title, const std::string& message)
{
if (!user)
{
return;
}
DataStoreLockGuard lock(m_dataStore);
m_observers = m_dataStore.getServiceManagementObservers();
if (m_observers.find(user->getId()) == -1)
{
return;
}
auto& trackedNotificationsMap = m_dataStore.getNotifications();
Notification* notification = Factory::getObject<Notification>(
user->getId(),
title,
message,
util::Timestamp());
if (!notification)
{
throw std::runtime_error("Failed to create notification");
}
trackedNotificationsMap.insert(notification->getId(), util::createNewRecord(notification));
m_dataStore.saveNotifications();
EventManager::sendNotificationAvailableEvent(user->getId());
}
/*
Function: restoreInventory
Description: Restores inventory quantities for all required items in the services associated
with a given booking. Each item's quantity is incremented by a fixed value.
Parameter: ServiceBooking* booking - Pointer to the booking whose inventory items need to be restored
Return type: void
*/
static void restoreInventory(ServiceBooking* booking, util::Map<std::string, TrackedRecord<InventoryItem>>& trackedInventoryItems)
{
const int INCREMENT_VALUE = 1;
if (!booking)
{
return;
}
const auto& services = booking->getServices();
for (int serviceIterator = 0; serviceIterator < services.getSize(); ++serviceIterator)
{
Service* service = services.getValueAt(serviceIterator);
if (!service)
{
continue;
}
const auto& items = service->getRequiredInventoryItems();
for (int InventoryIterator = 0; InventoryIterator < items.getSize(); ++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)
{
item->setQuantity(item->getQuantity() + INCREMENT_VALUE);
currentTrackedInventoryItem.state = RecordState::MODIFIED;
}
}
}
}
/*
Function: processBookingCancellation
Description: Handles cancellation or reassignment of a service booking based on user type.
Cancels associated job cards, updates booking status, clears technician assignments,
restores inventory, and sends appropriate notifications.
Parameters:
ServiceBooking* booking - The booking to cancel or reset
util::Map<std::string, JobCard*>& jobs - Collection of job cards to update
ServiceManagementService& currentService - Service layer for notifications
util::UserType userType - Type of user initiating cancellation (CUSTOMER or TECHNICIAN)
Return type: void
*/
static void processBookingCancellation(TrackedRecord<ServiceBooking>& trackedBooking,
util::Map<std::string, TrackedRecord<JobCard>>& jobs,
ServiceManagementService& currentService,
util::UserType userType,
util::Map<std::string, TrackedRecord<InventoryItem>>& trackedInventoryItems)
{
ServiceBooking* booking = trackedBooking.data;
if (!booking)
{
return;
}
const std::string& bookingId = booking->getId();
for (int jobIterator = 0; jobIterator < jobs.getSize(); ++jobIterator)
{
auto& trackedJobCard = jobs.getValueAt(jobIterator);
JobCard* jobCard = trackedJobCard.data;
if (!jobCard || jobCard->getBookingId() != booking->getId() || jobCard->getStatus() == util::ServiceJobStatus::CANCELLED)
{
continue;
}
jobCard->setStatus(util::ServiceJobStatus::CANCELLED);
trackedJobCard.state = RecordState::MODIFIED;
if (userType == util::UserType::CUSTOMER)
{
if (User* technician = booking->getAssignedTechnician())
{
const std::string jobTitle = "Job Cancelled";
const std::string jobMessage = "Your job card has been cancelled and the inventory has been restocked.";
currentService.sendNotification(technician, jobTitle, jobMessage);
}
}
}
if (userType == util::UserType::CUSTOMER)
{
booking->setStatus(util::ServiceJobStatus::CANCELLED);
if (User* technician = booking->getAssignedTechnician())
{
const std::string title = "Customer Service Cancelled";
const std::string message = "Your assigned job card has been cancelled and the inventory has been restocked.";
currentService.sendNotification(technician, title, message);
}
}
else if (userType == util::UserType::TECHNICIAN)
{
booking->setStatus(util::ServiceJobStatus::PENDING);
if (User* customer = booking->getCustomer())
{
const std::string title = "Technician Unavailable";
const std::string message = "Your booking has been reset to pending and we will reassign a new technician shortly.";
currentService.sendNotification(customer, title, message);
}
}
booking->setAssignedTechnician(nullptr);
booking->setAssignedTechnicianId("");
trackedBooking.state = RecordState::MODIFIED;
restoreInventory(booking, trackedInventoryItems);
}
/*
Function: cancelCustomerServiceBookings
Description: Cancels all service bookings associated with a given customer or technician.
Updates booking status, resets customer/technician assignments, sends notifications,
and restocks inventory items.
Parameter: const std::string& userID - ID of the customer or technician
Return type: void
*/
void ServiceManagementService::cancelCustomerServiceBookings(const std::string& customerID)
{
DataStoreLockGuard lock(m_dataStore);
auto& trackedUsers = m_dataStore.getUsers();
int userIndex = trackedUsers.find(customerID);
if (userIndex == -1)
{
throw std::runtime_error("User not found: " + customerID);
}
User* customer = trackedUsers.getValueAt(userIndex).data;
if (!customer)
{
throw std::runtime_error("User not found: " + customerID);
}
auto& trackedBookings = m_dataStore.getServiceBookings();
auto& trackedJobs = m_dataStore.getJobCards();
auto& trackedInventoryItems = m_dataStore.getInventoryItems();
for (int iteratorOne = 0; iteratorOne < trackedBookings.getSize(); iteratorOne++)
{
auto& trackedBooking = trackedBookings.getValueAt(iteratorOne);
ServiceBooking* booking = trackedBooking.data;
if (!booking)
{
continue;
}
if (booking->getCustomerId() != customerID)
{
continue;
}
if (booking->getStatus() != util::ServiceJobStatus::PENDING &&
booking->getStatus() != util::ServiceJobStatus::STARTED &&
booking->getStatus() != util::ServiceJobStatus::IN_PROGRESS)
{
continue;
}
processBookingCancellation(trackedBooking, trackedJobs, *this, util::UserType::CUSTOMER, trackedInventoryItems);
}
m_dataStore.saveUsers();
m_dataStore.saveServiceBookings();
m_dataStore.saveJobCards();
m_dataStore.saveInventoryItems();
}
/*
Function: cancelTechnicianJobs
Description: Cancels all jobs assigned to a technician. Updates job status, sends notifications,
and restocks inventory items used in the service.
Parameter: const std::string& technicianID - ID of the technician
Return type: void
*/
void ServiceManagementService::cancelTechnicianJobs(const std::string& technicianID)
{
DataStoreLockGuard lock(m_dataStore);
auto& trackedUsers = m_dataStore.getUsers();
int userIndex = trackedUsers.find(technicianID);
if (userIndex == -1)
{
throw std::runtime_error("User not found: " + technicianID);
}
User* technician = trackedUsers.getValueAt(userIndex).data;
if (!technician)
{
throw std::runtime_error("User not found: " + technicianID);
}
auto& trackedBookings = m_dataStore.getServiceBookings();
auto& trackedJobs = m_dataStore.getJobCards();
auto& trackedInventoryItems = m_dataStore.getInventoryItems();
for (int iteratorOne = 0; iteratorOne < trackedBookings.getSize(); iteratorOne++)
{
auto& trackedBooking = trackedBookings.getValueAt(iteratorOne);
ServiceBooking* booking = trackedBooking.data;
if (!booking)
{
continue;
}
std::string technicianId = booking->getAssignedTechnicianId();
if (technicianId != technicianID)
{
continue;
}
if (booking->getStatus() != util::ServiceJobStatus::PENDING &&
booking->getStatus() != util::ServiceJobStatus::STARTED &&
booking->getStatus() != util::ServiceJobStatus::IN_PROGRESS)
{
continue;
}
User* customer = booking->getCustomer();
if (!customer)
{
continue;
}
processBookingCancellation(trackedBooking, trackedJobs, *this, util::UserType::TECHNICIAN, trackedInventoryItems);
}
m_dataStore.saveUsers();
m_dataStore.saveInventoryItems();
m_dataStore.saveServiceBookings();
m_dataStore.saveJobCards();
}
/*
Function: createComboPackage
Description: Creates a new combo package with two services and a discount percentage.
Validates service IDs, ensures uniqueness, and inserts the new package into the DataStore.
Parameter: const std::string& packageName - name of the combo package
const util::Vector<std::string>& serviceIDsInNewCombo - list of service IDs
double discountPercentage - discount percentage for the package
Return type: void
*/
void ServiceManagementService::createComboPackage(const std::string& packageName, const util::Vector<std::string>& serviceIDsInNewCombo, double discountPercentage)
{
AuthenticationManagementService::ensureAuthorization();
DataStoreLockGuard lock(m_dataStore);
if (packageName.empty())
{
throw std::invalid_argument("The Combo Package Name cannot be empty.\n");
}
if (serviceIDsInNewCombo.getSize() < 2 || serviceIDsInNewCombo.getSize() > 2)
{
throw std::invalid_argument("Combo package must contain only two services.");
}
if (discountPercentage < 0.0 || discountPercentage > 100.0)
{
throw std::invalid_argument("Discount percentage must be between 0 and 100.");
}
auto& trackedServicesMap = m_dataStore.getServices();
for (int index = 0; index < serviceIDsInNewCombo.getSize(); index++)
{
const std::string& serviceid = serviceIDsInNewCombo[index];
if (trackedServicesMap.find(serviceid) == -1)
{
throw std::runtime_error("Service ID not found: " + serviceid);
}
}
auto& trackedComboPackageMap = m_dataStore.getComboPackages();
for (int iterator = 0; iterator < trackedComboPackageMap.getSize(); iterator++)
{
ComboPackage* existingCombos = trackedComboPackageMap.getValueAt(iterator).data;
const util::Map<std::string, Service*>& servicesInsideExistingCombos = existingCombos->getServices();
if (servicesInsideExistingCombos.getSize() == serviceIDsInNewCombo.getSize())
{
bool isIdentical = true;
for (int serviceIterator = 0; serviceIterator < serviceIDsInNewCombo.getSize(); serviceIterator++)
{
const std::string& id = serviceIDsInNewCombo[serviceIterator];
if (servicesInsideExistingCombos.find(id) == -1)
{
isIdentical = false;
break;
}
}
if (isIdentical)
{
throw std::runtime_error("A combo package with the same services already exists.");
}
}
}
util::Map<std::string, Service*> selectedServices;
for (int iteratorOne = 0; iteratorOne < serviceIDsInNewCombo.getSize(); iteratorOne++)
{
const std::string& serviceId = serviceIDsInNewCombo[iteratorOne];
int serviceIndex = trackedServicesMap.find(serviceId);
if (serviceIndex == -1)
{
throw std::runtime_error("Service ID not found: " + serviceId);
}
selectedServices.insert(serviceId, trackedServicesMap.getValueAt(serviceIndex).data);
}
ComboPackage* newComboPackage = Factory::getObject<ComboPackage>(packageName, discountPercentage, selectedServices);
trackedComboPackageMap.insert(newComboPackage->getId(), util::createNewRecord(newComboPackage));
m_dataStore.saveComboPackages();
}
/*
Function: getComboPackages
Description: Retrieves all combo packages stored in the DataStore.
Parameter: None
Return type: util::Map<std::string, ComboPackage*>
*/
util::Map<std::string, ComboPackage*> ServiceManagementService::getComboPackages()
{
AuthenticationManagementService::ensureAuthorization();
DataStoreLockGuard lock(m_dataStore);
util::Map<std::string, ComboPackage*> comboPackages;
comboPackages = util::getObjects(m_dataStore.getComboPackages());
return comboPackages;
}
/*
Function: removeComboPackage
Description: Removes a combo package by marking it inactive. Throws an exception if the package ID is not found.
Parameter: const std::string& comboPackageID - ID of the combo package
Return type: void
*/
void ServiceManagementService::removeComboPackage(const std::string& comboPackageID)
{
AuthenticationManagementService::ensureAuthorization();
DataStoreLockGuard lock(m_dataStore);
bool removed = false;
auto& trackedComboPackages = m_dataStore.getComboPackages();
for (int iterator = 0; iterator < trackedComboPackages.getSize(); iterator++)
{
auto& comboPackage = trackedComboPackages.getValueAt(iterator);
ComboPackage* currentComboPackage = comboPackage.data;
if (currentComboPackage && currentComboPackage->getId() == comboPackageID)
{
currentComboPackage->setState(util::State::INACTIVE);
comboPackage.state = RecordState::MODIFIED;
removed = true;
break;
}
}
if (!removed)
{
throw std::runtime_error("Combo package with ID '" + comboPackageID + "' not found.");
}
m_dataStore.saveComboPackages();
}
/*
Function: getServiceBookings
Description: Retrieves all service bookings from the datastore.
Parameters:
- None
Returns:
- util::Map<std::string, ServiceBooking*> containing all service bookings
*/
util::Map<std::string, ServiceBooking*> ServiceManagementService::getServiceBookings()
{
AuthenticationManagementService::ensureAuthorization();
DataStoreLockGuard lock(m_dataStore);
util::Map<std::string, ServiceBooking*> serviceBookings;
serviceBookings = util::getObjects(m_dataStore.getServiceBookings());
return serviceBookings;
}
/*
Function: getServiceBooking
Description: Retrieves a specific service booking by its ID.
Parameters:
- serviceID: std::string, ID of the service booking
Returns:
- ServiceBooking*: Pointer to the service booking, or nullptr if not found
*/
ServiceBooking* ServiceManagementService::getServiceBooking(const std::string& serviceID)
{
AuthenticationManagementService::ensureAuthorization();
auto currentServiceBookings = getServiceBookings();
for (int iterator = 0; iterator < currentServiceBookings.getSize(); iterator++)
{
if (currentServiceBookings.getValueAt(iterator)->getId() == serviceID)
{
return currentServiceBookings.getValueAt(iterator);
}
}
return nullptr;
}
/*
Function: createJobCard
Description: Creates a job card for a given service booking, service, and technician.
Validates booking, service, technician, and inventory availability before creation.
Parameters:
- bookingID: std::string, ID of the service booking
- technicianID: std::string, ID of the technician
- serviceID: std::string, ID of the service
Returns:
- void
Throws:
- std::runtime_error if booking, service, technician, or inventory validation fails
*/
void ServiceManagementService::createJobCard(const std::string& bookingID, const std::string& technicianID, const std::string& serviceID)
{
AuthenticationManagementService::ensureAuthorization();
DataStoreLockGuard lock(m_dataStore);
UserManagementService m_userManagementService;
ServiceBooking* currentBooking = getServiceBooking(bookingID);
auto& currentTrackedJobCards = m_dataStore.getJobCards();
if (currentBooking == nullptr)
{
throw std::runtime_error("Service Booking not available");
}
auto& currentServices = currentBooking->getServices();
if (currentServices.find(serviceID) == -1)
{
throw std::runtime_error("Invalid service Id");
}
Service* currentService = currentServices.getValueAt(currentServices.find(serviceID));
User* selectedTechnician = m_userManagementService.getUser(technicianID);
if (selectedTechnician == nullptr)
{
throw std::runtime_error("Technician not available");
}
auto& inventoryItems = currentService->getRequiredInventoryItems();
for (int iterator = 0; iterator < inventoryItems.getSize(); iterator++)
{
InventoryItem* currentInventoryItem = inventoryItems.getValueAt(iterator);
if (currentInventoryItem && currentInventoryItem->getQuantity() == 0)
{
std::string errorMessage = "Failed to create job card, " + currentInventoryItem->getPartName() + " is out of stock.";
throw std::runtime_error(errorMessage);
}
}
for (int iterator = 0; iterator < inventoryItems.getSize(); iterator++)
{
InventoryItem* currentInventoryItem = inventoryItems.getValueAt(iterator);
if (currentInventoryItem)
{
int currentStockQuantity = currentInventoryItem->getQuantity();
currentInventoryItem->setQuantity(currentStockQuantity - 1);
}
}
currentBooking->setAssignedTechnician(selectedTechnician);
currentBooking->setAssignedTechnicianId(selectedTechnician->getId());
if (currentBooking->getStatus() == util::ServiceJobStatus::PENDING)
{
currentBooking->setStatus(util::ServiceJobStatus::STARTED);
}
std::string title = "Job card created";
std::string message = "Job card created for the service and you are assigned for that.";
JobCard* jobCard = Factory::getObject<JobCard>(bookingID, currentBooking, currentService, serviceID, technicianID, selectedTechnician, util::Timestamp(), util::ServiceJobStatus::STARTED, util::Timestamp());
if (jobCard)
{
currentTrackedJobCards.insert(jobCard->getId(), util::createNewRecord(jobCard));
sendNotification(selectedTechnician, title, message);
}
else
{
throw std::runtime_error("Failed to create job card.");
}
title = "Technician assigned";
message = "A technician has been assigned to your Service Booking with ID " + bookingID;
sendNotification(currentBooking->getCustomer(), title, message);
m_dataStore.saveJobCards();
}
/*
Function: createService
Description: Creates a new service with associated inventory items and labor cost.
Validates inventory items before creation.
Parameters:
- name: std::string, name of the service
- inventoryItemIDs: util::Vector<std::string>, IDs of required inventory items
- laborCost: double, labor cost for the service
Returns:
- void
Throws:
- std::runtime_error if inventory items are not found or service creation fails
*/
void ServiceManagementService::createService(const std::string& name, const util::Vector<std::string>& inventoryItemIDs, double laborCost)
{
AuthenticationManagementService::ensureAuthorization();
DataStoreLockGuard lock(m_dataStore);
util::Map<std::string, InventoryItem*> currentServiceInventoryItems;
auto& trackedInventoryItems = m_dataStore.getInventoryItems();
auto& currentServices = m_dataStore.getServices();
for (int iteratorOne =0; iteratorOne < inventoryItemIDs.getSize(); iteratorOne++)
{
std::string currentItemID = inventoryItemIDs[iteratorOne];
bool itemFound = false;
for (int iteratorTwo = 0; iteratorTwo < trackedInventoryItems.getSize(); iteratorTwo++)
{
InventoryItem* currentInventoryItem = trackedInventoryItems.getValueAt(iteratorTwo).data;
if (currentInventoryItem && currentInventoryItem->getId() == currentItemID)
{
itemFound = true;
currentServiceInventoryItems.insert(currentInventoryItem->getId(), currentInventoryItem);
break;
}
}
if (!itemFound)
{
throw std::runtime_error("Inventory item with ID '" + currentItemID + "' not found.");
}
}
Service* newService = Factory::getObject<Service>(name, currentServiceInventoryItems, laborCost);
if (newService == nullptr)
{
throw std::runtime_error("Unable to create new service.");
}
if (currentServices.find(newService->getId()) != -1)
{
throw std::runtime_error("Service with this ID Already exists.");
}
currentServices.insert(newService->getId(), util::createNewRecord(newService));
m_dataStore.saveServices();
}
/*
Function: getServices
Description: Retrieves all services from the datastore.
Parameters:
- None
Returns:
- util::Map<std::string, Service*> containing all services
*/
util::Map<std::string, Service*> ServiceManagementService::getServices()
{
AuthenticationManagementService::ensureAuthorization();
DataStoreLockGuard lock(m_dataStore);
util::Map<std::string, Service*> services;
services = util::getObjects(m_dataStore.getServices());
return services;
}
/*
Function: removeService
Description: Marks a service as inactive by its ID.
Parameters:
- serviceID: std::string, ID of the service
Returns:
- void
Throws:
- std::runtime_error if the service is not found
*/
void ServiceManagementService::removeService(const std::string& serviceID)
{
AuthenticationManagementService::ensureAuthorization();
DataStoreLockGuard lock(m_dataStore);
auto& currentTrackedServices = m_dataStore.getServices();
auto& currentTrackedComboPackages = m_dataStore.getComboPackages();
if (currentTrackedServices.find(serviceID) != -1)
{
int serviceIndex, comboPackageIndex;
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++)
{
comboPackageIndex = iterator;
ComboPackage* currentComboPackage = currentTrackedComboPackages.getValueAt(iterator).data;
if (currentComboPackage && currentComboPackage->getState() == util::State::ACTIVE)
{
util::Map<std::string, Service*> currentServices = currentComboPackage->getServices();
for (int iterator = 0; iterator < currentServices.getSize(); iterator++)
{
auto currentService = currentServices.getValueAt(iterator);
if (currentService->getId() == serviceID)
{
currentComboPackage->setState(util::State::INACTIVE);
currentTrackedComboPackages.getValueAt(comboPackageIndex).state = RecordState::MODIFIED;
break;
}
}
}
}
}
else
{
throw std::runtime_error("Service not found.");
}
m_dataStore.saveServices();
m_dataStore.saveComboPackages();
}
/*
Function: getServiceBookings (overloaded)
Description: Retrieves all service bookings for a specific customer.
Parameters:
- customerID: std::string, ID of the customer
Returns:
- util::Map<std::string, ServiceBooking*> containing bookings for the customer
*/
util::Map<std::string, ServiceBooking*> ServiceManagementService::getServiceBookings(const std::string& customerID)
{
AuthenticationManagementService::ensureAuthorization();
util::Map<std::string, ServiceBooking*> currentServiceBookings = getServiceBookings();
util::Map<std::string, ServiceBooking*> currentUserServiceBookings;
if (currentServiceBookings.getSize() != 0)
{
for (int iterator = 0; iterator < currentServiceBookings.getSize(); iterator++)
{
auto currentServiceBooking = currentServiceBookings.getValueAt(iterator);
if (currentServiceBooking->getCustomerId() == customerID)
{
currentUserServiceBookings.insert(currentServiceBooking->getId(), currentServiceBooking);
}
}
}
return currentUserServiceBookings;
}
/*
Function: getJobCards
Description: Retrieves all job cards assigned to a specific technician.
Parameters:
- technicianID: std::string, ID of the technician
Returns:
- util::Map<std::string, JobCard*> containing job cards assigned to the technician
*/
util::Map<std::string, JobCard*> ServiceManagementService::getJobCards(const std::string& technicianID)
{
AuthenticationManagementService::ensureAuthorization();
DataStoreLockGuard lock(m_dataStore);
auto& trackedJobCards = m_dataStore.getJobCards();
util::Map<std::string, JobCard*> technicianJobCards;
for (int iterator = 0; iterator < trackedJobCards.getSize(); iterator++)
{
JobCard* currentJobCard = trackedJobCards.getValueAt(iterator).data;
if (currentJobCard->getTechnicianId() == technicianID)
{
technicianJobCards.insert(currentJobCard->getId(), currentJobCard);
}
}
return technicianJobCards;
}
/*
Function: hasCompletedAllJobs (static helper)
Description: Checks if all job cards for a given service booking are completed.
Parameters:
- bookingId: std::string, ID of the service booking
- currentAssignedJobs: util::Map<std::string, JobCard*>&, map of assigned job cards
Returns:
- bool: True if all job cards are completed, False otherwise
*/
static bool hasCompletedAllJobs(std::string bookingId, util::Map<std::string, JobCard*>& currentAssignedJobs)
{
for (int iterator = 0; iterator < currentAssignedJobs.getSize(); iterator++)
{
JobCard* currentJob = currentAssignedJobs.getValueAt(iterator);
if (currentJob->getBookingId() == bookingId)
{
if (currentJob->getStatus() != util::ServiceJobStatus::COMPLETED && currentJob->getStatus() != util::ServiceJobStatus::CANCELLED)
{
return false;
}
}
}
return true;
}
/*
Function: updateJobStatus
Description:
Updates the status of a job card assigned to the currently authenticated technician.
- If the job is STARTED, it moves to IN_PROGRESS.
- If the job is IN_PROGRESS, it moves to COMPLETED.
When all jobs in a service booking are completed, the booking status is updated,
an invoice is generated, and a notification is sent to the customer.
Parameters:
- jobID: const std::string&, unique identifier of the job card to update.
Returns:
- void
*/
void ServiceManagementService::updateJobStatus(const std::string& jobID)
{
AuthenticationManagementService::ensureAuthorization();
DataStoreLockGuard lock(m_dataStore);
AuthenticationManagementService authenticationManagementService;
PaymentManagementService paymentManagementService;
bool jobStatusUpdated = false, serviceBookingCompleted;
JobCard* currentJob;
User* currentTechnician = authenticationManagementService.getAuthenticatedUser();
if (currentTechnician == nullptr)
{
throw std::runtime_error("Unable to fetch current technician.");
}
util::Map<std::string, JobCard*> currentAssignedJobs = getJobCards(currentTechnician->getId());
if (currentAssignedJobs.getSize() == 0)
{
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)
{
int jobIndex = trackedJobCards.find(jobID);
if (jobIndex == -1)
{
throw std::runtime_error("Unable to fetch current job.");
}
currentJob = currentAssignedJobs.getValueAt(currentAssignedJobs.find(jobID));
if (currentJob == nullptr)
{
throw std::runtime_error("Unable to fetch current job.");
}
if (currentJob->getStatus() == util::ServiceJobStatus::STARTED)
{
currentJob->setStatus(util::ServiceJobStatus::IN_PROGRESS);
trackedJobCards.getValueAt(jobIndex).state = RecordState::MODIFIED;
jobStatusUpdated = true;
}
else if (currentJob->getStatus() == util::ServiceJobStatus::IN_PROGRESS)
{
currentJob->setStatus(util::ServiceJobStatus::COMPLETED);
trackedJobCards.getValueAt(jobIndex).state = RecordState::MODIFIED;
jobStatusUpdated = true;
serviceBookingCompleted = hasCompletedAllJobs(currentJob->getBookingId(), currentAssignedJobs);
if (serviceBookingCompleted)
{
const std::string& bookingId = currentJob->getBookingId();
currentJob->getBooking()->setStatus(util::ServiceJobStatus::COMPLETED);
trackedServiceBookings.getValueAt(trackedServiceBookings.find(bookingId)).state = RecordState::MODIFIED;
paymentManagementService.generateInvoice(currentJob->getBooking());
std::string title = "Service Booking completed. Invoice Generated.";
std::string message = "Services completed for the booking and invoice generated.";
sendNotification(currentJob->getBooking()->getCustomer(), title, message);
}
}
}
else
{
throw std::runtime_error("Failed to update job status. Job may already be completed.");
}
if (!jobStatusUpdated)
{
throw std::runtime_error("Failed to update job status. Job may already be completed.");
}
m_dataStore.saveJobCards();
m_dataStore.saveServiceBookings();
}