Compare commits

..

4 Commits

Author SHA1 Message Date
joelthomastrenser 95d1c9a04e Merge branch 'develop' into develop-sm-inventory-management 2026-06-15 15:25:50 +05:30
Jissin Mathew 2963d05ea1 Merged PR 1171: Service Management Refactoring -1927
User Story #1955
User Story #1956

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

Related work items: #1927, #1955, #1956
2026-06-15 15:24:40 +05:30
Avinash Rajesh f0c7d27e6c Implement Service Refactoring
<UserStory> 1958: Service Refactoring </UserStory>
UserStory #1958
<Changes>

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

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

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

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

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

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

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

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

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

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

</Changes>

<Test>

N/A

</Test>

<Review>

Sreeja Reghukumar

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

UserStory #1957

<Changes>

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

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

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

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

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

</Changes>

<Test>

N/A

</Test>

<Review>

Sreeja Reghukumar

</Review>
2026-06-12 15:30:34 +05:30
4 changed files with 88 additions and 92 deletions
@@ -324,6 +324,8 @@ Returns:
*/ */
util::Map<std::string, TrackedRecord<InventoryItem>>& DataStore::getInventoryItems() util::Map<std::string, TrackedRecord<InventoryItem>>& DataStore::getInventoryItems()
{ {
auto inventoryItems = loadRecords<InventoryItem, SerializedInventoryItem>(m_inventoryItems);
refreshCache(m_inventoryItemCache, inventoryItems);
return m_inventoryItemCache; return m_inventoryItemCache;
} }
@@ -583,6 +585,7 @@ Returns:
*/ */
void DataStore::saveInventoryItems() void DataStore::saveInventoryItems()
{ {
saveRecords<InventoryItem, SerializedInventoryItem>(m_inventoryItems, m_inventoryItemCache);
} }
/* /*
@@ -8,6 +8,7 @@ Date: 19-May-2026
#include <sstream> #include <sstream>
#include <stdexcept> #include <stdexcept>
#include "SerializedRecords.h"
#include "Factory.h" #include "Factory.h"
#include "StringHelper.h" #include "StringHelper.h"
#include "InventoryItem.h" #include "InventoryItem.h"
@@ -27,7 +28,8 @@ InventoryItem::InventoryItem()
: m_id("IIM" + std::to_string(++m_uid)), : m_id("IIM" + std::to_string(++m_uid)),
m_quantity(0), m_quantity(0),
m_status(util::State::ACTIVE), m_status(util::State::ACTIVE),
m_price(0.0) {} m_price(0.0) {
}
/* /*
Function: InventoryItem Function: InventoryItem
@@ -45,7 +47,8 @@ InventoryItem::InventoryItem(const std::string& partName, int quantity, double p
m_partName(partName), m_partName(partName),
m_quantity(quantity), m_quantity(quantity),
m_status(util::State::ACTIVE), m_status(util::State::ACTIVE),
m_price(price) {} m_price(price) {
}
/* /*
Function: InventoryItem (parameterized constructor with ID) Function: InventoryItem (parameterized constructor with ID)
@@ -206,73 +209,37 @@ void InventoryItem::setState(util::State status)
/* /*
Function: serialize Function: serialize
Description: Serializes the inventory item into a CSV-formatted string. Description: Serializes the InventoryItem object into a SerializedInventoryItem record.
Parameters: Parameters:
- None - None
Returns: Returns:
- std::string: Serialized inventory item record - SerializedInventoryItem: Serialized representation of the inventory item
*/ */
std::string InventoryItem::serialize() const SerializedInventoryItem InventoryItem::serialize() const
{ {
std::ostringstream serializedInventoryItem; SerializedInventoryItem serialized = {};
serializedInventoryItem << m_id << ',' strcpy_s(serialized.id, sizeof(serialized.id), m_id.c_str());
<< m_partName << ',' strcpy_s(serialized.partName, sizeof(serialized.partName), m_partName.c_str());
<< m_quantity << ',' serialized.quantity = m_quantity;
<< m_price << ',' serialized.price = m_price;
<< util::getStateString(m_status); serialized.status = m_status;
return serializedInventoryItem.str(); return serialized;
} }
/* /*
Function: deserialize Function: deserialize
Description: Deserializes a CSV-formatted string into an InventoryItem object. Description: Deserializes a SerializedInventoryItem record into an InventoryItem object.
Parameters: Parameters:
- record: const std::string&, serialized inventory item record - serializedInventoryItem: const SerializedInventoryItem&, serialized inventory item record
Returns: Returns:
- InventoryItem*: Pointer to the deserialized InventoryItem object - InventoryItem*: Pointer to the deserialized InventoryItem object
Throws:
- std::runtime_error if data is invalid
*/ */
InventoryItem* InventoryItem::deserialize(const std::string& record) InventoryItem* InventoryItem::deserialize(const SerializedInventoryItem& serializedInventoryItem)
{ {
std::string id, partName;
std::string quantityString, priceString, statusString;
int quantity;
double price;
std::istringstream serializedInventoryItem(record);
getline(serializedInventoryItem, id, ',');
getline(serializedInventoryItem, partName, ',');
getline(serializedInventoryItem, quantityString, ',');
getline(serializedInventoryItem, priceString, ',');
getline(serializedInventoryItem, statusString, ',');
try
{
quantity = std::stoi(quantityString);
price = std::stod(priceString);
}
catch (...)
{
throw std::runtime_error("Invalid inventory item data");
}
util::State status = util::getState(statusString);
return Factory::getObject<InventoryItem>( return Factory::getObject<InventoryItem>(
id, serializedInventoryItem.id,
partName, serializedInventoryItem.partName,
quantity, serializedInventoryItem.quantity,
price, serializedInventoryItem.price,
status serializedInventoryItem.status);
);
}
/*
Function: getHeaders
Description: Retrieves the CSV headers for inventory item serialization.
Parameters:
- None
Returns:
- std::string: Header string ("ID,PartName,Quantity,Price,Status")
*/
std::string InventoryItem::getHeaders()
{
return "ID,PartName,Quantity,Price,Status";
} }
@@ -6,11 +6,12 @@ Author: Trenser
Date: 19-May-2026 Date: 19-May-2026
*/ */
#pragma once #pragma once
#include <string> #include <string>
#include "Enums.h" #include "Enums.h"
struct SerializedInventoryItem;
class InventoryItem class InventoryItem
{ {
private: private:
@@ -34,7 +35,6 @@ public:
void setQuantity(int quantity); void setQuantity(int quantity);
void setPrice(double price); void setPrice(double price);
void setState(util::State status); void setState(util::State status);
std::string serialize() const; SerializedInventoryItem serialize() const;
static InventoryItem* deserialize(const std::string&); static InventoryItem* deserialize(const SerializedInventoryItem&);
static std::string getHeaders();
}; };
@@ -57,18 +57,19 @@ Returns:
*/ */
void InventoryManagementService::sendLowStockAlerts() void InventoryManagementService::sendLowStockAlerts()
{ {
auto& inventoryItems = m_dataStore.getInventoryItems(); DataStoreLockGuard lock(m_dataStore);
if (inventoryItems.isEmpty()) auto& trackedInventoryItemsMap = m_dataStore.getInventoryItems();
auto& trackedUserMap = m_dataStore.getUsers();
if (trackedInventoryItemsMap.isEmpty())
{ {
return; return;
} }
int inventoryItemsSize = inventoryItems.getSize(); int inventoryItemsSize = trackedInventoryItemsMap.getSize();
auto& usersMap = m_dataStore.getUsers(); int usersMapSize = trackedUserMap.getSize();
int usersMapSize = usersMap.getSize();
util::Vector<User*> adminUsers; util::Vector<User*> adminUsers;
for (int index = 0; index < usersMapSize; index++) for (int index = 0; index < usersMapSize; index++)
{ {
User* user = usersMap.getValueAt(index); User* user = trackedUserMap.getValueAt(index).data;
if (user->getUserType() == util::UserType::ADMIN) if (user->getUserType() == util::UserType::ADMIN)
{ {
adminUsers.push_back(user); adminUsers.push_back(user);
@@ -81,7 +82,7 @@ void InventoryManagementService::sendLowStockAlerts()
} }
for (int index = 0; index < inventoryItemsSize; index++) for (int index = 0; index < inventoryItemsSize; index++)
{ {
InventoryItem* inventoryItem = inventoryItems.getValueAt(index); InventoryItem* inventoryItem = trackedInventoryItemsMap.getValueAt(index).data;
if (inventoryItem && inventoryItem->getQuantity() < config::threshold::INVENTORY_LOW_STOCK_THRESHOLD) if (inventoryItem && inventoryItem->getQuantity() < config::threshold::INVENTORY_LOW_STOCK_THRESHOLD)
{ {
sendLowStockAlertsToAdmins(*this, inventoryItem, adminUsers); sendLowStockAlertsToAdmins(*this, inventoryItem, adminUsers);
@@ -100,8 +101,11 @@ Return type: void
*/ */
void InventoryManagementService::addInventoryItem(const std::string& partName, int quantity, double price) void InventoryManagementService::addInventoryItem(const std::string& partName, int quantity, double price)
{ {
DataStoreLockGuard lock(m_dataStore);
auto& trackedInventoryItemMap = m_dataStore.getInventoryItems();
InventoryItem* newItem = Factory::getObject<InventoryItem>(partName, quantity, price); InventoryItem* newItem = Factory::getObject<InventoryItem>(partName, quantity, price);
m_dataStore.getInventoryItems().insert(newItem->getId(), newItem); trackedInventoryItemMap.insert(newItem->getId(), util::createNewRecord(newItem));
m_dataStore.saveInventoryItems();
} }
/* /*
@@ -113,16 +117,22 @@ Return type: void
*/ */
void InventoryManagementService::addInventoryItemStock(const std::string& selectedItemId, int quantity) void InventoryManagementService::addInventoryItemStock(const std::string& selectedItemId, int quantity)
{ {
int index = m_dataStore.getInventoryItems().find(selectedItemId); DataStoreLockGuard lock(m_dataStore);
if (index != -1) auto& trackedInventoryItemMap = m_dataStore.getInventoryItems();
{ int index = trackedInventoryItemMap.find(selectedItemId);
InventoryItem* item = m_dataStore.getInventoryItems().getValueAt(index); if (index == -1)
if (item != nullptr) {
{ throw std::runtime_error("Inventory update failed: Item ID '" + selectedItemId + "' not found.");
int totalQuantity = item->getQuantity() + quantity; }
item->setQuantity(totalQuantity); InventoryItem* item = trackedInventoryItemMap.getValueAt(index).data;
} if (item == nullptr)
} {
throw std::runtime_error("Inventory update failed. Item does not exist.\n");
}
int totalQuantity = item->getQuantity() + quantity;
item->setQuantity(totalQuantity);
trackedInventoryItemMap.getValueAt(index).state = RecordState::MODIFIED;
m_dataStore.saveInventoryItems();
} }
/* /*
@@ -133,7 +143,10 @@ Return type: util::Map<std::string, InventoryItem*>
*/ */
util::Map<std::string, InventoryItem*> InventoryManagementService::getInventoryItems() util::Map<std::string, InventoryItem*> InventoryManagementService::getInventoryItems()
{ {
return m_dataStore.getInventoryItems(); DataStoreLockGuard lock(m_dataStore);
auto& trackedInventoryItemMap = m_dataStore.getInventoryItems();
auto inventoryMap = util::getObjects(trackedInventoryItemMap);
return inventoryMap;
} }
/* /*
@@ -144,15 +157,21 @@ Return type: void
*/ */
void InventoryManagementService::removeInventoryItem(const std::string& inventoryItemID) void InventoryManagementService::removeInventoryItem(const std::string& inventoryItemID)
{ {
int index = m_dataStore.getInventoryItems().find(inventoryItemID); DataStoreLockGuard lock(m_dataStore);
if (index != -1) auto& trackedInventoryItemMap = m_dataStore.getInventoryItems();
{ int index = trackedInventoryItemMap.find(inventoryItemID);
InventoryItem* item = m_dataStore.getInventoryItems().getValueAt(index); if (index == -1)
if (item != nullptr) {
{ throw std::runtime_error("Inventory removal failed: Item ID '" + inventoryItemID + "' not found.");
item->setState(util::State::INACTIVE); }
} InventoryItem* item = trackedInventoryItemMap.getValueAt(index).data;
} if (item == nullptr)
{
throw std::runtime_error("Inventory removal failed: Item ID does not exist.");
}
item->setState(util::State::INACTIVE);
trackedInventoryItemMap.getValueAt(index).state = RecordState::MODIFIED;
m_dataStore.saveInventoryItems();
} }
/* /*
@@ -163,12 +182,19 @@ Return type: InventoryItem*
*/ */
InventoryItem* InventoryManagementService::getInventoryItem(const std::string& inventoryItemID) InventoryItem* InventoryManagementService::getInventoryItem(const std::string& inventoryItemID)
{ {
int index = m_dataStore.getInventoryItems().find(inventoryItemID); DataStoreLockGuard lock(m_dataStore);
if (index != -1) auto& trackedInventoryItemMap = m_dataStore.getInventoryItems();
int index = trackedInventoryItemMap.find(inventoryItemID);
if (index == -1)
{ {
return m_dataStore.getInventoryItems().getValueAt(index); return nullptr;
} }
return nullptr; InventoryItem* inventoryItem = trackedInventoryItemMap.getValueAt(index).data;
if (inventoryItem == nullptr)
{
throw std::runtime_error("Item ID does not exist.");
}
return inventoryItem;
} }
/* /*