Compare commits

...

46 Commits

Author SHA1 Message Date
joelthomastrenser 714067f309 Merged PR 1202: Vehicle Service System v1.2.0.0
## New Features

### Shared Memory DataStore

* Replaced the existing in-memory DataStore with a shared-memory implementation using Windows File Mapping.
* Added support for sharing application data between multiple running instances.
* Created separate mappings for users, notifications, services, bookings, invoices, payments, and other entities.

### Binary Data Storage

* Added binary serialization for all major entities.
* Introduced record tracking to handle new, modified, and deleted records.
* Added automatic growth and shrinking of mappings when required.

## Refactoring

### DataStore Changes

* Refactored DataStore to use shared memory instead of local process memory.
* Added generic helper functions for loading and saving records.
* Updated application startup and shutdown to initialize and clean up shared-memory resources.

### Synchronization

* Added a global mutex to synchronize datastore operations across processes.
* Added remapping logic to detect mapping size changes made by other instances.

## Bug Fixes

* Fixed issues with data not being shared correctly between multiple application instances.
* Improved reliability of record updates and deletions.
* Fixed stale mapping issues when mappings were resized by another process.

Related work items: #1925, #1926, #1927, #1928, #1929, #1930, #1949, #1950, #1951, #1952, #1953, #1954, #1955, #1956, #1957, #1958, #1959, #1960, #2061, #2074, #2075, #2076, #2077, #2078, #2079, #2080, #2081, #2082, #2100, #2105, #2114, #2115, #2116
2026-06-19 16:51:22 +05:30
Avinash Rajesh 05b9fc7962 Merged PR 1200: Fix Customer Allowed to Create Service Bookings While Having Unpaid Invoices
Changes:
- Added verifyAllPaymentsCompleted() helper in MenuHelper.h to check if the authenticated customer has completed all invoices.
- Integrated payment verification into CustomerMenu::selectService() to block new service bookings when pending payments exist.
- Integrated payment verification into CustomerMenu::selectComboPackage() to block new combo package bookings when invoices are incomplete.
- Implemented iteration over customer invoices to ensure only users with all payments marked as COMPLETED can proceed with new bookings.
- Added user-facing messages to inform customers when bookings are denied due to outstanding payments.

Related work items: #2116
2026-06-18 19:55:12 +05:30
joelthomastrenser 956ef58c79 Implement review fixes 2026-06-18 19:54:12 +05:30
Jissin Mathew 32f1fa33ce Merged PR 1199: Fix Multiple Notifications Contain Formatting Issues, Vague Messaging, and Duplicate Entries
Changes:

- Updated ServiceManagementService::createJobCard() to prevent duplicate
  "Technician assigned" notifications when multiple job cards exist for
  the same booking.
- Refined job card creation notification to include Job Card ID, Service
  ID, and Booking ID for clearer technician communication.
- Corrected service booking cancellation notification formatting by
  standardizing capitalization and improving message clarity.
- Improved service booking completion notification to explicitly mention
  the booking ID and confirm invoice generation.
- Ensured consistent notification titles and messages across the
  workflow for better user understanding.

Fixes

#2114

Related work items: #2114
2026-06-18 19:44:35 +05:30
Jissin Mathew 684d6d3860 Implement review fixes 2026-06-18 19:43:41 +05:30
joelthomastrenser 976547e7d1 Merged PR 1201: Fix invoice display issues and improve combo package handling
**Changes**:

- Removed inventory quantity from invoice item display.
- Added null checks when displaying combo packages.
- Added null checks when selecting combo packages.
- Replaced unnecessary object copies with references.
- Zero-initialized SerializedObserver.
- Initialized event handle array before use.

Fixes
#2115

Related work items: #2115
2026-06-18 19:37:31 +05:30
joelthomastrenser 4fa143946e Fix combo package null handling and cleanup initialization
- Added null checks in combo package display/selection flows
- Replaced unnecessary copies with references
- Zero-initialized SerializedObserver
- Initialized event handle array
2026-06-18 19:31:18 +05:30
Avinash Rajesh bdb8431773 Customer Allowed to Create Service Bookings While Having Unpaid Invoices
- Added verifyAllPaymentsCompleted() helper in MenuHelper.h to check if the authenticated customer has completed all invoices.

- Integrated payment verification into CustomerMenu::selectService() to block new service bookings when pending payments exist.

- Integrated payment verification into CustomerMenu::selectComboPackage() to block new combo package bookings when invoices are incomplete.

- Implemented iteration over customer invoices to ensure only users with all payments marked as COMPLETED can proceed with new bookings.

- Added user-facing messages to inform customers when bookings are denied due to outstanding payments.

Fixes #2116
2026-06-18 19:26:45 +05:30
Jissin Mathew 2eaa719aca Fix Multiple Notifications Contain Formatting Issues, Vague Messaging, and Duplicate Entries
Changes:

- Updated ServiceManagementService::createJobCard() to prevent duplicate
  "Technician assigned" notifications when multiple job cards exist for
  the same booking.
- Refined job card creation notification to include Job Card ID, Service
  ID, and Booking ID for clearer technician communication.
- Corrected service booking cancellation notification formatting by
  standardizing capitalization and improving message clarity.
- Improved service booking completion notification to explicitly mention
  the booking ID and confirm invoice generation.
- Ensured consistent notification titles and messages across the
  workflow for better user understanding.

Fixes #2114
2026-06-18 19:21:59 +05:30
joelthomastrenser 931913fa30 Fix: remove inventory quantity from invoice display
- Removed Quantity column from invoice item listing
- Removed inventory stock quantity values from invoice output
- Display only item name and price in generated invoices

Fixes #2115
2026-06-18 19:20:02 +05:30
Jissin Mathew b983337630 Merged PR 1196: Fix Service Booking Cancellation, Job Completion Exception, and Inventory Quantity Handling
Changes

- Added Controller::removeServiceBooking() with documentation and delegation to ServiceManagementService.

- Implemented ServiceManagementService::removeServiceBooking() to handle cancellation of pending bookings, enforce status validation, send notifications, and persist changes safely.

- Updated CustomerMenu to include a "Cancel Service Booking" option, wired into handleOperation, and implemented CustomerMenu::cancelServiceBooking() for user interaction.

- Ensured consistent declarations in Controller.h, ServiceManagementService.h, and CustomerMenu.h.

- Refactored ServiceManagementService::updateJobStatus() to use tracked job card references instead of raw pointers, ensuring consistent state updates.

- Added proper null checks and error handling for current job retrieval to prevent unexpected termination.

- Updated logic to mark tracked job records as MODIFIED when status transitions occur (`STARTED` → `IN_PROGRESS`, `IN_PROGRESS` → `COMPLETED`).

- Simplified control flow and indentation for better readability and maintainability.

- Added retrieval of tracked service bookings and inventory items in ServiceManagementService::createJobCard().

- Validated service booking ID and inventory item indices before proceeding with job card creation.

- Decremented inventory item quantities when job cards are created and marked corresponding tracked inventory records as `MODIFIED`.

- Updated tracked service booking state to `MODIFIED` when technician is assigned and job status changes.

- Persisted changes by saving job cards, service bookings, and inventory items to the datastore.

Fixes

#2105
#2076
#2075
#2074
2026-06-18 11:36:43 +05:30
joelthomastrenser 5155192f6d Merge branch 'develop' into develop-sm-bugfix-2 2026-06-18 11:36:25 +05:30
Jissin Mathew ca0e277586 Implement Review fixes 2026-06-18 11:33:10 +05:30
Avinash Rajesh 69b7c31100 Merged PR 1195: Fix notification display and service dependency handling issues
**Changes:**
- Persist service bookings immediately after purchase to ensure data consistency.
- Enhanced notification display by appending authenticated user names to notification titles.
- Updated account disabled event handling to include authenticated user names in warning messages.
- Modified notification event handling in Admin, Customer, and Technician menus to pass authenticated user names.
- Improved service selection logic to skip services with depleted inventory items.
- Added dependency cleanup: services requiring a removed inventory item are automatically removed.
- Updated displayNewNotification to extract numeric IDs correctly and append authenticated user names to notification titles.
- Included StringHelper for improved string operations in notification handling.

#2080
#2081
#2082

Related work items: #2080, #2081, #2082
2026-06-18 11:17:11 +05:30
joelthomastrenser 9fa58b030e Merge branch 'develop' into develop-sm-bugfix-3 2026-06-18 11:17:01 +05:30
joelthomastrenser ac552d669a Merged PR 1194: Fix shared memory notification and authorization handling issues
Changes:
- Fixed DataStore cache refresh from overwriting records with pending local modifications.
- Fixed notification ID collisions by refreshing the notification cache before creating new notifications.
- Added authorization state tracking to authenticated sessions.
- Revoked authorization immediately when account-disabled events are received.
- Added authorization validation for protected service operations.
- Cleared authorization state during logout.
- Added admin notifications for newly created service bookings.
- Added admin notifications for newly created combo package bookings.
- Included Service Booking IDs in admin notification messages.

#2077 #2078 #2078 #2100

Related work items: #2077, #2078, #2079, #2100
2026-06-18 11:05:57 +05:30
Jissin Mathew b45463a66d Fix Cancel Service Booking Functionality
Changes:

- Added Controller::removeServiceBooking() with proper documentation and
  delegation to ServiceManagementService.
- Implemented ServiceManagementService::removeServiceBooking() to handle
  cancellation of pending bookings, enforce status validation, send
  notifications, and persist changes safely.
- Updated CustomerMenu to include a "Cancel Service Booking" option in
  the menu, wired it into handleOperation, and implemented
  CustomerMenu::cancelServiceBooking() for user interaction.
- Ensured consistent declarations in Controller.h,
  ServiceManagementService.h, and CustomerMenu.h.

Fixes #2105
2026-06-18 10:14:51 +05:30
Avinash Rajesh 1651dfeafe Service Can Still Be Purchased After Required Inventory Item Is Removed
Changes:

- Added m_dataStore.saveServiceBookings() in ServiceManagementService::purchaseService() to persist bookings immediately after successful purchase.

- Enhanced AdminMenu::removeInventoryItem() to also remove all services that depend on the removed inventory item, ensuring consistency between inventory and service availability.

- Updated selectServiceFromServices() in MenuHelper.h to skip services if any of their required inventory items have a quantity less than 1, preventing users from selecting unavailable services.

- Introduced inventory depletion checks before inserting services into the active services map, improving reliability of service selection.

Fixes  #2082
2026-06-17 18:10:12 +05:30
joelthomastrenser 468106952f Notify admins when new service bookings are created
Changes:
- Added notifyAllAdmins() helper in ServiceManagementService.
- Sent notifications to all administrators when a customer places a service booking.
- Sent notifications to all administrators when a customer purchases a combo package.
- Included the generated Service Booking ID in admin notifications.

Fixes #2100
2026-06-17 17:39:45 +05:30
Jissin Mathew bb0d186b62 Fix Completing a Job Throws an Exception
Changes:

- Refactored ServiceManagementService::updateJobStatus() to use tracked
  job card references instead of raw pointers, ensuring consistent state
  updates.
- Added proper null checks and error handling for current job retrieval
  to prevent unexpected termination.
- Updated logic to mark tracked job records as MODIFIED when status
  transitions occur (STARTED → IN_PROGRESS, IN_PROGRESS → COMPLETED).
- Simplified control flow and indentation for better readability and
  maintainability.

Fixes #2076
2026-06-17 17:01:34 +05:30
joelthomastrenser 82164e42c3 Stop disabled users from performing operations after account deactivation
Changes:
- Added authorization state tracking to authenticated sessions.
- Revoked authorization immediately when an account-disabled event is received.
- Added authorization validation before protected service operations.
- Cleared authorization state on logout.

Fixes #2078
2026-06-17 16:47:15 +05:30
Jissin Mathew 4243f4e43f Fix Inventory Quantity Is Not Decremented When Job Card Is Created
Changes:

- Added retrieval of tracked service bookings and inventory items in
  ServiceManagementService::createJobCard().
- Validated service booking ID and inventory item indices before
  proceeding with job card creation.
- Decremented inventory item quantities when job cards are created and
  marked corresponding tracked inventory records as MODIFIED.
- Updated tracked service booking state to MODIFIED when technician is
  assigned and job status changes.
- Persisted changes by saving job cards, service bookings, and inventory
  items to the datastore.

Fixes #2075
2026-06-17 15:29:46 +05:30
Avinash Rajesh 9d166362a7 Latest Notification Is Determined Using String Comparison
Changes:

- Added #include "StringHelper.h" to MenuHelper.h for utility functions.

- Updated displayNewNotification() to compare notification IDs using util::extractNumber() instead of raw string comparison.

- Ensured numeric ordering of notifications by extracting and comparing integer values from IDs.

- Preserved existing notification selection logic while improving accuracy of ID comparisons.

Fixes #2081
2026-06-17 15:17:55 +05:30
Jissin Mathew 974d4efe02 Fix Service Booking Creation Is Not Synchronized Between Processes
Changes:

- Updated ServiceManagementService::purchaseService() to persist service
  bookings immediately after creation using m_dataStore.saveServiceBookings().
- Updated ServiceManagementService::purchaseComboPackage() to persist
  combo package bookings immediately after creation using
  m_dataStore.saveServiceBookings().
- Changed DataStore::getJobCards() to use a reference for tracked bookings
  to ensure consistent linkage and prevent overwriting.

Fixes #2074
2026-06-17 14:47:37 +05:30
Avinash Rajesh 1179f92849 Notification Dialog Does Not Indicate Intended Recipient
Changes:

- Updated AdminMenu::handleNotificationEvent, CustomerMenu::handleNotificationEvent, and TechnicianMenu::handleNotificationEvent to retrieve the authenticated user and pass their name to displayNewNotification().

- Modified Menu::handleAccountDisabledEvent to append the authenticated user’s name to the message box title when available.

- Refactored displayNewNotification() in MenuHelper.h to accept the authenticated user’s name as an additional parameter.

- Enhanced notification display logic to append the user’s name to the notification title when present.

- Added null checks to ensure safe handling when no authenticated user exists.

FIxes #2080
2026-06-17 14:20:20 +05:30
joelthomastrenser b011f600cf Fix notification overwrite caused by stale notification cache
Changes:
- Retrieved the notifications map before creating a new notification in sendNotification().
- Ensured the notification cache is refreshed before generating a notification ID.
- Prevented newly created notifications from overwriting notifications added by other operations prior to save.

Fixes #2079
2026-06-17 14:07:38 +05:30
joelthomastrenser 78e6ae2fec Fix cache refresh from overwriting modified records
Changes:
- Added a check for RecordState::MODIFIED in DataStore::refreshCache().
- Preserved modified cache records during refresh instead of overwriting them with datastore values.
- Discarded refreshed records when a corresponding cached record has pending changes.

Fixes #2077
2026-06-17 11:30:15 +05:30
joelthomastrenser 528075e0cd Merged PR 1178: Implement interprocess event handling for notifications and account disable
Implement interprocess event handling for notifications and account disable

Changes:
- Implements #2061
- Introduce EventManager for user-specific Windows event publishing/listening
- Add real-time notification and account-disabled event propagation
- Register authentication events through Controller and AuthenticationManagementService
- Trigger notification events from Inventory, Payment, and Service Management modules
- Trigger account-disabled events when users are deactivated
- Extract common menu event listener logic into Menu base class
- Add notification popup handling for Admin, Customer, and Technician menus
- Refactor shared memory components into core/sharedmemory
- Update project structure and include paths for events and shared memory modules

Related work items: #1928, #2061
2026-06-16 11:29:17 +05:30
joelthomastrenser ae37b83cbc Implement review fixes 2026-06-16 11:29:00 +05:30
joelthomastrenser 8aaa4eeec0 Implement interprocess event handling for notifications and account disable
Changes:
- Implements #2061
- Introduce EventManager for user-specific Windows event publishing/listening
- Add real-time notification and account-disabled event propagation
- Register authentication events through Controller and AuthenticationManagementService
- Trigger notification events from Inventory, Payment, and Service Management modules
- Trigger account-disabled events when users are deactivated
- Extract common menu event listener logic into Menu base class
- Add notification popup handling for Admin, Customer, and Technician menus
- Refactor shared memory components into core/sharedmemory
- Update project structure and include paths for events and shared memory modules
2026-06-16 11:10:13 +05:30
Jissin Mathew f3e42a8b17 Merged PR 1172: Payment Management Refactoring - 1930
User Story #1949
User Story #1950

**Changes**

* Added `DataStoreLockGuard` header to project configuration and implemented the class for scoped mutex management.
* Refactored `Invoice` model to replace CSV-based serialization with `SerializedInvoice` struct, removing legacy `getHeaders()` and string parsing logic.
* Updated `DataStore::getInvoices()` to load records via `loadRecords`, refresh cache, and automatically enrich `Invoice` objects with linked `ServiceBooking` and `InventoryItem` entities.
* Implemented `DataStore::saveInvoices()` using the `saveRecords<Invoice, SerializedInvoice>` template for direct shared memory persistence.
* Integrated `DataStoreLockGuard` across all critical methods in `PaymentManagementService`: `sendPaymentReminders`, `generateInvoice`, `getInvoices`, `completePayment`, `getAllInvoices`, and `confirmPayment`.
* Refactored invoice creation and modification flows to use `createNewRecord` for insertion and explicitly set `RecordState::MODIFIED` before triggering `saveInvoices()`.
* Updated data access patterns to extract `.data` pointers from `TrackedRecord` wrappers instead of accessing raw map values directly.
* Added validation logic in `getInvoices()` to throw `runtime_error` if referenced ServiceBookings or InventoryItems are missing.
* Added necessary header dependencies (`Invoice.h`, `DataStoreLockGuard.h`) in service and store layers.

Related work items: #1930, #1949, #1950
2026-06-15 15:27:59 +05:30
joelthomastrenser d8ef54ddd0 Merge branch 'develop' into develop-sm-payment-management 2026-06-15 15:27:45 +05:30
Avinash Rajesh 5fb92fba0e Merged PR 1170: Inventory Management Refactoring - 1926
User Story #1957
User Story #1958
**Changes**
- Added DataStoreLockGuard for scoped datastore locking/unlocking.
- Refactored InventoryItem serialization from CSV strings to SerializedInventoryItem struct.
- Removed old serialize, deserialize, and getHeaders methods from InventoryItem.
- Updated DataStore to load, refresh, and save inventory items using typed records.
- Applied lock guard in InventoryManagementService for stock alerts, add/remove/update operations.
- Enhanced error handling for invalid item IDs and missing inventory records.
- Simplified getInventoryItems to return object maps from tracked records.
- Removed redundant persistence methods (loadInventoryItems, saveInventoryItems) from service layer.

Related work items: #1926, #1957, #1958
2026-06-15 15:26:00 +05:30
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
joelthomastrenser 69cda81a50 Merge branch 'develop' into develop-sm-service-management 2026-06-15 15:24:01 +05:30
Avinash Rajesh 9fb9be1b45 Merged PR 1169: Authentication Management Refactoring - 1929
User Story #1951
User Story #1952
**Changes**
- Added DataStoreLockGuard for scoped locking/unlocking.
- Applied lock guard in AuthenticationManagementService (login, password change).
- Switched User serialization from CSV to SerializedUser struct.
- Removed old CSV-based serialize, deserialize, and getHeaders.
- Updated DataStore::getUsers to refresh cache and attach notifications.
- Enhanced DataStore::saveUsers to persist users and notifications.
- Marked modified records in changePassword and saved changes.
- Included DataStoreLockGuard.h in project files.
- Improved error handling for invalid user IDs and missing users.

Related work items: #1929, #1951, #1952
2026-06-15 15:22:02 +05:30
Jissin Mathew d2a7420db5 Implement Review Fixes 2026-06-15 15:14:16 +05:30
Jissin Mathew e1676568cd Implement review fixes 2026-06-15 14:52:19 +05:30
Jissin Mathew 7047e96b3c Implement Service Refactoring
<UserStory> 1950: Implement Service Refactorings </UserStory>

UserStory #1950

<Changes>

1. Added `DataStoreLockGuard` integration in `PaymentManagementService` methods (`sendPaymentReminders`, `generateInvoice`, `getInvoices`, `completePayment`, `getAllInvoices`, `confirmPayment`) to ensure thread-safe access to the datastore.

2. Implemented record enrichment logic in `DataStore::getInvoices()` to automatically link `Invoice` objects with their corresponding `ServiceBooking` and `InventoryItem` entities during loading, validating relationships and throwing exceptions for invalid references.

3. Refactored invoice persistence by implementing `saveInvoices()` using `saveRecords<Invoice, SerializedInvoice>` to write tracked records directly to shared memory.

4. Updated invoice creation and modification flows:
   - `generateInvoice`: Uses `createNewRecord` to insert new invoices into the tracked cache and triggers immediate persistence.
   - `completePayment` & `confirmPayment`: Marks records as `MODIFIED` in the `TrackedRecord` state before saving changes.

5. Updated data access patterns across `PaymentManagementService` to use `.data` pointers from `TrackedRecord` objects returned by the datastore, replacing direct pointer access to mapped objects.

6. Added necessary header dependencies including `DataStoreLockGuard.h`, `Invoice.h`, and `SerializedRecords.h` to support locking mechanisms and structured serialization.

</Changes>

<Test>

N/A

</Test>

<Review>

Sreeja Reghukumar, please review

</Review>
2026-06-15 12:43:44 +05:30
Jissin Mathew f545d57f79 Implement Model Refactoring
<UserStory> 1949: Model Refactoring</UserStory>

UserStory #1949

<Changes>
Replaced CSV-based serialization and deserialization in the Invoice model with fixed-size SerializedRecord structures for shared memory storage.
Implemented serialize() method to convert Invoice objects into SerializedInvoice records using direct struct field assignment and strcpy_s for string fields.
Implemented deserialize() method to reconstruct Invoice objects directly from SerializedInvoice types instead of parsing CSV strings.
Updated Invoice class interfaces to use SerializedInvoice types, removing legacy CSV serialization APIs including getHeaders() function.
Added SerializedRecords.h dependency and forward declarations for SerializedInvoice structure in Invoice header.
Minor formatting adjustments in DataStore.cpp (closing brace placement).
</Changes>
<Test>
N/A

</Test>
<Review>
Sreeja Reghukumar, please review

</Review>
2026-06-15 11:09:42 +05:30
Jissin Mathew f3220ee191 Implement Service Refactoring
<UserStory> 1927: Implement Service Refactorings </UserStory>

UserStory #1927

<Changes>
1. Added DataStoreLockGuard integration across ServiceManagementService methods for thread-safe datastore operations.
2. Updated DataStore::getServices(), getComboPackages(), getServiceBookings(), and getJobCards() to enrich records with linked entities (inventory items, services, bookings, technicians).
3. Modified ServiceManagementService::purchaseService() and purchaseComboPackage() to use tracked records and persist bookings safely.
4. Enhanced restoreInventory() and processBookingCancellation() to handle tracked records, update record states, and restore inventory items correctly.
5. Refactored cancelCustomerServiceBookings() and cancelTechnicianJobs() to use tracked records, restore inventory, and persist changes.
6. Updated createComboPackage(), removeComboPackage(), and createJobCard() to use tracked records and save changes to datastore.
7. Updated createService() to validate inventory items with tracked records and persist new services safely.
8. Added required header dependencies for DataStoreLockGuard and shared memory support.
</Changes>

<Test>
N/A
</Test>

<Review>
Sreeja Reghukumar, please review
</Review>
2026-06-15 10:47:06 +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
Jissin Mathew 4b76cae358 Implement Model Refactoring
<UserStory> 1955: Model Refactoring</UserStory>

UserStory #1955

<Changes>
1. Replaced CSV-based serialization and deserialization in ComboPackage, JobCard, Service, and ServiceBooking models with fixed-size SerializedRecord structures for shared memory storage.
2. Implemented serialize() methods to convert objects into SerializedComboPackage, SerializedJobCard, SerializedService, and SerializedServiceBooking records.
3. Implemented deserialize() methods to reconstruct objects directly from SerializedRecord types instead of parsing CSV strings.
4. Updated model class interfaces to use SerializedRecord types, removing legacy CSV serialization APIs and header generation functions.
5. Added SerializedRecords.h dependencies and forward declarations for Serialized structures across affected models.
</Changes>

<Test>
N/A
</Test>

<Review>
Sreeja Reghukumar, please review
</Review>
2026-06-12 03:22:13 +05:30
joelthomastrenser 8a3ec278ce Merged PR 1159: Vehicle Service System v1.1.0.0
**New Features**

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

- Introduced a new PAID payment status.

- Implemented invoice confirmation flow from PAID → COMPLETED.

- Added invoice filtering based on payment status.

- Added admin menu option to confirm customer payments.

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

- Added support for job status transitions:

- STARTED → IN_PROGRESS

- IN_PROGRESS → COMPLETED

- Added invoice generation and customer notification upon booking completion.

- Improved job visibility by displaying current job status.

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

- Standardized status labels using "In Progress".

- Improved prompts, headings, and job selection messages.

- Enhanced readability of job listings.

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

- Prevented creation of duplicate usernames across all user states.

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

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

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

Related work items: #1797, #1798, #1807, #1808, #1809
2026-06-01 18:57:46 +05:30
44 changed files with 1758 additions and 673 deletions
@@ -102,7 +102,7 @@
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<AdditionalIncludeDirectories>$(ProjectDir)models;$(ProjectDir)controllers;$(ProjectDir)factories;$(ProjectDir)views;$(ProjectDir)services;$(ProjectDir)utilities;$(ProjectDir)core\patterns;$(ProjectDir)datastores;$(ProjectDir)datastores\sharedmemory;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(ProjectDir)models;$(ProjectDir)controllers;$(ProjectDir)factories;$(ProjectDir)views;$(ProjectDir)services;$(ProjectDir)utilities;$(ProjectDir)core\patterns;$(ProjectDir)datastores;$(ProjectDir)core\sharedmemory;$(ProjectDir)core\events;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
@@ -117,7 +117,7 @@
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<AdditionalIncludeDirectories>$(ProjectDir)models;$(ProjectDir)controllers;$(ProjectDir)factories;$(ProjectDir)views;$(ProjectDir)services;$(ProjectDir)utilities;$(ProjectDir)core\patterns;$(ProjectDir)datastores;$(ProjectDir)datastores\sharedmemory;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(ProjectDir)models;$(ProjectDir)controllers;$(ProjectDir)factories;$(ProjectDir)views;$(ProjectDir)services;$(ProjectDir)utilities;$(ProjectDir)core\patterns;$(ProjectDir)datastores;$(ProjectDir)core\sharedmemory;$(ProjectDir)core\events;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
@@ -126,10 +126,11 @@
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="controllers\Controller.cpp" />
<ClCompile Include="core\events\EventManager.cpp" />
<ClCompile Include="core\patterns\Observer.cpp" />
<ClCompile Include="core\patterns\Subject.cpp" />
<ClCompile Include="core\sharedmemory\SharedMemory.cpp" />
<ClCompile Include="datastores\DataStore.cpp" />
<ClCompile Include="datastores\sharedmemory\SharedMemory.cpp" />
<ClCompile Include="models\ComboPackage.cpp" />
<ClCompile Include="models\InventoryItem.cpp" />
<ClCompile Include="models\Invoice.cpp" />
@@ -148,21 +149,23 @@
<ClCompile Include="utilities\Validator.cpp" />
<ClCompile Include="views\AdminMenu.cpp" />
<ClCompile Include="views\CustomerMenu.cpp" />
<ClCompile Include="views\Menu.cpp" />
<ClCompile Include="views\TechnicianMenu.cpp" />
<ClCompile Include="views\UserInterface.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="controllers\Controller.h" />
<ClInclude Include="core\events\EventManager.h" />
<ClInclude Include="core\patterns\Observer.h" />
<ClInclude Include="core\patterns\Subject.h" />
<ClInclude Include="core\sharedmemory\FileHeader.h" />
<ClInclude Include="core\sharedmemory\MappingInfo.h" />
<ClInclude Include="core\sharedmemory\RecordState.h" />
<ClInclude Include="core\sharedmemory\SerializedRecords.h" />
<ClInclude Include="core\sharedmemory\SharedMemory.h" />
<ClInclude Include="core\sharedmemory\TrackedRecord.h" />
<ClInclude Include="datastores\DataStore.h" />
<ClInclude Include="datastores\DataStoreLockGuard.h" />
<ClInclude Include="datastores\sharedmemory\FileHeader.h" />
<ClInclude Include="datastores\sharedmemory\MappingInfo.h" />
<ClInclude Include="datastores\sharedmemory\RecordState.h" />
<ClInclude Include="datastores\sharedmemory\SerializedRecords.h" />
<ClInclude Include="datastores\sharedmemory\SharedMemory.h" />
<ClInclude Include="datastores\sharedmemory\TrackedRecord.h" />
<ClInclude Include="factories\Factory.h" />
<ClInclude Include="models\ComboPackage.h" />
<ClInclude Include="models\InventoryItem.h" />
@@ -191,6 +194,7 @@
<ClInclude Include="utilities\Vector.h" />
<ClInclude Include="views\AdminMenu.h" />
<ClInclude Include="views\CustomerMenu.h" />
<ClInclude Include="views\Menu.h" />
<ClInclude Include="views\MenuHelper.h" />
<ClInclude Include="views\TechnicianMenu.h" />
<ClInclude Include="views\UserInterface.h" />
@@ -64,11 +64,17 @@
<Filter Include="Source Files\Core\Patterns">
<UniqueIdentifier>{8057b93d-51a9-42df-b06e-01ce395f6308}</UniqueIdentifier>
</Filter>
<Filter Include="Header Files\DataStores\SharedMemory">
<UniqueIdentifier>{ec639004-44c6-4bd6-9963-077adde82b5f}</UniqueIdentifier>
<Filter Include="Header Files\Core\SharedMemory">
<UniqueIdentifier>{d9da9793-fe6f-4914-bee3-99d5934da228}</UniqueIdentifier>
</Filter>
<Filter Include="Source Files\DataStores\SharedMemory">
<UniqueIdentifier>{7aa8722e-adfa-466e-8211-de63f3b7892b}</UniqueIdentifier>
<Filter Include="Source Files\Core\SharedMemory">
<UniqueIdentifier>{0769afb6-f57d-4ae3-a1cf-ceca6e606af0}</UniqueIdentifier>
</Filter>
<Filter Include="Header Files\Core\Events">
<UniqueIdentifier>{85029bdb-6941-41dc-a3a7-9e5841671d8c}</UniqueIdentifier>
</Filter>
<Filter Include="Source Files\Core\Events">
<UniqueIdentifier>{1050aca7-6f2c-4ccb-a446-db9c898c3599}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
@@ -144,8 +150,14 @@
<ClCompile Include="models\ComboPackage.cpp">
<Filter>Source Files\Models</Filter>
</ClCompile>
<ClCompile Include="datastores\sharedmemory\SharedMemory.cpp">
<Filter>Source Files\DataStores\SharedMemory</Filter>
<ClCompile Include="core\sharedmemory\SharedMemory.cpp">
<Filter>Source Files\Core\SharedMemory</Filter>
</ClCompile>
<ClCompile Include="core\events\EventManager.cpp">
<Filter>Source Files\Core\Events</Filter>
</ClCompile>
<ClCompile Include="views\Menu.cpp">
<Filter>Source Files\Views</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
@@ -254,26 +266,32 @@
<ClInclude Include="views\MenuHelper.h">
<Filter>Header Files\Views</Filter>
</ClInclude>
<ClInclude Include="datastores\sharedmemory\FileHeader.h">
<Filter>Header Files\DataStores\SharedMemory</Filter>
</ClInclude>
<ClInclude Include="datastores\sharedmemory\MappingInfo.h">
<Filter>Header Files\DataStores\SharedMemory</Filter>
</ClInclude>
<ClInclude Include="datastores\sharedmemory\RecordState.h">
<Filter>Header Files\DataStores\SharedMemory</Filter>
</ClInclude>
<ClInclude Include="datastores\sharedmemory\TrackedRecord.h">
<Filter>Header Files\DataStores\SharedMemory</Filter>
</ClInclude>
<ClInclude Include="datastores\sharedmemory\SerializedRecords.h">
<Filter>Header Files\DataStores\SharedMemory</Filter>
</ClInclude>
<ClInclude Include="datastores\sharedmemory\SharedMemory.h">
<Filter>Header Files\DataStores\SharedMemory</Filter>
</ClInclude>
<ClInclude Include="datastores\DataStoreLockGuard.h">
<Filter>Header Files\DataStores</Filter>
</ClInclude>
<ClInclude Include="core\sharedmemory\FileHeader.h">
<Filter>Header Files\Core\SharedMemory</Filter>
</ClInclude>
<ClInclude Include="core\sharedmemory\MappingInfo.h">
<Filter>Header Files\Core\SharedMemory</Filter>
</ClInclude>
<ClInclude Include="core\sharedmemory\RecordState.h">
<Filter>Header Files\Core\SharedMemory</Filter>
</ClInclude>
<ClInclude Include="core\sharedmemory\SerializedRecords.h">
<Filter>Header Files\Core\SharedMemory</Filter>
</ClInclude>
<ClInclude Include="core\sharedmemory\SharedMemory.h">
<Filter>Header Files\Core\SharedMemory</Filter>
</ClInclude>
<ClInclude Include="core\sharedmemory\TrackedRecord.h">
<Filter>Header Files\Core\SharedMemory</Filter>
</ClInclude>
<ClInclude Include="core\events\EventManager.h">
<Filter>Header Files\Core\Events</Filter>
</ClInclude>
<ClInclude Include="views\Menu.h">
<Filter>Header Files\Views</Filter>
</ClInclude>
</ItemGroup>
</Project>
@@ -234,6 +234,17 @@ void Controller::removeInventoryItem(const std::string& inventoryItemID)
m_inventoryManagementService.removeInventoryItem(inventoryItemID);
}
/*
Function: removeServiceBooking
Description: Removes a service booking from the service management system by its booking ID.
Parameter: const std::string& bookingID - ID of the service booking
Return type: void
*/
void Controller::removeServiceBooking(const std::string& bookingID)
{
m_serviceManagementService.removeServiceBooking(bookingID);
}
/*
Function: addInventoryItemStock
Description: Adds stock to an existing inventory item in the inventory management service.
@@ -265,6 +276,7 @@ util::Map<std::string, const ServiceBooking*> Controller::getServiceBookings()
return readOnlyServiceBookings;
}
/*
Function: getServiceBookingsByUser
Description: Retrieves all service bookings for a specific user.
@@ -622,3 +634,16 @@ void Controller::shutdown()
auto& dataStore = DataStore::getInstance();
dataStore.shutdown();
}
/*
Function: registerEvents
Description: Registers menu event handles with the authentication
service.
Parameter: HANDLE accountDisabledEvent - account disabled event handle
HANDLE notificationAvailableEvent - notification event handle
Return type: void
*/
void Controller::registerEvents(HANDLE accountDisabledEvent, HANDLE notificationAvailableEvent)
{
m_authenticationManagementService.registerEvents(accountDisabledEvent, notificationAvailableEvent);
}
@@ -8,6 +8,7 @@ Date:19-May-2026
*/
#pragma once
#include <windows.h>
#include <string>
#include "AuthenticationManagementService.h"
#include "Enums.h"
@@ -57,6 +58,7 @@ public:
util::Map<std::string, const User*> getUsers(util::UserType userType);
void createJobCard(const std::string& bookingID, const std::string& technicianID, const std::string& serviceID);
void createService(const std::string& name, const util::Vector<std::string>& inventoryItemIDs, double laborCost);
void removeServiceBooking(const std::string& bookingID);
void removeService(const std::string& serviceID);
util::Map<std::string, const JobCard*> getJobCardsByUser();
void updateJobStatus(const std::string& jobID);
@@ -72,4 +74,5 @@ public:
void configureNotifications(bool paymentNotifications, bool serviceNotifications);
bool initialize();
void shutdown();
void registerEvents(HANDLE accountDisabledEvent, HANDLE notificationAvailableEvent);
};
@@ -0,0 +1,225 @@
/*
File: EventManager.cpp
Description: Implementation file containing the method definitions of the
EventManager class, including listener management and
interprocess event publishing.
Author: Trenser
Date:15-Jun-2026
*/
#include <iostream>
#include <stdexcept>
#include "EventManager.h"
namespace
{
const std::string USER_DISABLED_EVENT = "userDisabled_";
const std::string NOTIFICATION_AVAILABLE_EVENT = "notificationAvailable_";
}
/*
Function: EventManager
Description: Constructs an EventManager instance with default values.
Parameter: None
Return type: None
*/
EventManager::EventManager()
:
m_userDisabledEvent(NULL),
m_notificationAvailableEvent(NULL),
m_shutdownEvent(NULL),
m_running(false) {}
/*
Function: ~EventManager
Description: Destroys the EventManager and performs final cleanup.
Parameter: None
Return type: None
*/
EventManager::~EventManager()
{
shutdown();
if (m_listenerThread.joinable())
{
m_listenerThread.join();
}
}
/*
Function: initialize
Description: Creates the user-specific events and starts the listener
thread.
Parameter: const std::string& userId - unique identifier of the user
std::function<void()> userDisabledCallback - callback for
user disable events
std::function<void()> notificationCallback - callback for
notification events
Return type: bool - true if initialization succeeds, false otherwise
*/
bool EventManager::initialize(const std::string& userId, std::function<void()> userDisabledCallback, std::function<void()> notificationCallback)
{
if (m_running.load())
{
return false;
}
m_userDisabledCallback = userDisabledCallback;
m_notificationCallback = notificationCallback;
m_userDisabledEvent = CreateEventA(NULL, FALSE, FALSE, (USER_DISABLED_EVENT + userId).c_str());
if (!m_userDisabledEvent)
{
return false;
}
if (GetLastError() == ERROR_ALREADY_EXISTS)
{
CloseHandle(m_userDisabledEvent);
m_userDisabledEvent = NULL;
throw std::runtime_error("Only one session allowed per user.");
}
m_notificationAvailableEvent = CreateEventA(NULL, FALSE, FALSE, (NOTIFICATION_AVAILABLE_EVENT + userId).c_str());
if (!m_notificationAvailableEvent)
{
CloseHandle(m_userDisabledEvent);
m_userDisabledEvent = NULL;
return false;
}
m_shutdownEvent = CreateEventA(NULL, FALSE, FALSE, NULL);
if (!m_shutdownEvent)
{
CloseHandle(m_userDisabledEvent);
CloseHandle(m_notificationAvailableEvent);
m_userDisabledEvent = NULL;
m_notificationAvailableEvent = NULL;
return false;
}
m_running.store(true);
m_listenerThread = std::thread(&EventManager::run, this);
return true;
}
/*
Function: shutdown
Description: Stops the listener thread and releases event resources.
Parameter: None
Return type: None
*/
void EventManager::shutdown()
{
if (!m_running.load())
{
return;
}
m_running.store(false);
if (m_shutdownEvent)
{
SetEvent(m_shutdownEvent);
}
if (m_listenerThread.joinable())
{
if (std::this_thread::get_id() != m_listenerThread.get_id())
{
m_listenerThread.join();
}
}
if (m_userDisabledEvent)
{
CloseHandle(m_userDisabledEvent);
m_userDisabledEvent = NULL;
}
if (m_notificationAvailableEvent)
{
CloseHandle(m_notificationAvailableEvent);
m_notificationAvailableEvent = NULL;
}
if (m_shutdownEvent)
{
CloseHandle(m_shutdownEvent);
m_shutdownEvent = NULL;
}
}
/*
Function: run
Description: Waits for and dispatches user-related events.
Parameter: None
Return type: void
*/
void EventManager::run()
{
HANDLE handles[3];
handles[0] = m_userDisabledEvent;
handles[1] = m_notificationAvailableEvent;
handles[2] = m_shutdownEvent;
while (m_running.load())
{
DWORD result = WaitForMultipleObjects(3, handles, FALSE, INFINITE);
switch (result)
{
case WAIT_OBJECT_0:
try
{
if (m_userDisabledCallback)
{
m_userDisabledCallback();
}
}
catch (const std::exception& exception)
{
std::cout << exception.what() << std::endl;
}
break;
case WAIT_OBJECT_0 + 1:
try
{
if (m_notificationCallback)
{
m_notificationCallback();
}
}
catch (const std::exception& exception)
{
std::cout << exception.what() << std::endl;
}
break;
case WAIT_OBJECT_0 + 2:
return;
default:
break;
}
}
}
/*
Function: sendUserDisabledEvent
Description: Publishes a user disabled event for a specific user.
Parameter: const std::string& userId - target user identifier
Return type: void
*/
void EventManager::sendUserDisabledEvent(const std::string& userId)
{
HANDLE eventHandle = CreateEventA(NULL, FALSE, FALSE, (USER_DISABLED_EVENT + userId).c_str());
if (!eventHandle)
{
return;
}
SetEvent(eventHandle);
CloseHandle(eventHandle);
}
/*
Function: sendNotificationAvailableEvent
Description: Publishes a notification available event for a specific
user.
Parameter: const std::string& userId - target user identifier
Return type: void
*/
void EventManager::sendNotificationAvailableEvent(const std::string& userId)
{
HANDLE eventHandle = CreateEventA(NULL, FALSE, FALSE, (NOTIFICATION_AVAILABLE_EVENT + userId).c_str());
if (!eventHandle)
{
return;
}
SetEvent(eventHandle);
CloseHandle(eventHandle);
}
@@ -0,0 +1,37 @@
/*
File: EventManager.h
Description: Header file declaring the EventManager class, which manages
user-specific interprocess events for user disable and
notification availability updates.
Author: Trenser
Date:15-Jun-2026
*/
#pragma once
#include <windows.h>
#include <atomic>
#include <functional>
#include <string>
#include <thread>
class EventManager
{
private:
HANDLE m_userDisabledEvent;
HANDLE m_notificationAvailableEvent;
HANDLE m_shutdownEvent;
std::atomic<bool> m_running;
std::thread m_listenerThread;
std::function<void()> m_userDisabledCallback;
std::function<void()> m_notificationCallback;
void run();
public:
EventManager();
~EventManager();
bool initialize(const std::string& userId, std::function<void()> userDisabledCallback, std::function<void()> notificationCallback);
void shutdown();
static void sendUserDisabledEvent(const std::string& userId);
static void sendNotificationAvailableEvent(const std::string& userId);
};
@@ -12,6 +12,9 @@ Date: 19-May-2026
#include "Config.h"
#include "SerializedRecords.h"
#include "FileHelper.h"
#include "ServiceBooking.h"
#include "JobCard.h"
#include "Invoice.h"
/*
Function: DataStore
@@ -252,6 +255,28 @@ Returns:
*/
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;
}
@@ -265,6 +290,28 @@ Returns:
*/
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;
}
@@ -278,6 +325,8 @@ Returns:
*/
util::Map<std::string, TrackedRecord<InventoryItem>>& DataStore::getInventoryItems()
{
auto inventoryItems = loadRecords<InventoryItem, SerializedInventoryItem>(m_inventoryItems);
refreshCache(m_inventoryItemCache, inventoryItems);
return m_inventoryItemCache;
}
@@ -291,6 +340,49 @@ Returns:
*/
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;
}
@@ -304,9 +396,51 @@ Returns:
*/
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;
}
/*
Function: getInvoices
Description: Retrieves all invoice records from the datastore.
@@ -317,6 +451,45 @@ Returns:
*/
util::Map<std::string, TrackedRecord<Invoice>>& DataStore::getInvoices()
{
auto& serviceBookings = getServiceBookings();
auto& inventoryItems = getInventoryItems();
util::Map<std::string, TrackedRecord<Invoice>> invoices = loadRecords<Invoice, SerializedInvoice>(m_invoices);
refreshCache(m_invoiceCache, invoices);
for (int iterator = 0; iterator < m_invoiceCache.getSize(); iterator++)
{
auto& trackedInvoice = m_invoiceCache.getValueAt(iterator);
Invoice* invoice = trackedInvoice.data;
if (!invoice)
{
continue;
}
const std::string& currentBookingId = invoice->getBookingId();
int currentBookingIndex = serviceBookings.find(currentBookingId);
if (currentBookingIndex == -1)
{
throw std::runtime_error("Invalid Service Booking Index.");
}
ServiceBooking* currentBooking = serviceBookings.getValueAt(currentBookingIndex).data;
auto& currentInventoryItemIds = invoice->getPartIDs();
util::Map<std::string, InventoryItem*> currentInventoryItems;
for (int iterator = 0; iterator < currentInventoryItemIds.getSize(); iterator++)
{
const std::string& currentItemId = currentInventoryItemIds[iterator];
int currentItemIndex = inventoryItems.find(currentItemId);
if (currentItemIndex == -1)
{
throw std::runtime_error("Invalid Inventory item id.");
}
InventoryItem* currentItem = inventoryItems.getValueAt(currentItemIndex).data;
if (!currentItem)
{
continue;
}
currentInventoryItems[currentItemId] = currentItem;
}
invoice->setBooking(currentBooking);
invoice->setParts(currentInventoryItems);
}
return m_invoiceCache;
}
@@ -426,6 +599,7 @@ Returns:
*/
void DataStore::saveServices()
{
saveRecords<Service, SerializedService>(m_services, m_serviceCache);
}
/*
@@ -438,6 +612,7 @@ Returns:
*/
void DataStore::saveComboPackages()
{
saveRecords<ComboPackage, SerializedComboPackage>(m_comboPackages, m_comboPackageCache);
}
/*
@@ -450,6 +625,7 @@ Returns:
*/
void DataStore::saveInventoryItems()
{
saveRecords<InventoryItem, SerializedInventoryItem>(m_inventoryItems, m_inventoryItemCache);
}
/*
@@ -462,6 +638,7 @@ Returns:
*/
void DataStore::saveServiceBookings()
{
saveRecords<ServiceBooking, SerializedServiceBooking>(m_serviceBookings, m_serviceBookingCache);
}
/*
@@ -474,6 +651,7 @@ Returns:
*/
void DataStore::saveJobCards()
{
saveRecords<JobCard, SerializedJobCard>(m_jobCards, m_jobCardCache);
}
/*
@@ -486,6 +664,7 @@ Returns:
*/
void DataStore::saveInvoices()
{
saveRecords<Invoice, SerializedInvoice>(m_invoices, m_invoiceCache);
}
/*
@@ -512,7 +691,7 @@ void DataStore::saveObservers(MappingInfo& mapping, util::Map<std::string, User*
SharedMemory::setRecordCount(mapping, observerCount);
for (size_t index = 0; index < observerCount; index++)
{
SerializedObserver serializedObserver;
SerializedObserver serializedObserver{};
User* user = observers.getValueAt(static_cast<int>(index));
strcpy_s(serializedObserver.id, sizeof(serializedObserver.id), user->getId().c_str());
SerializedObserver* destination = static_cast<SerializedObserver*>(SharedMemory::getRecordAddress(mapping, index));
@@ -601,5 +780,4 @@ bool DataStore::unlockDataStore()
return false;
}
return ReleaseMutex(m_globalMutex) != 0;
}
}
@@ -11,6 +11,7 @@ Date: 19-May-2026
#include "Map.h"
#include "MappingInfo.h"
#include "TrackedRecord.h"
#include "SerializedRecords.h"
#include "SharedMemory.h"
#include "User.h"
#include "Notification.h"
@@ -219,6 +220,12 @@ void DataStore::refreshCache(util::Map<std::string, TrackedRecord<TObject>>& cac
if (oldIndex != -1)
{
TrackedRecord<TObject>& oldRecord = oldCache.getValueAt(oldIndex);
if (oldRecord.state == RecordState::MODIFIED)
{
delete refreshedRecord.data;
cache.insert(id, oldRecord);
continue;
}
*oldRecord.data = *refreshedRecord.data;
oldRecord.slotIndex = refreshedRecord.slotIndex;
oldRecord.state = refreshedRecord.state;
@@ -9,6 +9,7 @@ Date: 19-May-2026
#include <sstream>
#include <stdexcept>
#include "SerializedRecords.h"
#include "ComboPackage.h"
#include "Service.h"
#include "Factory.h"
@@ -28,7 +29,8 @@ Returns:
ComboPackage::ComboPackage()
: m_id("CMP" + std::to_string(++m_uid)),
m_status(util::State::ACTIVE),
m_discountPercentage(0.0) {}
m_discountPercentage(0.0) {
}
/*
Function: ComboPackage
@@ -270,72 +272,38 @@ static util::Vector<std::string> getServiceIDsAsVector(const std::string& servic
/*
Function: serialize
Description: Serializes the combo package into a CSV-formatted string.
Description: Serializes the ComboPackage object into a SerializedComboPackage record.
Parameters:
- None
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 << m_id << ','
<< m_packageName << ','
<< m_discountPercentage << ','
<< getServiceIDsAsString(m_serviceIDs) << ','
<< util::getStateString(m_status);
return serializedComboPackage.str();
SerializedComboPackage serialized = {};
strcpy_s(serialized.id, sizeof(serialized.id), m_id.c_str());
strcpy_s(serialized.packageName, sizeof(serialized.packageName), m_packageName.c_str());
strcpy_s(serialized.serviceIDs, sizeof(serialized.serviceIDs), getServiceIDsAsString(m_serviceIDs).c_str());
serialized.discountPercentage = m_discountPercentage;
serialized.status = m_status;
return serialized;
}
/*
Function: deserialize
Description: Deserializes a CSV-formatted string into a ComboPackage object.
Description: Deserializes a SerializedComboPackage record into a ComboPackage object.
Parameters:
- record: const std::string&, serialized combo package record
- serializedComboPackage: const SerializedComboPackage&, serialized combo package record
Returns:
- 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;
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);
util::Vector<std::string> serviceIDs = getServiceIDsAsVector(serializedComboPackage.serviceIDs);
return Factory::getObject<ComboPackage>(
id,
packageName,
discountPercentage,
serializedComboPackage.id,
serializedComboPackage.packageName,
serializedComboPackage.discountPercentage,
serviceIDs,
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";
}
serializedComboPackage.status);
}
@@ -39,7 +39,6 @@ public:
void setDiscountPercentage(double discountPercentage);
void setServices(const util::Map<std::string, Service*>& services);
void setState(util::State status);
std::string serialize() const;
static ComboPackage* deserialize(const std::string&);
static std::string getHeaders();
SerializedComboPackage serialize() const;
static ComboPackage* deserialize(const SerializedComboPackage&);
};
@@ -8,6 +8,7 @@ Date: 19-May-2026
#include <sstream>
#include <stdexcept>
#include "SerializedRecords.h"
#include "Factory.h"
#include "StringHelper.h"
#include "InventoryItem.h"
@@ -27,7 +28,8 @@ InventoryItem::InventoryItem()
: m_id("IIM" + std::to_string(++m_uid)),
m_quantity(0),
m_status(util::State::ACTIVE),
m_price(0.0) {}
m_price(0.0) {
}
/*
Function: InventoryItem
@@ -45,7 +47,8 @@ InventoryItem::InventoryItem(const std::string& partName, int quantity, double p
m_partName(partName),
m_quantity(quantity),
m_status(util::State::ACTIVE),
m_price(price) {}
m_price(price) {
}
/*
Function: InventoryItem (parameterized constructor with ID)
@@ -206,73 +209,37 @@ void InventoryItem::setState(util::State status)
/*
Function: serialize
Description: Serializes the inventory item into a CSV-formatted string.
Description: Serializes the InventoryItem object into a SerializedInventoryItem record.
Parameters:
- None
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 << m_id << ','
<< m_partName << ','
<< m_quantity << ','
<< m_price << ','
<< util::getStateString(m_status);
return serializedInventoryItem.str();
SerializedInventoryItem serialized = {};
strcpy_s(serialized.id, sizeof(serialized.id), m_id.c_str());
strcpy_s(serialized.partName, sizeof(serialized.partName), m_partName.c_str());
serialized.quantity = m_quantity;
serialized.price = m_price;
serialized.status = m_status;
return serialized;
}
/*
Function: deserialize
Description: Deserializes a CSV-formatted string into an InventoryItem object.
Description: Deserializes a SerializedInventoryItem record into an InventoryItem object.
Parameters:
- record: const std::string&, serialized inventory item record
- serializedInventoryItem: const SerializedInventoryItem&, serialized inventory item record
Returns:
- 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>(
id,
partName,
quantity,
price,
status
);
}
/*
Function: getHeaders
Description: Retrieves the CSV headers for inventory item serialization.
Parameters:
- None
Returns:
- std::string: Header string ("ID,PartName,Quantity,Price,Status")
*/
std::string InventoryItem::getHeaders()
{
return "ID,PartName,Quantity,Price,Status";
serializedInventoryItem.id,
serializedInventoryItem.partName,
serializedInventoryItem.quantity,
serializedInventoryItem.price,
serializedInventoryItem.status);
}
@@ -6,11 +6,12 @@ Author: Trenser
Date: 19-May-2026
*/
#pragma once
#include <string>
#include "Enums.h"
struct SerializedInventoryItem;
class InventoryItem
{
private:
@@ -34,7 +35,6 @@ public:
void setQuantity(int quantity);
void setPrice(double price);
void setState(util::State status);
std::string serialize() const;
static InventoryItem* deserialize(const std::string&);
static std::string getHeaders();
SerializedInventoryItem serialize() const;
static InventoryItem* deserialize(const SerializedInventoryItem&);
};
@@ -9,6 +9,7 @@ Date: 19-May-2026
#include <sstream>
#include <stdexcept>
#include "SerializedRecords.h"
#include "Invoice.h"
#include "Factory.h"
#include "InventoryItem.h"
@@ -34,7 +35,8 @@ Invoice::Invoice()
m_discountPercentage(0.0),
m_totalAmount(0.0),
m_paymentMethod(util::PaymentMode()),
m_status(util::PaymentStatus()) {}
m_status(util::PaymentStatus()) {
}
/*
Function: Invoice
@@ -57,16 +59,16 @@ Returns:
Invoice::Invoice(
const std::string& bookingId,
ServiceBooking* booking,
const util::Timestamp& invoiceDate,
double laborCost,
const util::Timestamp& invoiceDate,
double laborCost,
const util::Map<std::string, InventoryItem*>& parts,
double partsCost,
double discountPercentage,
double totalAmount,
const util::Timestamp& paymentDate,
util::PaymentMode paymentMethod,
double discountPercentage,
double totalAmount,
const util::Timestamp& paymentDate,
util::PaymentMode paymentMethod,
util::PaymentStatus status
)
)
: m_id("INV" + std::to_string(++m_uid)),
m_bookingId(bookingId),
m_booking(booking),
@@ -78,7 +80,7 @@ Invoice::Invoice(
m_totalAmount(totalAmount),
m_paymentDate(paymentDate),
m_paymentMethod(paymentMethod),
m_status(status)
m_status(status)
{
int numberOfParts = m_parts.getSize();
auto partPointers = m_parts.getValues();
@@ -473,100 +475,50 @@ static util::Vector<std::string> getPartIDsAsVector(const std::string& partIDsSt
/*
Function: serialize
Description: Serializes the invoice into a CSV-formatted string.
Description: Serializes the Invoice object into a SerializedInvoice record.
Parameters:
- None
Returns:
- std::string: Serialized invoice record
- SerializedInvoice: Serialized representation of the invoice
*/
std::string Invoice::serialize() const
SerializedInvoice Invoice::serialize() const
{
std::ostringstream serializedInvoice;
serializedInvoice << m_id << ','
<< m_bookingId << ','
<< m_invoiceDate.toString() << ','
<< m_laborCost << ','
<< getPartIDsAsString(m_partIDs) << ','
<< m_partsCost << ','
<< m_discountPercentage << ','
<< m_totalAmount << ','
<< m_paymentDate.toString() << ','
<< util::getPaymentModeString(m_paymentMethod) << ','
<< util::getPaymentStatusString(m_status);
return serializedInvoice.str();
SerializedInvoice serialized = {};
strcpy_s(serialized.id, sizeof(serialized.id), m_id.c_str());
strcpy_s(serialized.bookingId, sizeof(serialized.bookingId), m_bookingId.c_str());
strcpy_s(serialized.partIDs, sizeof(serialized.partIDs), getPartIDsAsString(m_partIDs).c_str());
serialized.invoiceDate = m_invoiceDate;
serialized.laborCost = m_laborCost;
serialized.partsCost = m_partsCost;
serialized.discountPercentage = m_discountPercentage;
serialized.totalAmount = m_totalAmount;
serialized.paymentDate = m_paymentDate;
serialized.paymentMethod = m_paymentMethod;
serialized.status = m_status;
return serialized;
}
/*
Function: deserialize
Description: Deserializes a CSV-formatted string into an Invoice object.
Description: Deserializes a SerializedInvoice record into an Invoice object.
Parameters:
- record: const std::string&, serialized invoice record
- serializedInvoice: const SerializedInvoice&, serialized invoice record
Returns:
- Invoice*: Pointer to the deserialized Invoice object
Throws:
- std::runtime_error if data is invalid
*/
Invoice* Invoice::deserialize(const std::string& record)
Invoice* Invoice::deserialize(const SerializedInvoice& serializedInvoice)
{
std::string id, bookingId;
std::string invoiceDateString, laborCostString, partIDsString;
std::string partsCostString, discountPercentageString, totalAmountString;
std::string paymentDateString, paymentMethodString, statusString;
double laborCost, partsCost, discountPercentage, totalAmount;
std::istringstream serializedInvoice(record);
getline(serializedInvoice, id, ',');
getline(serializedInvoice, bookingId, ',');
getline(serializedInvoice, invoiceDateString, ',');
getline(serializedInvoice, laborCostString, ',');
getline(serializedInvoice, partIDsString, ',');
getline(serializedInvoice, partsCostString, ',');
getline(serializedInvoice, discountPercentageString, ',');
getline(serializedInvoice, totalAmountString, ',');
getline(serializedInvoice, paymentDateString, ',');
getline(serializedInvoice, paymentMethodString, ',');
getline(serializedInvoice, statusString, ',');
util::Timestamp invoiceDate;
util::Timestamp paymentDate;
try
{
invoiceDate = util::Timestamp::fromString(invoiceDateString);
paymentDate = util::Timestamp::fromString(paymentDateString);
laborCost = std::stod(laborCostString);
partsCost = std::stod(partsCostString);
discountPercentage = std::stod(discountPercentageString);
totalAmount = std::stod(totalAmountString);
}
catch (...)
{
throw std::runtime_error("Invalid invoice data");
}
util::Vector<std::string> partIDs = getPartIDsAsVector(partIDsString);
util::PaymentMode paymentMethod = util::getPaymentMode(paymentMethodString);
util::PaymentStatus status = util::getPaymentStatus(statusString);
util::Vector<std::string> partIDs = getPartIDsAsVector(serializedInvoice.partIDs);
return Factory::getObject<Invoice>(
id,
bookingId,
invoiceDate,
serializedInvoice.id,
serializedInvoice.bookingId,
serializedInvoice.invoiceDate,
partIDs,
laborCost,
partsCost,
discountPercentage,
totalAmount,
paymentDate,
paymentMethod,
status
);
}
/*
Function: getHeaders
Description: Retrieves the CSV headers for invoice serialization.
Parameters:
- None
Returns:
- std::string: Header string ("ID,BookingID,InvoiceDate,LaborCost,PartIDs,PartsCost,DiscountPercentage,TotalAmount,PaymentDate,PaymentMethod,Status")
*/
std::string Invoice::getHeaders()
{
return "ID,BookingID,InvoiceDate,LaborCost,PartIDs,PartsCost,DiscountPercentage,TotalAmount,PaymentDate,PaymentMethod,Status";
serializedInvoice.laborCost,
serializedInvoice.partsCost,
serializedInvoice.discountPercentage,
serializedInvoice.totalAmount,
serializedInvoice.paymentDate,
serializedInvoice.paymentMethod,
serializedInvoice.status);
}
@@ -6,7 +6,6 @@ Author: Trenser
Date: 19-May-2026
*/
#pragma once
#include <string>
#include "Map.h"
@@ -16,6 +15,7 @@ Date: 19-May-2026
class ServiceBooking;
class InventoryItem;
struct SerializedInvoice;
class Invoice
{
@@ -39,14 +39,14 @@ public:
Invoice(
const std::string& bookingId,
ServiceBooking* booking,
const util::Timestamp& invoiceDate,
double laborCost,
const util::Map<std::string,InventoryItem*>& parts,
const util::Timestamp& invoiceDate,
double laborCost,
const util::Map<std::string, InventoryItem*>& parts,
double partsCost,
double discountPercentage,
double totalAmount,
const util::Timestamp& paymentDate,
util::PaymentMode paymentMethod,
double discountPercentage,
double totalAmount,
const util::Timestamp& paymentDate,
util::PaymentMode paymentMethod,
util::PaymentStatus status
);
Invoice(
@@ -87,7 +87,6 @@ public:
void setPaymentDate(const util::Timestamp& paymentDate);
void setPaymentMethod(util::PaymentMode paymentMethod);
void setStatus(util::PaymentStatus status);
std::string serialize() const;
static Invoice* deserialize(const std::string&);
static std::string getHeaders();
SerializedInvoice serialize() const;
static Invoice* deserialize(const SerializedInvoice&);
};
@@ -9,6 +9,7 @@ Date:19-May-2026
#include <sstream>
#include <stdexcept>
#include "SerializedRecords.h"
#include "JobCard.h"
#include "Factory.h"
#include "StringHelper.h"
@@ -28,7 +29,8 @@ JobCard::JobCard()
m_booking(nullptr),
m_service(nullptr),
m_technician(nullptr),
m_status(util::ServiceJobStatus()) {}
m_status(util::ServiceJobStatus()) {
}
/*
Function: JobCard
@@ -65,7 +67,8 @@ JobCard::JobCard(const std::string& bookingId,
m_technician(technician),
m_assignedDate(assignedDate),
m_status(status),
m_completionDate(completionDate) {}
m_completionDate(completionDate) {
}
/*
Function: JobCard (parameterized constructor with ID)
@@ -351,79 +354,41 @@ void JobCard::setCompletionDate(const util::Timestamp& completionDate)
/*
Function: serialize
Description: Serializes the job card into a CSV-formatted string.
Description: Serializes the JobCard object into a SerializedJobCard record.
Parameters:
- None
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 << m_id << ','
<< m_bookingId << ','
<< m_serviceId << ','
<< m_technicianId << ','
<< m_assignedDate.toString() << ','
<< util::getServiceJobStatusString(m_status) << ','
<< m_completionDate.toString();
return serializedJobCard.str();
SerializedJobCard serialized = {};
strcpy_s(serialized.id, sizeof(serialized.id), m_id.c_str());
strcpy_s(serialized.bookingId, sizeof(serialized.bookingId), m_bookingId.c_str());
strcpy_s(serialized.serviceId, sizeof(serialized.serviceId), m_serviceId.c_str());
strcpy_s(serialized.technicianId, sizeof(serialized.technicianId), m_technicianId.c_str());
serialized.assignedDate = m_assignedDate;
serialized.status = m_status;
serialized.completionDate = m_completionDate;
return serialized;
}
/*
Function: deserialize
Description: Deserializes a CSV-formatted string into a JobCard object.
Description: Deserializes a SerializedJobCard record into a JobCard object.
Parameters:
- record: const std::string&, serialized job card record
- serializedJobCard: const SerializedJobCard&, serialized job card record
Returns:
- 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>(
id,
bookingId,
serviceId,
technicianId,
assignedDate,
status,
completionDate
);
}
/*
Function: getHeaders
Description: Retrieves the CSV headers for job card serialization.
Parameters:
- None
Returns:
- std::string: Header string ("ID,BookingID,ServiceID,TechnicianID,AssignedDate,Status,CompletionDate")
*/
std::string JobCard::getHeaders()
{
return "ID,BookingID,ServiceID,TechnicianID,AssignedDate,Status,CompletionDate";
serializedJobCard.id,
serializedJobCard.bookingId,
serializedJobCard.serviceId,
serializedJobCard.technicianId,
serializedJobCard.assignedDate,
serializedJobCard.status,
serializedJobCard.completionDate);
}
@@ -15,6 +15,7 @@ Date:19-May-2026
class ServiceBooking;
class Service;
class User;
struct SerializedJobCard;
class JobCard
{
@@ -34,11 +35,11 @@ public:
JobCard();
JobCard(const std::string& bookingId,
ServiceBooking* booking,
Service* service,
const std::string& serviceId,
const std::string& technicianId,
User* technician,
const util::Timestamp& assignedDate,
Service* service,
const std::string& serviceId,
const std::string& technicianId,
User* technician,
const util::Timestamp& assignedDate,
util::ServiceJobStatus status,
const util::Timestamp& completionDate
);
@@ -70,7 +71,6 @@ public:
void setAssignedDate(const util::Timestamp& assignedDate);
void setStatus(util::ServiceJobStatus status);
void setCompletionDate(const util::Timestamp& completionDate);
std::string serialize() const;
static JobCard* deserialize(const std::string&);
static std::string getHeaders();
SerializedJobCard serialize() const;
static JobCard* deserialize(const SerializedJobCard&);
};
@@ -8,6 +8,7 @@ Date: 19-May-2026
*/
#include <sstream>
#include "SerializedRecords.h"
#include "Service.h"
#include "InventoryItem.h"
#include "StringHelper.h"
@@ -27,7 +28,8 @@ Returns:
Service::Service()
: m_id("SRV" + std::to_string(++m_uid)),
m_status(util::State::ACTIVE),
m_laborCost(0.0) {}
m_laborCost(0.0) {
}
/*
Function: Service
@@ -44,7 +46,7 @@ Service::Service(const std::string& name, const util::Map<std::string, Inventory
m_name(name),
m_requiredInventoryItems(requiredInventoryItems),
m_status(util::State::ACTIVE),
m_laborCost(laborCost)
m_laborCost(laborCost)
{
int numberOfInventoryItems = m_requiredInventoryItems.getSize();
auto inventoryItemPointers = m_requiredInventoryItems.getValues();
@@ -266,72 +268,38 @@ static util::Vector<std::string> getInventoryItemIDsAsVector(const std::string&
/*
Function: serialize
Description: Serializes the service into a CSV-formatted string.
Description: Serializes the Service object into a SerializedService record.
Parameters:
- None
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 << m_id << ','
<< m_name << ','
<< getInventoryItemIDsAsString(m_requiredInventoryItemIDs) << ','
<< m_laborCost << ','
<< util::getStateString(m_status);
return serializedService.str();
SerializedService serialized = {};
strcpy_s(serialized.id, sizeof(serialized.id), m_id.c_str());
strcpy_s(serialized.name, sizeof(serialized.name), m_name.c_str());
strcpy_s(serialized.inventoryItemIDs, sizeof(serialized.inventoryItemIDs), getInventoryItemIDsAsString(m_requiredInventoryItemIDs).c_str());
serialized.laborCost = m_laborCost;
serialized.status = m_status;
return serialized;
}
/*
Function: deserialize
Description: Deserializes a CSV-formatted string into a Service object.
Description: Deserializes a SerializedService record into a Service object.
Parameters:
- record: const std::string&, serialized service record
- serializedService: const SerializedService&, serialized service record
Returns:
- 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;
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);
util::Vector<std::string> inventoryItemIDs = getInventoryItemIDsAsVector(serializedService.inventoryItemIDs);
return Factory::getObject<Service>(
id,
name,
serializedService.id,
serializedService.name,
inventoryItemIDs,
laborCost,
status
);
}
/*
Function: getHeaders
Description: Retrieves the CSV headers for service serialization.
Parameters:
- None
Returns:
- std::string: Header string ("ID,Name,InventoryIDs,LaborCost,Status")
*/
std::string Service::getHeaders()
{
return "ID,Name,InventoryIDs,LaborCost,Status";
serializedService.laborCost,
serializedService.status);
}
@@ -6,7 +6,6 @@ Author: Trenser
Date: 19-May-2026
*/
#pragma once
#include <string>
#include "Map.h"
@@ -14,6 +13,7 @@ Date: 19-May-2026
#include "Enums.h"
class InventoryItem;
struct SerializedService;
class Service
{
@@ -40,7 +40,6 @@ public:
void setRequiredInventoryItems(const util::Map<std::string, InventoryItem*>& requiredInventoryItems);
void setLaborCost(double laborCost);
void setState(util::State status);
std::string serialize() const;
static Service* deserialize(const std::string&);
static std::string getHeaders();
SerializedService serialize() const;
static Service* deserialize(const SerializedService&);
};
@@ -6,8 +6,10 @@ Description: Implementation file containing the method definitions of the
Author: Trenser
Date:19-May-2026
*/
#include <stdexcept>
#include <sstream>
#include "SerializedRecords.h"
#include "ServiceBooking.h"
#include "Service.h"
#include "Enums.h"
@@ -28,7 +30,8 @@ ServiceBooking::ServiceBooking()
m_customer(nullptr),
m_assignedTechnician(nullptr),
m_status(util::ServiceJobStatus::PENDING),
m_discountPercentage(0.0) {}
m_discountPercentage(0.0) {
}
/*
Function: ServiceBooking
@@ -437,84 +440,46 @@ static util::Vector<std::string> getServiceIDsAsVector(const std::string& servic
/*
Function: serialize
Description: Serializes the service booking into a CSV-formatted string.
Description: Serializes the ServiceBooking object into a SerializedServiceBooking record.
Parameters:
- None
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;
serializedBooking << m_id << ','
<< util::getServiceJobStatusString(m_status) << ','
<< getServiceIDsAsString(m_serviceIDs) << ','
<< m_customerId << ','
<< m_vehicleNumber << ','
<< m_vehicleBrand << ','
<< m_vehicleModel << ','
<< m_assignedTechnicianId << ','
<< m_discountPercentage << ',';
return serializedBooking.str();
SerializedServiceBooking serialized = {};
strcpy_s(serialized.id, sizeof(serialized.id), m_id.c_str());
strcpy_s(serialized.serviceIDs, sizeof(serialized.serviceIDs), getServiceIDsAsString(m_serviceIDs).c_str());
strcpy_s(serialized.customerId, sizeof(serialized.customerId), m_customerId.c_str());
strcpy_s(serialized.vehicleNumber, sizeof(serialized.vehicleNumber), m_vehicleNumber.c_str());
strcpy_s(serialized.vehicleBrand, sizeof(serialized.vehicleBrand), m_vehicleBrand.c_str());
strcpy_s(serialized.vehicleModel, sizeof(serialized.vehicleModel), m_vehicleModel.c_str());
strcpy_s(serialized.assignedTechnicianId, sizeof(serialized.assignedTechnicianId), m_assignedTechnicianId.c_str());
serialized.status = m_status;
serialized.discountPercentage = m_discountPercentage;
return serialized;
}
/*
Function: deserialize
Description: Deserializes a CSV-formatted string into a ServiceBooking object.
Description: Deserializes a SerializedServiceBooking record into a ServiceBooking object.
Parameters:
- record: const std::string&, serialized booking record
- serializedServiceBooking: const SerializedServiceBooking&, serialized service booking record
Returns:
- 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;
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);
util::Vector<std::string> serviceIDs = getServiceIDsAsVector(serializedServiceBooking.serviceIDs);
return Factory::getObject<ServiceBooking>(
id,
status,
serializedServiceBooking.id,
serializedServiceBooking.status,
serviceIDs,
customerId,
vehicleNumber,
vehicleBrand,
vehicleModel,
assignedTechnicianId,
discountPercentage
);
}
/*
Function: getHeaders
Description: Retrieves the CSV headers for service booking serialization.
Parameters:
- None
Returns:
- std::string: Header string ("ID,Status,ServiceIDs,CustomerID,VehicleNumber,VehicleBrand,VehicleModel,AssignedTechnicianID,DiscountPercentage")
*/
std::string ServiceBooking::getHeaders()
{
return "ID,Status,ServiceIDs,CustomerID,VehicleNumber,VehicleBrand,VehicleModel,AssignedTechnicianID,DiscountPercentage";
serializedServiceBooking.customerId,
serializedServiceBooking.vehicleNumber,
serializedServiceBooking.vehicleBrand,
serializedServiceBooking.vehicleModel,
serializedServiceBooking.assignedTechnicianId,
serializedServiceBooking.discountPercentage);
}
@@ -6,6 +6,7 @@ Description: Header file declaring the ServiceBooking class, which represents
Author: Trenser
Date:19-May-2026
*/
#pragma once
#include <string>
#include "Map.h"
@@ -14,6 +15,7 @@ Date:19-May-2026
class Service;
class User;
struct SerializedServiceBooking;
class ServiceBooking
{
@@ -78,7 +80,6 @@ public:
void setAssignedTechnicianId(const std::string& assignedTechnicianId);
void setAssignedTechnician(User* assignedTechnician);
void setDiscountPercentage(double discountPercentage);
std::string serialize() const;
static ServiceBooking* deserialize(const std::string&);
static std::string getHeaders();
SerializedServiceBooking serialize() const;
static ServiceBooking* deserialize(const SerializedServiceBooking&);
};
@@ -14,6 +14,28 @@ Date:19-May-2026
#include "DataStoreLockGuard.h"
User* AuthenticationManagementService::m_authenticatedUser = nullptr;
bool AuthenticationManagementService::m_isAuthorized = false;
EventManager AuthenticationManagementService::m_eventManager;
HANDLE AuthenticationManagementService::m_accountDisabledEvent = NULL;
HANDLE AuthenticationManagementService::m_notificationsAvailableEvent = NULL;
/*
Function: ensureAuthorization
Description: Verifies that a user is currently authenticated before allowing
access to a protected operation. Throws an exception if no
authorized user session exists.
Parameter: None
Return type: void
Throws: std::runtime_error - if the user is not authorized
*/
void AuthenticationManagementService::ensureAuthorization()
{
if (!m_authenticatedUser || !m_isAuthorized)
{
throw std::runtime_error("You are not authorized to do this operation!");
}
}
/*
Function: login
@@ -37,6 +59,24 @@ bool AuthenticationManagementService::login(const std::string& username, const s
if (password == user->getPassword())
{
m_authenticatedUser = user;
m_isAuthorized = true;
m_eventManager.initialize(
user->getId(),
[]()
{
if (m_accountDisabledEvent)
{
AuthenticationManagementService::m_isAuthorized = false;
SetEvent(m_accountDisabledEvent);
}
},
[]()
{
if (m_notificationsAvailableEvent)
{
SetEvent(m_notificationsAvailableEvent);
}
});
return true;
}
return false;
@@ -65,7 +105,11 @@ Return type: void
*/
void AuthenticationManagementService::logout()
{
m_eventManager.shutdown();
m_authenticatedUser = nullptr;
m_isAuthorized = false;
m_accountDisabledEvent = NULL;
m_notificationsAvailableEvent = NULL;
}
/*
@@ -77,6 +121,7 @@ Return type: void
*/
void AuthenticationManagementService::changePassword(const std::string& newPassword)
{
AuthenticationManagementService::ensureAuthorization();
DataStoreLockGuard lock(m_dataStore);
auto& trackedUsersMap = m_dataStore.getUsers();
if (m_authenticatedUser == nullptr)
@@ -92,3 +137,17 @@ void AuthenticationManagementService::changePassword(const std::string& newPassw
trackedUsersMap.getValueAt(index).state = RecordState::MODIFIED;
m_dataStore.saveUsers();
}
/*
Function: registerEvents
Description: Registers menu event handles used to notify the active
menu of account disable and notification events.
Parameter: HANDLE accountDisabledEvent - account disabled event handle
HANDLE notificationAvailableEvent - notification event handle
Return type: void
*/
void AuthenticationManagementService::registerEvents(HANDLE accountDisabledEvent, HANDLE notificationAvailableEvent)
{
m_accountDisabledEvent = accountDisabledEvent;
m_notificationsAvailableEvent = notificationAvailableEvent;
}
@@ -9,6 +9,8 @@ Date:19-May-2026
#pragma once
#include <string>
#include <windows.h>
#include "EventManager.h"
#include "DataStore.h"
class User;
@@ -17,11 +19,17 @@ class AuthenticationManagementService
{
private:
static User* m_authenticatedUser;
static bool m_isAuthorized;
static EventManager m_eventManager;
static HANDLE m_accountDisabledEvent;
static HANDLE m_notificationsAvailableEvent;
DataStore& m_dataStore;
public:
AuthenticationManagementService() : m_dataStore(DataStore::getInstance()) {}
static void ensureAuthorization();
bool login(const std::string& username, const std::string& password);
void logout();
void changePassword(const std::string& newPassword);
User* getAuthenticatedUser();
void registerEvents(HANDLE accountDisabledEvent, HANDLE notificationAvailableEvent);
};
@@ -14,11 +14,13 @@ Date: 22-May-2026
#include "Factory.h"
#include "InventoryItem.h"
#include "InventoryManagementService.h"
#include "AuthenticationManagementService.h"
#include "Timestamp.h"
#include "User.h"
#include "Utility.h"
#include "Vector.h"
#include "DataStoreLockGuard.h"
#include "EventManager.h"
util::Map<std::string, User*> InventoryManagementService::m_observers{};
@@ -57,18 +59,19 @@ Returns:
*/
void InventoryManagementService::sendLowStockAlerts()
{
auto& inventoryItems = m_dataStore.getInventoryItems();
if (inventoryItems.isEmpty())
DataStoreLockGuard lock(m_dataStore);
auto& trackedInventoryItemsMap = m_dataStore.getInventoryItems();
auto& trackedUserMap = m_dataStore.getUsers();
if (trackedInventoryItemsMap.isEmpty())
{
return;
}
int inventoryItemsSize = inventoryItems.getSize();
auto& usersMap = m_dataStore.getUsers();
int usersMapSize = usersMap.getSize();
int inventoryItemsSize = trackedInventoryItemsMap.getSize();
int usersMapSize = trackedUserMap.getSize();
util::Vector<User*> adminUsers;
for (int index = 0; index < usersMapSize; index++)
{
User* user = usersMap.getValueAt(index);
User* user = trackedUserMap.getValueAt(index).data;
if (user->getUserType() == util::UserType::ADMIN)
{
adminUsers.push_back(user);
@@ -81,7 +84,7 @@ void InventoryManagementService::sendLowStockAlerts()
}
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)
{
sendLowStockAlertsToAdmins(*this, inventoryItem, adminUsers);
@@ -100,8 +103,12 @@ Return type: void
*/
void InventoryManagementService::addInventoryItem(const std::string& partName, int quantity, double price)
{
AuthenticationManagementService::ensureAuthorization();
DataStoreLockGuard lock(m_dataStore);
auto& trackedInventoryItemMap = m_dataStore.getInventoryItems();
InventoryItem* newItem = Factory::getObject<InventoryItem>(partName, quantity, price);
m_dataStore.getInventoryItems().insert(newItem->getId(), newItem);
trackedInventoryItemMap.insert(newItem->getId(), util::createNewRecord(newItem));
m_dataStore.saveInventoryItems();
}
/*
@@ -113,16 +120,23 @@ Return type: void
*/
void InventoryManagementService::addInventoryItemStock(const std::string& selectedItemId, int quantity)
{
int index = m_dataStore.getInventoryItems().find(selectedItemId);
if (index != -1)
{
InventoryItem* item = m_dataStore.getInventoryItems().getValueAt(index);
if (item != nullptr)
{
int totalQuantity = item->getQuantity() + quantity;
item->setQuantity(totalQuantity);
}
}
AuthenticationManagementService::ensureAuthorization();
DataStoreLockGuard lock(m_dataStore);
auto& trackedInventoryItemMap = m_dataStore.getInventoryItems();
int index = trackedInventoryItemMap.find(selectedItemId);
if (index == -1)
{
throw std::runtime_error("Inventory update failed: Item ID '" + selectedItemId + "' not found.");
}
InventoryItem* item = trackedInventoryItemMap.getValueAt(index).data;
if (item == nullptr)
{
throw std::runtime_error("Inventory update failed. Item does not exist.\n");
}
int totalQuantity = item->getQuantity() + quantity;
item->setQuantity(totalQuantity);
trackedInventoryItemMap.getValueAt(index).state = RecordState::MODIFIED;
m_dataStore.saveInventoryItems();
}
/*
@@ -133,7 +147,11 @@ Return type: util::Map<std::string, InventoryItem*>
*/
util::Map<std::string, InventoryItem*> InventoryManagementService::getInventoryItems()
{
return m_dataStore.getInventoryItems();
AuthenticationManagementService::ensureAuthorization();
DataStoreLockGuard lock(m_dataStore);
auto& trackedInventoryItemMap = m_dataStore.getInventoryItems();
auto inventoryMap = util::getObjects(trackedInventoryItemMap);
return inventoryMap;
}
/*
@@ -144,15 +162,22 @@ Return type: void
*/
void InventoryManagementService::removeInventoryItem(const std::string& inventoryItemID)
{
int index = m_dataStore.getInventoryItems().find(inventoryItemID);
if (index != -1)
{
InventoryItem* item = m_dataStore.getInventoryItems().getValueAt(index);
if (item != nullptr)
{
item->setState(util::State::INACTIVE);
}
}
AuthenticationManagementService::ensureAuthorization();
DataStoreLockGuard lock(m_dataStore);
auto& trackedInventoryItemMap = m_dataStore.getInventoryItems();
int index = trackedInventoryItemMap.find(inventoryItemID);
if (index == -1)
{
throw std::runtime_error("Inventory removal failed: Item ID '" + inventoryItemID + "' not found.");
}
InventoryItem* item = trackedInventoryItemMap.getValueAt(index).data;
if (item == nullptr)
{
throw std::runtime_error("Inventory removal failed: Item ID does not exist.");
}
item->setState(util::State::INACTIVE);
trackedInventoryItemMap.getValueAt(index).state = RecordState::MODIFIED;
m_dataStore.saveInventoryItems();
}
/*
@@ -163,12 +188,20 @@ Return type: InventoryItem*
*/
InventoryItem* InventoryManagementService::getInventoryItem(const std::string& inventoryItemID)
{
int index = m_dataStore.getInventoryItems().find(inventoryItemID);
if (index != -1)
AuthenticationManagementService::ensureAuthorization();
DataStoreLockGuard lock(m_dataStore);
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;
}
/*
@@ -243,6 +276,7 @@ void InventoryManagementService::sendNotification(User* user, const std::string&
{
return;
}
auto& trackedNotificationsMap = m_dataStore.getNotifications();
Notification* notification = Factory::getObject<Notification>(
user->getId(),
title,
@@ -252,8 +286,8 @@ void InventoryManagementService::sendNotification(User* user, const std::string&
{
throw std::runtime_error("Failed to create notification");
}
auto& trackedNotificationsMap = m_dataStore.getNotifications();
trackedNotificationsMap.insert(notification->getId(), util::createNewRecord(notification));
m_dataStore.saveNotifications();
EventManager::sendNotificationAvailableEvent(user->getId());
}
@@ -15,12 +15,15 @@ Date: 20-May-2026
#include "Invoice.h"
#include "JobCard.h"
#include "PaymentManagementService.h"
#include "AuthenticationManagementService.h"
#include "DataStoreLockGuard.h"
#include "Service.h"
#include "ServiceBooking.h"
#include "Timestamp.h"
#include "User.h"
#include "Utility.h"
#include "DataStoreLockGuard.h"
#include "EventManager.h"
util::Map<std::string, User*> PaymentManagementService::m_observers{};
@@ -96,6 +99,7 @@ void PaymentManagementService::sendNotification(User* user, const std::string& t
{
return;
}
auto& trackedNotificationsMap = m_dataStore.getNotifications();
Notification* notification = Factory::getObject<Notification>(
user->getId(),
title,
@@ -105,9 +109,9 @@ void PaymentManagementService::sendNotification(User* user, const std::string& t
{
throw std::runtime_error("Failed to create notification");
}
auto& trackedNotificationsMap = m_dataStore.getNotifications();
trackedNotificationsMap.insert(notification->getId(), util::createNewRecord(notification));
m_dataStore.saveNotifications();
EventManager::sendNotificationAvailableEvent(user->getId());
}
/*
@@ -121,11 +125,12 @@ Returns:
*/
void PaymentManagementService::sendPaymentReminders()
{
auto& invoicesMap = m_dataStore.getInvoices();
int invoicesMapSize = invoicesMap.getSize();
DataStoreLockGuard lock(m_dataStore);
auto& trackedInvoicesMap = m_dataStore.getInvoices();
int invoicesMapSize = trackedInvoicesMap.getSize();
for (int index = 0; index < invoicesMapSize; index++)
{
const Invoice* invoice = invoicesMap.getValueAt(index);
const Invoice* invoice = trackedInvoicesMap.getValueAt(index).data;
if (invoice && invoice->getStatus() == util::PaymentStatus::PENDING)
{
util::Timestamp invoiceCreationTimestamp = invoice->getInvoiceDate();
@@ -181,6 +186,7 @@ Throws:
*/
void PaymentManagementService::generateInvoice(ServiceBooking* booking)
{
DataStoreLockGuard lock(m_dataStore);
if (!booking)
{
throw std::runtime_error("Invoice generation failed: booking is null.");
@@ -190,10 +196,10 @@ void PaymentManagementService::generateInvoice(ServiceBooking* booking)
std::string bookingID = booking->getId();
util::Map<std::string, Service*> servicesInTheBookedService = booking->getServices();
util::Map<std::string, InventoryItem*> completeInventoryItemMapOfBooking;
util::Map<std::string, JobCard*> currentJobCards = m_dataStore.getJobCards();
for (int iterator = 0; iterator < currentJobCards.getSize(); iterator++)
auto& currentTrackedJobCards = m_dataStore.getJobCards();
for (int iterator = 0; iterator < currentTrackedJobCards.getSize(); iterator++)
{
JobCard* currentJobCard = currentJobCards.getValueAt(iterator);
JobCard* currentJobCard = currentTrackedJobCards.getValueAt(iterator).data;
util::ServiceJobStatus currentJobCardStatus = currentJobCard->getStatus();
if (currentJobCard->getBookingId() == bookingID && currentJobCardStatus != util::ServiceJobStatus::CANCELLED && currentJobCardStatus != util::ServiceJobStatus::COMPLETED)
{
@@ -213,8 +219,9 @@ void PaymentManagementService::generateInvoice(ServiceBooking* booking)
totalServiceCost = totalLaborCost + totalPartsCost;
totalServiceCost -= (totalServiceCost * (discountPercentage / 100));
Invoice* invoice = Factory::getObject<Invoice>(bookingID, booking, util::Timestamp(), totalLaborCost, completeInventoryItemMapOfBooking, totalPartsCost, discountPercentage, totalServiceCost, util::Timestamp(), util::PaymentMode::NOTSET, util::PaymentStatus::PENDING);
util::Map<std::string, Invoice*>& currentInvoices = m_dataStore.getInvoices();
currentInvoices.insert(invoice->getId(), invoice);
auto& currentTrackedInvoices = m_dataStore.getInvoices();
currentTrackedInvoices.insert(invoice->getId(), util::createNewRecord(invoice));
m_dataStore.saveInvoices();
}
/*
@@ -227,11 +234,13 @@ Returns:
*/
util::Map<std::string, Invoice*> PaymentManagementService::getInvoices(const std::string& customerID)
{
util::Map<std::string, Invoice*>& currentInvoices = m_dataStore.getInvoices();
AuthenticationManagementService::ensureAuthorization();
DataStoreLockGuard lock(m_dataStore);
auto& currentTrackedInvoices = m_dataStore.getInvoices();
util::Map<std::string, Invoice*> currentUserInvoices;
for (int iterator = 0; iterator < currentInvoices.getSize(); iterator++)
for (int iterator = 0; iterator < currentTrackedInvoices.getSize(); iterator++)
{
Invoice* currentInvoice = currentInvoices.getValueAt(iterator);
Invoice* currentInvoice = currentTrackedInvoices.getValueAt(iterator).data;
if (currentInvoice->getBooking()->getCustomerId() == customerID)
{
currentUserInvoices.insert(currentInvoice->getId(), currentInvoice);
@@ -254,11 +263,14 @@ Throws:
*/
void PaymentManagementService::completePayment(const std::string& invoiceID, util::PaymentMode paymentMode)
{
auto& currentInvoices = m_dataStore.getInvoices();
int invoiceIndex = currentInvoices.find(invoiceID);
AuthenticationManagementService::ensureAuthorization();
DataStoreLockGuard lock(m_dataStore);
auto& currentTrackedInvoices = m_dataStore.getInvoices();
int invoiceIndex = currentTrackedInvoices.find(invoiceID);
if (invoiceIndex != -1)
{
Invoice* invoice = currentInvoices.getValueAt(invoiceIndex);
auto& trackedInvoice = currentTrackedInvoices.getValueAt(invoiceIndex);
Invoice* invoice = trackedInvoice.data;
if (invoice && invoice->getStatus() != util::PaymentStatus::PAID)
{
User* currentUser = invoice->getBooking()->getCustomer();
@@ -269,25 +281,31 @@ void PaymentManagementService::completePayment(const std::string& invoiceID, uti
title = "Payment successful";
message = "Payment successful for Invoice ID " + invoiceID;
sendNotification(currentUser, title, message);
trackedInvoice.state = RecordState::MODIFIED;
}
}
else
{
throw std::runtime_error("Payment failed: invalid invoice ID.");
}
m_dataStore.saveInvoices();
}
/*
Function: getAllInvoice
Function: getAllInvoices
Description: Provides access to all invoices stored in the data store.
Parameters:
- none
Returns:
- util::Map<std::string, Invoice*>&: Map of invoice IDs to invoice objects
- util::Map<std::string, Invoice*>: Map of invoice IDs to invoice objects
*/
util::Map<std::string, Invoice*>& PaymentManagementService::getAllInvoices()
util::Map<std::string, Invoice*> PaymentManagementService::getAllInvoices()
{
return m_dataStore.getInvoices();
AuthenticationManagementService::ensureAuthorization();
DataStoreLockGuard lock(m_dataStore);
util::Map<std::string, Invoice*> invoices;
invoices = util::getObjects(m_dataStore.getInvoices());
return invoices;
}
/*
@@ -303,20 +321,25 @@ Throws:
*/
void PaymentManagementService::confirmPayment(const std::string& invoiceID)
{
auto& currentInvoices = m_dataStore.getInvoices();
int invoiceIndex = currentInvoices.find(invoiceID);
AuthenticationManagementService::ensureAuthorization();
DataStoreLockGuard lock(m_dataStore);
auto& currentTrackedInvoices = m_dataStore.getInvoices();
int invoiceIndex = currentTrackedInvoices.find(invoiceID);
if (invoiceIndex == -1)
{
throw std::runtime_error("Payment confirmation failed: invalid invoice ID.");
}
Invoice* invoice = currentInvoices.getValueAt(invoiceIndex);
auto& trackedInvoice = currentTrackedInvoices.getValueAt(invoiceIndex);
Invoice* invoice = trackedInvoice.data;
if (!invoice || invoice->getStatus() != util::PaymentStatus::PAID)
{
throw std::runtime_error("Payment confirmation failed: invoice is not awaiting confirmation.");
}
User* currentUser = invoice->getBooking()->getCustomer();
invoice->setStatus(util::PaymentStatus::COMPLETED);
trackedInvoice.state = RecordState::MODIFIED;
std::string title = "Payment Confirmed";
std::string message = "Payment Confirmed for Invoice ID " + invoiceID;
sendNotification(currentUser, title, message);
m_dataStore.saveInvoices();
}
@@ -27,7 +27,7 @@ public:
void generateInvoice(ServiceBooking* booking);
util::Map<std::string, Invoice*> getInvoices(const std::string& customerID);
void completePayment(const std::string& invoiceID, util::PaymentMode paymentMode);
util::Map<std::string, Invoice*>& getAllInvoices();
util::Map<std::string, Invoice*> getAllInvoices();
void confirmPayment(const std::string& invoiceID);
void sendPaymentReminders();
void sendNotification(User* user, const std::string& title, const std::string& message) override;
@@ -24,8 +24,34 @@ Date:19-May-2026
#include "Timestamp.h"
#include "User.h"
#include "UserManagementService.h"
#include "Utility.h"
#include "DataStoreLockGuard.h"
#include "Utility.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
@@ -40,24 +66,26 @@ 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!");
}
auto& servicesMap = m_dataStore.getServices();
auto& serviceBookingMap = m_dataStore.getServiceBookings();
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 = servicesMap.find(serviceIDs[index]);
int serviceIndex = trackedServicesMap.find(serviceIDs[index]);
if (serviceIndex == -1)
{
throw std::runtime_error("Service not found!");
}
Service* service = servicesMap.getValueAt(serviceIndex);
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);
@@ -65,10 +93,12 @@ void ServiceManagementService::purchaseService(const util::Vector<std::string>&
{
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 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);
}
/*
@@ -84,30 +114,34 @@ 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!");
}
auto& comboPackagesMap = m_dataStore.getComboPackages();
auto& serviceBookingMap = m_dataStore.getServiceBookings();
int comboPackageIndex = comboPackagesMap.find(comboPackageID);
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 = comboPackagesMap[comboPackageID];
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");
}
serviceBookingMap[serviceBooking->getId()] = serviceBooking;
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);
m_dataStore.saveServiceBookings();
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{};
@@ -184,6 +218,7 @@ void ServiceManagementService::sendNotification(User* user, const std::string& t
{
return;
}
auto& trackedNotificationsMap = m_dataStore.getNotifications();
Notification* notification = Factory::getObject<Notification>(
user->getId(),
title,
@@ -193,9 +228,9 @@ void ServiceManagementService::sendNotification(User* user, const std::string& t
{
throw std::runtime_error("Failed to create notification");
}
auto& trackedNotificationsMap = m_dataStore.getNotifications();
trackedNotificationsMap.insert(notification->getId(), util::createNewRecord(notification));
m_dataStore.saveNotifications();
EventManager::sendNotificationAvailableEvent(user->getId());
}
/*
@@ -205,7 +240,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
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;
if (!booking)
@@ -224,9 +259,17 @@ static void restoreInventory(ServiceBooking* booking)
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;
}
}
}
@@ -244,23 +287,28 @@ Parameters:
util::UserType userType - Type of user initiating cancellation (CUSTOMER or TECHNICIAN)
Return type: void
*/
static void processBookingCancellation(ServiceBooking* booking,
util::Map<std::string, JobCard*>& jobs,
static void processBookingCancellation(TrackedRecord<ServiceBooking>& trackedBooking,
util::Map<std::string, TrackedRecord<JobCard>>& jobs,
ServiceManagementService& currentService,
util::UserType userType)
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)
{
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)
{
continue;
}
jobCard->setStatus(util::ServiceJobStatus::CANCELLED);
trackedJobCard.state = RecordState::MODIFIED;
if (userType == util::UserType::CUSTOMER)
{
if (User* technician = booking->getAssignedTechnician())
@@ -293,7 +341,8 @@ static void processBookingCancellation(ServiceBooking* booking,
}
booking->setAssignedTechnician(nullptr);
booking->setAssignedTechnicianId("");
restoreInventory(booking);
trackedBooking.state = RecordState::MODIFIED;
restoreInventory(booking, trackedInventoryItems);
}
/*
@@ -306,22 +355,25 @@ Return type: void
*/
void ServiceManagementService::cancelCustomerServiceBookings(const std::string& customerID)
{
auto& users = m_dataStore.getUsers();
int userIndex = users.find(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 = users.getValueAt(userIndex);
User* customer = trackedUsers.getValueAt(userIndex).data;
if (!customer)
{
throw std::runtime_error("User not found: " + customerID);
}
auto& bookings = m_dataStore.getServiceBookings();
auto& jobs = m_dataStore.getJobCards();
for (int iteratorOne = 0; iteratorOne < bookings.getSize(); iteratorOne++)
auto& trackedBookings = m_dataStore.getServiceBookings();
auto& trackedJobs = m_dataStore.getJobCards();
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)
{
continue;
@@ -336,8 +388,12 @@ void ServiceManagementService::cancelCustomerServiceBookings(const std::string&
{
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 +405,25 @@ Return type: void
*/
void ServiceManagementService::cancelTechnicianJobs(const std::string& technicianID)
{
auto& users = m_dataStore.getUsers();
int userIndex = users.find(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 = users.getValueAt(userIndex);
User* technician = trackedUsers.getValueAt(userIndex).data;
if (!technician)
{
throw std::runtime_error("User not found: " + technicianID);
}
auto& bookings = m_dataStore.getServiceBookings();
auto& jobs = m_dataStore.getJobCards();
for (int iteratorOne = 0; iteratorOne < bookings.getSize(); iteratorOne++)
auto& trackedBookings = m_dataStore.getServiceBookings();
auto& trackedJobs = m_dataStore.getJobCards();
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)
{
continue;
@@ -385,8 +444,12 @@ void ServiceManagementService::cancelTechnicianJobs(const std::string& technicia
{
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 +463,8 @@ 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");
@@ -412,19 +477,19 @@ void ServiceManagementService::createComboPackage(const std::string& packageName
{
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++)
{
const std::string serviceid = serviceIDsInNewCombo[index];
if (servicesMap.find(serviceid) == -1)
const std::string& serviceid = serviceIDsInNewCombo[index];
if (trackedServicesMap.find(serviceid) == -1)
{
throw std::runtime_error("Service ID not found: " + serviceid);
}
}
auto& comboPackageMap = m_dataStore.getComboPackages();
for (int iterator = 0; iterator < comboPackageMap.getSize(); iterator++)
auto& trackedComboPackageMap = m_dataStore.getComboPackages();
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();
if (servicesInsideExistingCombos.getSize() == serviceIDsInNewCombo.getSize())
{
@@ -448,15 +513,16 @@ void ServiceManagementService::createComboPackage(const std::string& packageName
for (int iteratorOne = 0; iteratorOne < serviceIDsInNewCombo.getSize(); iteratorOne++)
{
const std::string& serviceId = serviceIDsInNewCombo[iteratorOne];
int serviceIndex = servicesMap.find(serviceId);
int serviceIndex = trackedServicesMap.find(serviceId);
if (serviceIndex == -1)
{
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);
comboPackageMap.insert(newComboPackage->getId(), newComboPackage);
trackedComboPackageMap.insert(newComboPackage->getId(), util::createNewRecord(newComboPackage));
m_dataStore.saveComboPackages();
}
/*
@@ -467,7 +533,11 @@ Return type: util::Map<std::string, ComboPackage*>
*/
util::Map<std::string, ComboPackage*> ServiceManagementService::getComboPackages()
{
return m_dataStore.getComboPackages();
AuthenticationManagementService::ensureAuthorization();
DataStoreLockGuard lock(m_dataStore);
util::Map<std::string, ComboPackage*> comboPackages;
comboPackages = util::getObjects(m_dataStore.getComboPackages());
return comboPackages;
}
/*
@@ -478,14 +548,18 @@ Return type: void
*/
void ServiceManagementService::removeComboPackage(const std::string& comboPackageID)
{
AuthenticationManagementService::ensureAuthorization();
DataStoreLockGuard lock(m_dataStore);
bool removed = false;
util::Map<std::string, ComboPackage*>& currentComboPackages = m_dataStore.getComboPackages();
for (int iterator = 0; iterator < currentComboPackages.getSize(); iterator++)
auto& trackedComboPackages = m_dataStore.getComboPackages();
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)
{
currentComboPackage->setState(util::State::INACTIVE);
comboPackage.state = RecordState::MODIFIED;
removed = true;
break;
}
@@ -494,6 +568,7 @@ void ServiceManagementService::removeComboPackage(const std::string& comboPackag
{
throw std::runtime_error("Combo package with ID '" + comboPackageID + "' not found.");
}
m_dataStore.saveComboPackages();
}
/*
@@ -506,7 +581,11 @@ Returns:
*/
util::Map<std::string, ServiceBooking*> ServiceManagementService::getServiceBookings()
{
return m_dataStore.getServiceBookings();
AuthenticationManagementService::ensureAuthorization();
DataStoreLockGuard lock(m_dataStore);
util::Map<std::string, ServiceBooking*> serviceBookings;
serviceBookings = util::getObjects(m_dataStore.getServiceBookings());
return serviceBookings;
}
/*
@@ -519,6 +598,7 @@ Returns:
*/
ServiceBooking* ServiceManagementService::getServiceBooking(const std::string& serviceID)
{
AuthenticationManagementService::ensureAuthorization();
auto currentServiceBookings = getServiceBookings();
for (int iterator = 0; iterator < currentServiceBookings.getSize(); iterator++)
{
@@ -545,13 +625,28 @@ Throws:
*/
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& currentJobCards = m_dataStore.getJobCards();
if (currentBooking == nullptr)
{
throw std::runtime_error("Service Booking not available");
}
std::string title, message;
if (currentBooking == nullptr)
{
throw std::runtime_error("Service Booking not available");
}
if (currentBooking->getStatus() == util::ServiceJobStatus::CANCELLED)
{
throw std::runtime_error("Cannot create job card. Service Booking was cancelled!");
}
auto& currentTrackedJobCards = m_dataStore.getJobCards();
auto& currentTrackedInventoryItems = m_dataStore.getInventoryItems();
auto& currentTrackedServiceBookings = m_dataStore.getServiceBookings();
int currentTrackedServiceBookingIndex = currentTrackedServiceBookings.find(bookingID);
if (currentTrackedServiceBookingIndex == -1)
{
throw std::runtime_error("Invalid service booking id.");
}
auto& currentTrackedServiceBooking = currentTrackedServiceBookings.getValueAt(currentTrackedServiceBookingIndex);
auto& currentServices = currentBooking->getServices();
if (currentServices.find(serviceID) == -1)
{
@@ -576,33 +671,54 @@ void ServiceManagementService::createJobCard(const std::string& bookingID, const
for (int iterator = 0; iterator < inventoryItems.getSize(); iterator++)
{
InventoryItem* currentInventoryItem = inventoryItems.getValueAt(iterator);
const std::string& currentInventoryItemId = inventoryItems.getKeyAt(iterator);
if (currentInventoryItem)
{
int trackedCurrentInventoryItemIndex = currentTrackedInventoryItems.find(currentInventoryItemId);
if (trackedCurrentInventoryItemIndex == -1)
{
throw std::runtime_error("Invalid inventory item index.");
}
auto& trackedCurrentInventoryItem = currentTrackedInventoryItems.getValueAt(trackedCurrentInventoryItemIndex);
int currentStockQuantity = currentInventoryItem->getQuantity();
currentInventoryItem->setQuantity(currentStockQuantity - 1);
trackedCurrentInventoryItem.state = RecordState::MODIFIED;
}
}
currentBooking->setAssignedTechnician(selectedTechnician);
currentBooking->setAssignedTechnicianId(selectedTechnician->getId());
const User* currentAssignedTechnician = currentBooking->getAssignedTechnician();
const std::string& currentAssignedTechnicianId = currentBooking->getAssignedTechnicianId();
if (!currentAssignedTechnician && currentAssignedTechnicianId.empty())
{
currentBooking->setAssignedTechnician(selectedTechnician);
currentBooking->setAssignedTechnicianId(selectedTechnician->getId());
title = "Technician assigned";
message = "A technician has been assigned to your Service Booking with ID " + bookingID;
sendNotification(currentBooking->getCustomer(), title, message);
}
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.";
currentTrackedServiceBooking.state = RecordState::MODIFIED;
JobCard* jobCard = Factory::getObject<JobCard>(bookingID, currentBooking, currentService, serviceID, technicianID, selectedTechnician, util::Timestamp(), util::ServiceJobStatus::STARTED, util::Timestamp());
if (jobCard)
{
currentJobCards.insert(jobCard->getId(), jobCard);
title = "Job Card Assigned";
message = "A new Job Card (ID: " + jobCard->getId() +
") has been created for Service " + serviceID +
" in Booking " + bookingID +
". You have been assigned to this job.";
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();
m_dataStore.saveServiceBookings();
m_dataStore.saveInventoryItems();
}
/*
@@ -620,15 +736,18 @@ Throws:
*/
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 inventoryItems = m_dataStore.getInventoryItems();
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 < 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)
{
itemFound = true;
@@ -646,12 +765,12 @@ void ServiceManagementService::createService(const std::string& name, const util
{
throw std::runtime_error("Unable to create new service.");
}
util::Map<std::string, Service*>& currentServices = m_dataStore.getServices();
if (currentServices.find(newService->getId()) != -1)
{
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 +783,11 @@ Returns:
*/
util::Map<std::string, Service*> ServiceManagementService::getServices()
{
return m_dataStore.getServices();
AuthenticationManagementService::ensureAuthorization();
DataStoreLockGuard lock(m_dataStore);
util::Map<std::string, Service*> services;
services = util::getObjects(m_dataStore.getServices());
return services;
}
/*
@@ -679,14 +802,20 @@ Throws:
*/
void ServiceManagementService::removeService(const std::string& serviceID)
{
util::Map<std::string, Service*>& currentServices = m_dataStore.getServices();
util::Map<std::string, ComboPackage*>& currentComboPackages = m_dataStore.getComboPackages();
if (currentServices.find(serviceID) != -1)
AuthenticationManagementService::ensureAuthorization();
DataStoreLockGuard lock(m_dataStore);
auto& currentTrackedServices = m_dataStore.getServices();
auto& currentTrackedComboPackages = m_dataStore.getComboPackages();
if (currentTrackedServices.find(serviceID) != -1)
{
currentServices.getValueAt(currentServices.find(serviceID))->setState(util::State::INACTIVE);
for (int iterator = 0; iterator < currentComboPackages.getSize(); iterator++)
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++)
{
ComboPackage* currentComboPackage = currentComboPackages.getValueAt(iterator);
comboPackageIndex = iterator;
ComboPackage* currentComboPackage = currentTrackedComboPackages.getValueAt(iterator).data;
if (currentComboPackage && currentComboPackage->getState() == util::State::ACTIVE)
{
util::Map<std::string, Service*> currentServices = currentComboPackage->getServices();
@@ -696,6 +825,7 @@ void ServiceManagementService::removeService(const std::string& serviceID)
if (currentService->getId() == serviceID)
{
currentComboPackage->setState(util::State::INACTIVE);
currentTrackedComboPackages.getValueAt(comboPackageIndex).state = RecordState::MODIFIED;
break;
}
}
@@ -706,6 +836,63 @@ void ServiceManagementService::removeService(const std::string& serviceID)
{
throw std::runtime_error("Service not found.");
}
m_dataStore.saveServices();
m_dataStore.saveComboPackages();
}
/*
Function: removeServiceBooking
Description: Removes a pending service booking by its ID.
Cancels only if status is PENDING, otherwise throws exceptions
for invalid states. Sends notification to the customer and
persists changes.
Parameter: const std::string& bookingID - ID of the service booking
Return type: void
*/
void ServiceManagementService::removeServiceBooking(const std::string& bookingID)
{
DataStoreLockGuard lock(m_dataStore);
auto& trackedServiceBookings = m_dataStore.getServiceBookings();
bool serviceBookingRemoved = false;
for (int iterator = 0; iterator < trackedServiceBookings.getSize(); iterator++)
{
auto& currentTrackedServiceBooking = trackedServiceBookings.getValueAt(iterator);
ServiceBooking* currentServiceBooking = currentTrackedServiceBooking.data;
if (currentServiceBooking && currentServiceBooking->getId() == bookingID)
{
if (currentServiceBooking->getStatus() == util::ServiceJobStatus::PENDING)
{
const std::string title = "Service Booking Cancelled";
const std::string message = "Service Booking (ID: " + bookingID + ") has been successfully cancelled";
currentServiceBooking->setStatus(util::ServiceJobStatus::CANCELLED);
currentTrackedServiceBooking.state = RecordState::MODIFIED;
serviceBookingRemoved = true;
sendNotification(currentServiceBooking->getCustomer(), title, message);
break;
}
else if(currentServiceBooking->getStatus() == util::ServiceJobStatus::COMPLETED)
{
throw std::runtime_error("Unable to cancel completed service booking.");
}
else if (currentServiceBooking->getStatus() == util::ServiceJobStatus::STARTED)
{
throw std::runtime_error("Unable to cancel started service booking.");
}
else if (currentServiceBooking->getStatus() == util::ServiceJobStatus::IN_PROGRESS)
{
throw std::runtime_error("Unable to cancel currently Inprogress service booking.");
}
else
{
throw std::runtime_error("Service Booking already cancelled.");
}
}
}
if (!serviceBookingRemoved)
{
throw std::runtime_error("Unable to cancel service booking.");
}
m_dataStore.saveServiceBookings();
}
/*
@@ -718,6 +905,7 @@ Returns:
*/
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)
@@ -744,11 +932,13 @@ Returns:
*/
util::Map<std::string, JobCard*> ServiceManagementService::getJobCards(const std::string& technicianID)
{
util::Map<std::string, JobCard*> jobCards = m_dataStore.getJobCards();
AuthenticationManagementService::ensureAuthorization();
DataStoreLockGuard lock(m_dataStore);
auto& trackedJobCards = m_dataStore.getJobCards();
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)
{
technicianJobCards.insert(currentJobCard->getId(), currentJobCard);
@@ -797,53 +987,68 @@ Returns:
*/
void ServiceManagementService::updateJobStatus(const std::string& jobID)
{
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.");
}
if (currentAssignedJobs.find(jobID) != -1)
{
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);
jobStatusUpdated = true;
}
AuthenticationManagementService::ensureAuthorization();
DataStoreLockGuard lock(m_dataStore);
AuthenticationManagementService authenticationManagementService;
PaymentManagementService paymentManagementService;
bool jobStatusUpdated = false, serviceBookingCompleted;
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.");
}
auto& trackedCurrentJob = trackedJobCards.getValueAt(jobIndex);
JobCard* currentJob = trackedCurrentJob.data;
if (currentJob == nullptr)
{
throw std::runtime_error("Unable to fetch current job.");
}
if (currentJob->getStatus() == util::ServiceJobStatus::STARTED)
{
currentJob->setStatus(util::ServiceJobStatus::IN_PROGRESS);
trackedCurrentJob.state = RecordState::MODIFIED;
jobStatusUpdated = true;
}
else if (currentJob->getStatus() == util::ServiceJobStatus::IN_PROGRESS)
{
currentJob->setStatus(util::ServiceJobStatus::COMPLETED);
trackedCurrentJob.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.";
std::string title = "Service Booking Completed";
std::string message = "Service Booking (ID: " + bookingId + ") has been completed successfully. An invoice has been 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.");
}
}
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();
}
@@ -35,6 +35,7 @@ public:
void createJobCard(const std::string& bookingID, const std::string& technicianID, const std::string& serviceID);
void createService(const std::string& name, const util::Vector<std::string>& inventoryItemIDs, double laborCost);
void removeService(const std::string& serviceID);
void removeServiceBooking(const std::string& bookingID);
util::Map<std::string, JobCard*> getJobCards(const std::string& technicianID);
void updateJobStatus(const std::string& jobID);
void cancelCustomerServiceBookings(const std::string& customerID);
@@ -15,6 +15,7 @@ Date:19-May-2026
#include "Notification.h"
#include "PaymentManagementService.h"
#include "ServiceManagementService.h"
#include "AuthenticationManagementService.h"
#include "User.h"
#include "UserManagementService.h"
#include "Vector.h"
@@ -22,6 +23,7 @@ Date:19-May-2026
#include "Utility.h"
#include "TrackedRecord.h"
#include "DataStoreLockGuard.h"
#include "EventManager.h"
/*
Function: ensureAdminExists
@@ -113,6 +115,7 @@ Return type: void
*/
void UserManagementService::updateUserDetails(const std::string& userID, const std::string& email, const std::string& phone)
{
AuthenticationManagementService::ensureAuthorization();
DataStoreLockGuard lock(m_dataStore);
auto& trackedUsersMap = m_dataStore.getUsers();
auto usersMap = util::getObjects(trackedUsersMap);
@@ -160,6 +163,7 @@ Throws:
*/
util::Vector<Notification*> UserManagementService::getUserNotifications(const std::string& userID)
{
AuthenticationManagementService::ensureAuthorization();
DataStoreLockGuard lock(m_dataStore);
auto& trackedUsersMap = m_dataStore.getUsers();
if (trackedUsersMap.find(userID) == -1)
@@ -203,6 +207,7 @@ Throws:
*/
void UserManagementService::deleteNotification(const std::string& notificationID, const std::string& userID)
{
AuthenticationManagementService::ensureAuthorization();
DataStoreLockGuard lock(m_dataStore);
auto& trackedUsersMap = m_dataStore.getUsers();
auto& trackedNotificationsMap = m_dataStore.getNotifications();
@@ -264,33 +269,42 @@ Return type: void
*/
void UserManagementService::removeUser(const std::string& userID)
{
AuthenticationManagementService::ensureAuthorization();
InventoryManagementService inventoryManagementService;
PaymentManagementService paymentManagementService;
ServiceManagementService serviceManagementService;
DataStoreLockGuard lock(m_dataStore);
auto& trackedUsersMap = m_dataStore.getUsers();
int index = trackedUsersMap.find(userID);
if (index != -1)
std::string removedUserID;
{
User* user = trackedUsersMap.getValueAt(index).data;
if (user != nullptr)
DataStoreLockGuard lock(m_dataStore);
auto& trackedUsersMap = m_dataStore.getUsers();
int index = trackedUsersMap.find(userID);
if (index != -1)
{
if (user->getUserType() == util::UserType::CUSTOMER)
User* user = trackedUsersMap.getValueAt(index).data;
if (user != nullptr)
{
serviceManagementService.cancelCustomerServiceBookings(userID);
if (user->getUserType() == util::UserType::CUSTOMER)
{
serviceManagementService.cancelCustomerServiceBookings(userID);
}
if (user->getUserType() == util::UserType::TECHNICIAN)
{
serviceManagementService.cancelTechnicianJobs(userID);
}
inventoryManagementService.detach(user);
paymentManagementService.detach(user);
serviceManagementService.detach(user);
user->setState(util::State::INACTIVE);
trackedUsersMap.getValueAt(index).state = RecordState::MODIFIED;
removedUserID = user->getId();
m_dataStore.saveUsers();
}
if (user->getUserType() == util::UserType::TECHNICIAN)
{
serviceManagementService.cancelTechnicianJobs(userID);
}
inventoryManagementService.detach(user);
paymentManagementService.detach(user);
serviceManagementService.detach(user);
user->setState(util::State::INACTIVE);
trackedUsersMap.getValueAt(index).state = RecordState::MODIFIED;
m_dataStore.saveUsers();
}
}
if (!removedUserID.empty())
{
EventManager::sendUserDisabledEvent(removedUserID);
}
}
/*
@@ -30,10 +30,16 @@ Return type: void
*/
void AdminMenu::showMenu()
{
startEventListener();
while (true)
{
try
{
if (!m_isMenuActive)
{
logout();
break;
}
int choice;
util::clear();
std::cout << "Admin Menu"
@@ -68,6 +74,7 @@ void AdminMenu::showMenu()
util::pressEnter();
}
}
stopEventListener();
}
/*
@@ -78,6 +85,11 @@ Return type: bool - true if menu continues, false if logout
*/
bool AdminMenu::handleOperation(int choice)
{
if (!m_isMenuActive)
{
logout();
return false;
}
switch (choice)
{
case 1:
@@ -141,6 +153,25 @@ bool AdminMenu::handleOperation(int choice)
return true;
}
/*
Function: handleNotificationEvent
Description: Retrieves and displays the latest notification for the
currently logged in admin.
Parameter: None
Return type: void
*/
void AdminMenu::handleNotificationEvent()
{
auto notifications = m_controller.getNotifications();
const User* authenticatedUser = m_controller.getAuthenticatedUser();
std::string name;
if (authenticatedUser)
{
name = authenticatedUser->getName();
}
displayNewNotification(notifications, name);
}
/*
Function: logout
Description: Logs out the currently authenticated admin user.
@@ -307,6 +338,23 @@ void AdminMenu::removeInventoryItem()
std::string selectedItemId = selectedItem->getId();
m_controller.removeInventoryItem(selectedItemId);
std::cout << "Item " << selectedItem->getPartName() << " removed successfully." << std::endl;
const util::Map<std::string, const Service*>& listOfService = m_controller.getServices();
for (int serviceIndex = 0; serviceIndex < listOfService.getSize(); serviceIndex++)
{
const Service* service = listOfService.getValueAt(serviceIndex);
if (!service)
{
continue;
}
const util::Map<std::string, InventoryItem*>& requiredItems = service->getRequiredInventoryItems();
if (requiredItems.find(selectedItemId) != -1)
{
m_controller.removeService(service->getId());
std::cout << "Service " << service->getName()
<< " removed as the item "
<< selectedItem->getPartName() << " required for the service has been removed." << std::endl;
}
}
}
}
util::pressEnter();
@@ -9,12 +9,13 @@ Date:19-May-2026
#pragma once
#include "Controller.h"
#include "Menu.h"
class AdminMenu
class AdminMenu : public Menu
{
private:
Controller m_controller;
bool handleOperation(int choice);
void handleNotificationEvent() override;
public:
void showMenu();
void logout();
@@ -32,10 +32,17 @@ Return type: void
*/
void CustomerMenu::showMenu()
{
startEventListener();
while (true)
{
try
{
if (!m_isMenuActive)
{
logout();
break;
}
int choice;
util::clear();
std::cout << "Customer Menu"
@@ -44,11 +51,12 @@ void CustomerMenu::showMenu()
<< "\n3. Update Profile"
<< "\n4. Change Password"
<< "\n5. View Service History"
<< "\n6. Complete Payments"
<< "\n7. View Invoices"
<< "\n8. View Notifications"
<< "\n9. Configure Notifications"
<< "\n10. Logout"
<< "\n6. Cancel Service Booking"
<< "\n7. Complete Payments"
<< "\n8. View Invoices"
<< "\n9. View Notifications"
<< "\n10. Configure Notifications"
<< "\n11. Logout"
<< "\nEnter a choice: ";
util::read(choice);
if (!handleOperation(choice))
@@ -62,6 +70,7 @@ void CustomerMenu::showMenu()
util::pressEnter();
}
}
stopEventListener();
}
/*
@@ -72,6 +81,11 @@ Return type: bool - true if menu continues, false if logout
*/
bool CustomerMenu::handleOperation(int choice)
{
if (!m_isMenuActive)
{
logout();
return false;
}
switch (choice)
{
case 1:
@@ -90,18 +104,21 @@ bool CustomerMenu::handleOperation(int choice)
viewServiceHistory();
break;
case 6:
completePayments();
cancelServiceBooking();
break;
case 7:
viewInvoices();
completePayments();
break;
case 8:
viewNotifications();
viewInvoices();
break;
case 9:
configureNotifications();
viewNotifications();
break;
case 10:
configureNotifications();
break;
case 11:
logout();
return false;
default:
@@ -111,6 +128,25 @@ bool CustomerMenu::handleOperation(int choice)
return true;
}
/*
Function: handleNotificationEvent
Description: Retrieves and displays the latest notification for the
currently logged in admin.
Parameter: None
Return type: void
*/
void CustomerMenu::handleNotificationEvent()
{
auto notifications = m_controller.getNotifications();
const User* authenticatedUser = m_controller.getAuthenticatedUser();
std::string name;
if (authenticatedUser)
{
name = authenticatedUser->getName();
}
displayNewNotification(notifications, name);
}
/*
Function: logout
Description: Logs out the currently authenticated customer user.
@@ -184,6 +220,13 @@ void CustomerMenu::selectService()
util::pressEnter();
return;
}
if (!verifyAllPaymentsCompleted(m_controller))
{
std::cout << "Your booking cannot be processed because you have pending payments for previous services. Please complete all outstanding invoices before booking a new service."
<< std::endl;
util::pressEnter();
return;
}
util::Vector<std::string> selectedServices;
const Service* selectedService = selectServiceFromServices(services);
if (selectedService == nullptr)
@@ -226,6 +269,13 @@ void CustomerMenu::selectComboPackage()
util::pressEnter();
return;
}
if (!verifyAllPaymentsCompleted(m_controller))
{
std::cout << "Your booking cannot be processed because you have pending payments for previous services. Please complete all outstanding invoices before booking a new combo package."
<< std::endl;
util::pressEnter();
return;
}
const ComboPackage* selectedComboPackage = selectComboPackageFromPackages(activeComboPackages);
if (selectedComboPackage == nullptr)
{
@@ -301,6 +351,46 @@ void CustomerMenu::viewServiceHistory()
util::pressEnter();
}
/*
Function: cancelServiceBooking
Description: Allows the customer to cancel a pending service booking.
Displays the list of active bookings, lets the user select one,
and removes it from the system. If no bookings are available,
an appropriate message is shown.
Parameter: None
Return type: void
*/
void CustomerMenu::cancelServiceBooking()
{
util::clear();
std::cout << "Cancel Service Booking\n";
const User* currentUser = m_controller.getAuthenticatedUser();
std::string currentUserID = currentUser->getId();
util::Map<std::string, const ServiceBooking*> serviceBookingsByCurrentUser = m_controller.getServiceBookingsByUser(currentUserID);
util::Map<int, const ServiceBooking*> serviceBookingsMap;
auto currentPendingServiceBookings = filterActiveServiceBookings(serviceBookingsByCurrentUser);
int bookingsSize = currentPendingServiceBookings.getSize();
if (listServiceBookings(currentPendingServiceBookings, bookingsSize, serviceBookingsMap))
{
const ServiceBooking* selectedService = selectPendingServiceBookings(serviceBookingsMap);
if (selectedService)
{
m_controller.removeServiceBooking(selectedService->getId());
std::cout << "Cancelled Service booking of id " + selectedService->getId() << std::endl << std::endl;
}
else
{
std::cout << "Invalid service booking index.\n\n";
return;
}
}
else
{
std::cout << "No pending service bookings available.\n\n";
}
util::pressEnter();
}
/*
Function: completePayments
Description: Allows the customer to complete pending payments for invoices.
@@ -9,13 +9,14 @@ Date:19-May-2026
*/
#pragma once
#include "Menu.h"
#include "Controller.h"
class CustomerMenu
class CustomerMenu : public Menu
{
private:
Controller m_controller;
bool handleOperation(int choice);
void handleNotificationEvent();
public:
void showMenu();
void logout();
@@ -27,5 +28,6 @@ public:
void completePayments();
void viewInvoices();
void viewNotifications();
void cancelServiceBooking();
void configureNotifications();
};
};
@@ -0,0 +1,150 @@
/*
File: Menu.cpp
Description: Implementation file containing common menu event listener
functionality, account disable handling, and notification
event dispatching for all menu types.
Author: Trenser
Date:16-Jun-2026
*/
#include "Menu.h"
/*
Function: Menu
Description: Constructs a Menu object and initializes event handles
and menu state.
Parameter: None
Return type: None
*/
Menu::Menu()
:
m_isMenuActive(false),
m_accountDisabledEvent(NULL),
m_notificationAvailableEvent(NULL),
m_shutdownEvent(NULL) {}
/*
Function: ~Menu
Description: Destroys the Menu object and performs event listener
cleanup.
Parameter: None
Return type: None
*/
Menu::~Menu()
{
stopEventListener();
}
/*
Function: startEventListener
Description: Creates menu event handles, registers them with the
authentication service, and starts the event listener
thread.
Parameter: None
Return type: void
*/
void Menu::startEventListener()
{
if (m_isMenuActive.load())
{
return;
}
m_isMenuActive.store(true);
m_accountDisabledEvent = CreateEventA(NULL, FALSE, FALSE, NULL);
m_notificationAvailableEvent = CreateEventA(NULL, FALSE, FALSE, NULL);
m_shutdownEvent = CreateEventA(NULL, FALSE, FALSE, NULL);
m_controller.registerEvents(m_accountDisabledEvent, m_notificationAvailableEvent);
m_eventListenerThread = std::thread(&Menu::eventListenerLoop, this);
}
/*
Function: eventListenerLoop
Description: Waits for account disabled, notification available,
and shutdown events and dispatches them to the
appropriate handlers.
Parameter: None
Return type: void
*/
void Menu::eventListenerLoop()
{
HANDLE handles[3] = { NULL, NULL, NULL };
handles[0] = m_accountDisabledEvent;
handles[1] = m_notificationAvailableEvent;
handles[2] = m_shutdownEvent;
while (m_isMenuActive.load())
{
DWORD result = WaitForMultipleObjects(3, handles, FALSE, INFINITE);
switch (result)
{
case WAIT_OBJECT_0:
handleAccountDisabledEvent();
break;
case WAIT_OBJECT_0 + 1:
handleNotificationEvent();
break;
case WAIT_OBJECT_0 + 2:
return;
}
}
}
/*
Function: stopEventListener
Description: Stops the event listener thread and releases all
associated event handles.
Parameter: None
Return type: void
*/
void Menu::stopEventListener()
{
m_isMenuActive.store(false);
if (m_shutdownEvent)
{
SetEvent(m_shutdownEvent);
}
if (m_eventListenerThread.joinable())
{
m_eventListenerThread.join();
}
if (m_accountDisabledEvent)
{
CloseHandle(m_accountDisabledEvent);
}
if (m_notificationAvailableEvent)
{
CloseHandle(m_notificationAvailableEvent);
}
if (m_shutdownEvent)
{
CloseHandle(m_shutdownEvent);
}
m_accountDisabledEvent = NULL;
m_notificationAvailableEvent = NULL;
m_shutdownEvent = NULL;
}
/*
Function: handleAccountDisabledEvent
Description: Handles an account disabled event by marking the menu
inactive and notifying the user.
Parameter: None
Return type: void
*/
void Menu::handleAccountDisabledEvent()
{
m_isMenuActive.store(false);
const User* authenticatedUser = m_controller.getAuthenticatedUser();
std::string messageTitle = "Account Disabled";
if (authenticatedUser)
{
messageTitle += " - " + authenticatedUser->getName();
}
MessageBoxA(
GetConsoleWindow(),
"Your account has been disabled.",
messageTitle.c_str(),
MB_OK |
MB_ICONWARNING |
MB_SETFOREGROUND |
MB_TOPMOST);
}
@@ -0,0 +1,33 @@
/*
File: Menu.h
Description: Base class providing common event listener functionality
for all menu implementations.
Author: Trenser
Date:16-Jun-2026
*/
#pragma once
#include <windows.h>
#include <atomic>
#include <thread>
#include "Controller.h"
class Menu
{
protected:
Controller m_controller;
std::atomic<bool> m_isMenuActive;
HANDLE m_accountDisabledEvent;
HANDLE m_notificationAvailableEvent;
HANDLE m_shutdownEvent;
std::thread m_eventListenerThread;
void startEventListener();
void stopEventListener();
void eventListenerLoop();
void handleAccountDisabledEvent();
virtual void handleNotificationEvent() = 0;
public:
Menu();
virtual ~Menu();
};
@@ -28,6 +28,7 @@ Date: 21-May-2026
#include "Utility.h"
#include "Validator.h"
#include "Vector.h"
#include "StringHelper.h"
/*
Function: displayAllServices
@@ -587,7 +588,7 @@ inline void displayInvoices(util::Map<std::string, const Invoice*> currentUserIn
<< util::getPaymentStatusString(selectedInvoice->getStatus()) << std::endl;
std::cout << std::left << std::setw(20) << "Payment Mode:"
<< util::getPaymentModeString(selectedInvoice->getPaymentMethod()) << std::endl;
auto inventoryItemsInInvoice = selectedInvoice->getParts();
auto& inventoryItemsInInvoice = selectedInvoice->getParts();
if (inventoryItemsInInvoice.isEmpty())
{
std::cout << "No inventory items used.\n\n";
@@ -596,7 +597,6 @@ inline void displayInvoices(util::Map<std::string, const Invoice*> currentUserIn
std::cout << "\nItems Used:\n";
std::cout << std::left
<< std::setw(20) << "ItemName"
<< std::setw(10) << "Quantity"
<< std::setw(10) << "Price"
<< std::endl;
std::cout << std::string(40, '-') << std::endl;
@@ -605,7 +605,6 @@ inline void displayInvoices(util::Map<std::string, const Invoice*> currentUserIn
InventoryItem* currentItem = inventoryItemsInInvoice.getValueAt(iterator);
std::cout << std::left
<< std::setw(20) << currentItem->getPartName()
<< std::setw(10) << currentItem->getQuantity()
<< std::setw(10) << currentItem->getPrice()
<< std::endl;
}
@@ -1056,6 +1055,21 @@ inline const Service* selectServiceFromServices(const util::Map<std::string, con
{
continue;
}
bool hasDepletedItem = false;
const util::Map<std::string, InventoryItem*>& requiredItems = currentService->getRequiredInventoryItems();
for (int itemIndex = 0; itemIndex < requiredItems.getSize(); itemIndex++)
{
const InventoryItem* item = requiredItems.getValueAt(itemIndex);
if (!item || item->getQuantity() < 1)
{
hasDepletedItem = true;
break;
}
}
if (hasDepletedItem)
{
continue;
}
activeServicesMap.insert(currentIndex, currentService);
double partsCost = util::calculatePartsCost(currentService);
std::cout << std::left
@@ -1130,15 +1144,18 @@ inline void displayAllComboPackages(util::Map<std::string, const ComboPackage*>
for (int index = 0; index < comboPackages.getSize(); index++)
{
const ComboPackage* currentComboPackage = comboPackages.getValueAt(index);
if (currentComboPackage && currentComboPackage->getState() != util::State::ACTIVE)
if (currentComboPackage)
{
continue;
if (currentComboPackage->getState() != util::State::ACTIVE)
{
continue;
}
std::cout << std::left
<< std::setw(15) << currentComboPackage->getId()
<< std::setw(35) << util::truncateString(currentComboPackage->getPackageName(), 30)
<< std::setw(15) << util::calculateComboServiceEstimatedCost(currentComboPackage)
<< std::endl;
}
std::cout << std::left
<< std::setw(15) << currentComboPackage->getId()
<< std::setw(35) << util::truncateString(currentComboPackage->getPackageName(), 30)
<< std::setw(15) << util::calculateComboServiceEstimatedCost(currentComboPackage)
<< std::endl;
}
}
@@ -1164,18 +1181,21 @@ inline const ComboPackage* selectComboPackageFromPackages(const util::Map<std::s
for (int index = 0; index < comboPackages.getSize(); index++)
{
const ComboPackage* currentComboPackage = comboPackages.getValueAt(index);
if (currentComboPackage && currentComboPackage->getState() != util::State::ACTIVE)
if (currentComboPackage)
{
continue;
if (currentComboPackage->getState() != util::State::ACTIVE)
{
continue;
}
activeComboPackages.insert(currentIndex, currentComboPackage);
std::cout << std::left
<< std::setw(10) << currentIndex
<< std::setw(15) << currentComboPackage->getId()
<< std::setw(35) << util::truncateString(currentComboPackage->getPackageName(), 30)
<< std::setw(15) << util::calculateComboServiceEstimatedCost(currentComboPackage)
<< std::endl;
currentIndex++;
}
activeComboPackages.insert(currentIndex, currentComboPackage);
std::cout << std::left
<< std::setw(10) << currentIndex
<< std::setw(15) << currentComboPackage->getId()
<< std::setw(35) << util::truncateString(currentComboPackage->getPackageName(), 30)
<< std::setw(15) << util::calculateComboServiceEstimatedCost(currentComboPackage)
<< std::endl;
currentIndex++;
}
if (activeComboPackages.getSize() == 0)
{
@@ -1407,4 +1427,89 @@ inline std::string selectComboPackage(util::Map<std::string, const ComboPackage*
std::cout << "Enter a valid choice.\n";
return "";
}
}
}
/*
Function: displayNewNotification
Description: Displays the most recent notification from the supplied
notification collection.
Parameter: util::Vector<const Notification*> notifications -
collection of notifications
const std::string& - The name of the user currently authenticated with the system
Return type: void
*/
inline void displayNewNotification(util::Vector<const Notification*> notifications, const std::string& name)
{
const Notification* notification = nullptr;
size_t numberOfNotifications = notifications.getSize();
for (int index = 0; index < numberOfNotifications; index++)
{
if (!notification)
{
notification = notifications[index];
}
else
{
if (util::extractNumber(notification->getId()) < util::extractNumber(notifications[index]->getId()))
{
notification = notifications[index];
}
}
}
if (notification)
{
std::string messageTitle = notification->getTitle();
if (!name.empty())
{
messageTitle += " - " + name;
}
MessageBoxA(
GetConsoleWindow(),
notification->getMessage().c_str(),
messageTitle.c_str(),
MB_OK |
MB_ICONINFORMATION);
}
}
/*
Function: verifyAllPaymentsCompleted
Description: Checks whether the authenticated customer has completed
all payments for their invoices. Iterates through all
invoices belonging to the customer and verifies that
each invoice has a payment status of COMPLETED.
Parameters: Controller& m_controller -
reference to the Controller object used to access
authenticated user and invoice data
Return type: bool
true if all invoices for the authenticated customer
are completed, false if any invoice is pending or not completed
Throws: std::runtime_error if no authenticated user is found
*/
inline bool verifyAllPaymentsCompleted(Controller& m_controller)
{
const User* authenticatedUser = m_controller.getAuthenticatedUser();
if (!authenticatedUser)
{
throw std::runtime_error("No authenticated user found.");
}
const std::string& authenticatedUserId = authenticatedUser->getId();
util::Map<std::string, const Invoice*> listOfInvoices = m_controller.getAllInvoices();
for (int invoiceIndex = 0; invoiceIndex < listOfInvoices.getSize(); ++invoiceIndex)
{
const Invoice* invoice = listOfInvoices.getValueAt(invoiceIndex);
if (!invoice)
{
continue;
}
const std::string& customerId = invoice->getBooking()->getCustomerId();
if (customerId == authenticatedUserId)
{
if (invoice->getStatus() != util::PaymentStatus::COMPLETED)
{
return false;
}
}
}
return true;
}
@@ -27,10 +27,16 @@ Returns:
*/
void TechnicianMenu::showMenu()
{
startEventListener();
while (true)
{
try
{
if (!m_isMenuActive)
{
logout();
break;
}
int choice;
util::clear();
std::cout << "Technician Menu"
@@ -52,6 +58,7 @@ void TechnicianMenu::showMenu()
util::pressEnter();
}
}
stopEventListener();
}
/*
@@ -62,6 +69,11 @@ Return type: bool - true if menu continues, false if logout
*/
bool TechnicianMenu::handleOperation(int choice)
{
if (!m_isMenuActive)
{
logout();
return false;
}
switch (choice)
{
case 1:
@@ -86,6 +98,25 @@ bool TechnicianMenu::handleOperation(int choice)
return true;
}
/*
Function: handleNotificationEvent
Description: Retrieves and displays the latest notification for the
currently logged in admin.
Parameter: None
Return type: void
*/
void TechnicianMenu::handleNotificationEvent()
{
auto notifications = m_controller.getNotifications();
const User* authenticatedUser = m_controller.getAuthenticatedUser();
std::string name;
if (authenticatedUser)
{
name = authenticatedUser->getName();
}
displayNewNotification(notifications, name);
}
/*
Function: displayJobs
Description: Displays all Jobs assigned to a Technician
@@ -9,12 +9,13 @@ Date:19-May-2026
#pragma once
#include "Controller.h"
#include "Menu.h"
class TechnicianMenu
class TechnicianMenu : public Menu
{
private:
Controller m_controller;
bool handleOperation(int choice);
void handleNotificationEvent();
public:
void showMenu();
void displayJobs();