Compare commits

...

30 Commits

Author SHA1 Message Date
joelthomastrenser f484c62a1e Fix cache refresh handling for unsaved records
- Preserve NEW_RECORD entries during cache refresh
- Prevent accidental deletion of pending datastore changes
- Clean up only stale persisted records
2026-06-15 15:16:56 +05:30
joelthomastrenser 404d217504 Clean up legacy code
- Remove FileManager and related file-based persistence logic
- Remove obsolete observer persistence utilities
- Remove unused service APIs and includes
- Delete duplicate DataStore mutex stubs
- Perform general dead-code cleanup
2026-06-15 15:16:55 +05:30
joelthomastrenser 67ac7f6625 Implement Service Refactoring
<UserStory> 1954: Implement Service Refactoring </UserStory>

UserStory #1954

<Changes>
1. Refactored notification handling to persist notifications directly in the datastore instead of maintaining notification collections within User objects.
2. Removed recipient User pointer dependencies from Notification and retained recipient user identification through recipientUserId.
3. Implemented generic observer persistence support in DataStore with shared helper methods for loading and saving observer subscriptions.
4. Added datastore-backed observer management for ServiceManagementService, PaymentManagementService, and InventoryManagementService.
5. Updated attach() and detach() operations to load, modify, and persist observer subscriptions using shared memory mappings.
6. Refactored sendNotification() implementations to create and persist Notification records directly to the datastore for subscribed observers.
7. Updated UserManagementService notification retrieval and deletion logic to operate on datastore notification records filtered by recipient user ID.
8. Removed notification ownership and observer-specific notification APIs from User and Observer classes.
9. Added configurable shared memory growth factor support and updated mapping expansion logic to use centralized configuration values.
10. Removed obsolete NotificationManagementService implementation and updated project configuration references.
11. Added DataStoreLockGuard integration for observer and notification persistence operations to ensure synchronized datastore access.
</Changes>

<Test>
N/A
</Test>

<Review>
Sreeja Reghukumar, please review
</Review>
2026-06-15 15:16:55 +05:30
joelthomastrenser 5f4ee72ffe Implement Notification Model Refactoring
<UserStory> 1953: Model Refactoring </UserStory>

UserStory #1953

<Changes>
1. Replaced CSV-based Notification serialization and deserialization with SerializedNotification record-based serialization for shared memory storage.
2. Implemented Notification::serialize() to convert Notification objects into fixed-size SerializedNotification structures.
3. Implemented Notification::deserialize() to reconstruct Notification objects directly from SerializedNotification records.
4. Added Notification state persistence by introducing util::State support in constructors, serialization, and deserialization flows.
5. Updated Notification class interfaces to use SerializedNotification types instead of std::string serialization APIs.
6. Removed legacy CSV serialization support, including CSV parsing logic and header generation functionality.
7. Added SerializedNotification dependencies through SerializedRecords.h inclusion and forward declaration support.
8. Initialized Notification objects with ACTIVE state by default and added state getter/setter APIs.
</Changes>

<Test>
N/A
</Test>

<Review>
Sreeja Reghukumar, please review
</Review>
2026-06-15 15:16:55 +05:30
joelthomastrenser 1a4821ea8a Merged PR 1166: User Management Refactoring - 1925
User Story #1959
User Story #1960

**Changes**
- Refactored User serialization/deserialization to use SerializedUser.
- Removed user loading and saving responsibilities from UserManagementService.
- Added DataStoreLockGuard for automatic datastore locking/unlocking.
- Updated user operations to work with tracked records.
- Added persistence support for user creation, updates, notification changes, and user removal.
- Refactored notification handling to use datastore-managed notifications.
- Updated DataStore to load and save users and notifications using shared memory records.
- Reworked application startup and shutdown flow using Controller::initialize() and Controller::shutdown().
- Updated UI flow to use the new initialization and shutdown methods.
- Added required project and shared memory updates to support the refactoring.

Related work items: #1925, #1959, #1960
2026-06-15 15:16:15 +05:30
joelthomastrenser b5c8b1ee9b Implement review fixes 2026-06-15 14:46:37 +05:30
joelthomastrenser 89fc662181 Implement Service Refactoring
<UserStory> 1960: Service Refactoring </UserStory>

UserStory #1960

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

<Test>
N/A
</Test>

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

UserStory #1959

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

<Test>
N/A
</Test>

<Review>
Sreeja Reghukumar, please review
</Review>
2026-06-12 03:19:43 +05:30
joelthomastrenser 3b1f3301d6 Implemented datastore shared memory codebase
Changes:
- Added SharedMemory module for file-backed memory-mapped storage
- Added MappingInfo, FileHeader, RecordState and TrackedRecord infrastructure
- Replaced CSV-based serialization with binary struct serialization
- Added DataStore initialization and shutdown lifecycle management
- Added datastore mutex synchronization for multi-process access
- Added shared-memory mapping configuration for all datastore entities
- Added generic loadRecords and saveRecords template infrastructure
- Added automatic datastore directory creation and .dat file storage
- Updated configuration to use binary datastore files and initial capacities
- Added enum underlying types for serialization compatibility
- Added password masking support for login, registration and password change flows
- Added Visual Studio project configuration for shared-memory components
- Added datastore-owned record caches for runtime object management
- Updated datastore APIs to return cached tracked-record collections by reference
- Added generic cache refresh and cleanup infrastructure
- Updated save operations to persist datastore caches directly
- Added automatic cache cleanup during datastore destruction
- Prepared datastore for multi-process shared-memory persistence
2026-06-12 03:18:15 +05:30
Avinash Rajesh 602b538830 Merged PR 1157: Fix Duplicate Customer Notification Sent When Assigned Technician Is Removed
Fix Duplicate Customer Notification Sent When Assigned Technician Is Removed

- Refactored processBookingCancellation to simplify parameters and remove redundant notification arguments.
- Added util::UserType parameter to differentiate cancellation flows for CUSTOMER vs TECHNICIAN.
- Updated cancelCustomerServiceBookings to use processBookingCancellation with util::UserType::CUSTOMER.
- Updated cancelTechnicianJobs to use processBookingCancellation with util::UserType::TECHNICIAN.
- Enhanced booking status handling by including IN_PROGRESS status in cancellation checks.
- Ensured job cards are consistently marked CANCELLED and inventory restored.
- Fixed duplicate notification issue where customers received multiple alerts when technician was removed.

Fixes #1807

Related work items: #1807
2026-06-01 18:15:36 +05:30
Jissin Mathew 86873d2a21 Merged PR 1158: Fix Technician Job Status Update Screen UI and Formatting Issues
Fix Technician Job Status Update Screen UI and Formatting Issues

- Corrected inconsistent status label formatting: replaced "Inprogress" with "In Progress" in TechnicianMenu and MenuHelper.
- Updated headings in selectJobCardToUpdate to clearer phrasing:
  - "Select a job to mark as In Progress"
  - "Select a job to mark as Completed".
- Added spacing before and after the "No jobs available" message to improve readability and provide clear separation from headings.
- Replaced duplicated prompt "Select the Job Card to Update (Index):" with concise "Enter the job index to update:".
- Improved TechnicianMenu option display to show "In Progress" instead of "Inprogress".

Related work items: #1808
2026-06-01 18:13:34 +05:30
Avinash Rajesh c64f3cff72 Implemented Review Fixes 2026-06-01 18:13:11 +05:30
Jissin Mathew ce50467816 Fix Technician Job Status Update Screen UI and Formatting Issues
- Corrected inconsistent status label formatting: replaced "Inprogress" with "In Progress" in TechnicianMenu and MenuHelper.
- Updated headings in selectJobCardToUpdate to clearer phrasing:
  - "Select a job to mark as In Progress"
  - "Select a job to mark as Completed".
- Added spacing before and after the "No jobs available" message to improve readability and provide clear separation from headings.
- Replaced duplicated prompt "Select the Job Card to Update (Index):" with concise "Enter the job index to update:".
- Improved TechnicianMenu option display to show "In Progress" instead of "Inprogress".

Fixes #1808
2026-06-01 18:02:45 +05:30
Avinash Rajesh 20475ace73 Fix Duplicate Customer Notification Sent When Assigned Technician Is Removed
- Refactored processBookingCancellation to simplify parameters and remove redundant notification arguments.
- Added util::UserType parameter to differentiate cancellation flows for CUSTOMER vs TECHNICIAN.
- Updated cancelCustomerServiceBookings to use processBookingCancellation with util::UserType::CUSTOMER.
- Updated cancelTechnicianJobs to use processBookingCancellation with util::UserType::TECHNICIAN.
- Enhanced booking status handling by including IN_PROGRESS status in cancellation checks.
- Ensured job cards are consistently marked CANCELLED and inventory restored.
- Fixed duplicate notification issue where customers received multiple alerts when technician was removed.

Fixes #1807
2026-06-01 17:46:55 +05:30
joelthomastrenser 1e63b900ab Merged PR 1156: Fix: Prevent duplicate usernames across all user states
Fix: Prevent duplicate usernames across all user states

Changes:
- Updated username duplicate validation to consider all existing users.
- Prevented reuse of usernames belonging to deleted/disabled accounts.
- Fixed authentication conflicts caused by duplicate usernames.

Fixes #1809

Related work items: #1809
2026-06-01 17:44:49 +05:30
joelthomastrenser dd29c7324f Fix: Prevent duplicate usernames across all user states
Changes:
- Updated username duplicate validation to consider all existing users.
- Prevented reuse of usernames belonging to deleted/disabled accounts.
- Fixed authentication conflicts caused by duplicate usernames.

Fixes #1809
2026-06-01 17:40:27 +05:30
joelthomastrenser 17f24b7733 Merged PR 1155: Implement Confirm Payment Functionality
- Added Controller::getAllInvoices – retrieves all invoices from PaymentManagementService and returns them as a read-only map
- Implemented Controller::confirmPayment – delegates payment confirmation for a given invoice ID to PaymentManagementService
- Introduced PaymentManagementService::getAllInvoice – provides access to all invoices stored in the datastore
- Added PaymentManagementService::confirmPayment – confirms payment for a specific invoice, updates payment date and status, and sends notification
- Extended util::PaymentStatus enum – added PAID status and updated string conversion
- Integrated AdminMenu::confirmPayment – validates invoice list, filters by status, allows selection, and confirms payment
- Updated CustomerMenu::completePayments – uses parameterized status filtering for invoice selection
- Enhanced MenuHelper::selectInvoiceFromUserForPayment – accepts requiredStatus parameter for flexible filtering
- Adjusted AdminMenu options – added "Confirm Payment" before Logout

Related work items: #1797
2026-06-01 17:34:41 +05:30
Avinash Rajesh 1032fc64bd Commit aee6356e: Implement Confirm Payment Functionality
<User Story> Complete Payments - 1797</User Story>

<Changes>
1. Added Controller::getAllInvoices
   - Retrieves all invoices from PaymentManagementService and returns them as a read-only map.
2. Implemented Controller::confirmPayment
   - Delegates payment confirmation for a given invoice ID to PaymentManagementService.
3. Introduced PaymentManagementService::getAllInvoice
   - Provides access to all invoices stored in the datastore.
4. Added PaymentManagementService::confirmPayment
   - Confirms payment for a specific invoice, updates payment date and status, and sends notification.
5. Extended util::PaymentStatus enum
   - Added PAID status and updated string conversion.
6. Integrated AdminMenu::confirmPayment
   - Validates invoice list, filters by status, allows selection, and confirms payment.
7. Updated CustomerMenu::completePayments
   - Uses parameterized status filtering for invoice selection.
8. Enhanced MenuHelper::selectInvoiceFromUserForPayment
   - Accepts requiredStatus parameter for flexible filtering.
9. Adjusted AdminMenu options
   - Added "Confirm Payment" before Logout.
</Changes>

<Test>
Acceptance Criteria:
1. Admin selects "Confirm Payment" from menu.
   - Verify system prompts and displays invoices filtered by status.
2. Admin selects invoice with status = PAID.
   - Verify payment confirmation updates date, sets status, and sends notification.
3. Admin attempts confirmation with empty invoice list.
   - Verify error message: "No pending invoices available for confirmation."
4. Customer completes payment.
   - Verify selection uses util::PaymentStatus::PENDING and payment flow works correctly.
5. Invalid invoice ID entered.
   - Verify system throws runtime_error with "Payment failed: invalid invoice ID."

Precondition:
1. Admin logged into system.
2. At least one invoice exists in datastore.
3. Notification system available.

Steps:
1. Navigate to Admin menu → Confirm Payment.
2. Select invoice with PAID status.
3. Confirm payment and check notification.
4. Attempt with empty invoice list.
5. Attempt with invalid invoice ID.
</Test>

<Review>
Sreeja Reghukumar
</Review>
2026-06-01 17:30:27 +05:30
joelthomastrenser cfd1a2b675 Merged PR 1154: Implement Update Job Status for technician
- Renamed Controller and ServiceManagementService methods from completeJob to updateJobStatus for clarity and flexibility.
- Enhanced ServiceManagementService::updateJobStatus to support transitions:
- STARTED → INPROGRESS
- INPROGRESS → COMPLETED (with invoice generation and customer notification).
- Added INPROGRESS state to ServiceJobStatus enum and updated string conversion utilities.
- Introduced filterJobCards helper to generalize job filtering by status.
- Updated TechnicianMenu to allow technicians to select job type (Started/Inprogress) and update status accordingly.
- Improved job display to show current status and truncated service names for readability.

Related work items: #1798
2026-06-01 17:25:28 +05:30
Jissin Mathew 70ec47df04 Fix review comments 2026-06-01 16:49:57 +05:30
Jissin Mathew 2ea77bf9b6 Implement Update Job Status for technician
<UserStory> SER1798: Update Job Status </UserStory>

<Changes>
    1. Renamed Controller and ServiceManagementService methods from completeJob to updateJobStatus for clarity and flexibility.
    2. Enhanced ServiceManagementService::updateJobStatus to support transitions:
       - STARTED → INPROGRESS
       - INPROGRESS → COMPLETED (with invoice generation and customer notification).
    3. Added INPROGRESS state to ServiceJobStatus enum and updated string conversion utilities.
    4. Introduced filterJobCards helper to generalize job filtering by status.
    5. Updated TechnicianMenu to allow technicians to select job type (Started/Inprogress) and update status accordingly.
    6. Improved job display to show current status and truncated service names for readability.
</Changes>

<Test>

 Acceptance Criteria:
 1. Technician can select a job with status STARTED and update it to INPROGRESS.
 2. Technician can select a job with status INPROGRESS and update it to COMPLETED.
 3. Completed bookings automatically generate invoices and send notifications to customers.
 4. Job status updates are reflected in the technician’s job list and customer view.

 Precondition:
 1. Technician is logged into the system.
 2. Assigned job cards exist with valid statuses (STARTED or INPROGRESS).
 3. Datastore and payment service are available.

 Steps:
 1. Navigate to Technician menu and choose "Update Job Status".
    - Verify that the system prompts for job type selection (Started/Inprogress).
 2. Select a job card from the filtered list.
    - Verify that the job card details are displayed with status.
 3. Confirm update.
    - Verify that the job status changes correctly.
 4. For jobs updated to COMPLETED:
    - Verify that the booking status is updated, invoice is generated, and notification is sent.
</Test>

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

#1798
2026-06-01 16:49:56 +05:30
joelthomastrenser 972e353832 Merged PR 1152: Vehicle Service System v1.0.0.0
Features Included:

Secure access and session management with role-based authentication
Customer and technician account management
Service booking and technician assignment
Service and combo package management
Job card tracking and completion workflow
Inventory management and stock monitoring
Invoice generation and payment processing
Notification management with configurable preferences
Observer pattern based alerts for inventory, payments, and service updates
2026-06-01 16:48:25 +05:30
joelthomastrenser c5ada405e6 Merged PR 1148: Vehicle Service System
**Features Included:**
- Secure access and session management with role-based authentication
- Customer and technician account management
- Service booking and technician assignment
- Service and combo package management
- Job card tracking and completion workflow
- Inventory management and stock monitoring
- Invoice generation and payment processing
- Notification management with configurable preferences
- Observer pattern based alerts for inventory, payments, and service updates

Related work items: #1551, #1552, #1553, #1560, #1561, #1563, #1564, #1568, #1569, #1570, #1571, #1572, #1573, #1574, #1575, #1576, #1577, #1578, #1579, #1580, #1581, #1582, #1583, #1584, #1585, #1586, #1587, #1588, #1592, #1593, #1594, #1595, #1596, #1597, #1598, #1599, #1600, #1601, #1624, #1626, #1646, #1648, #1649, #1655, #1656, #1668, #1669, #1672, #1673, #1679, #1680, #1708, #1709, #1736, #1737, #1738, #1739, #1740, #1741, #1742, #1743, #1744, #1745, #1746, #1747, #1748, #1749, #1750, #1751, #1752, #1753, #1754, #1777, #1778, #1779, #1780, #1781, #1782, #1783, #1784, #1786, #1788, #1789, #1793
2026-05-29 11:50:03 +05:30
joelthomastrenser d6b4310de6 Merged PR 1142: Add display menus for users, services, combo packages and jobs
Add display menus for users, services, combo packages and jobs

Changes:
- Added display options for users, services, combo packages and technician jobs
- Updated admin and technician menu options and navigation
- Added reusable helper functions for displaying data in tabular format
- Improved user and combo package listing displays
- Fixed minor validation and error message formatting issues
2026-05-28 16:39:37 +05:30
joelthomastrenser 451085e9c2 Add display menus for users, services, combo packages and jobs
Changes:
- Added display options for users, services, combo packages and technician jobs
- Updated admin and technician menu options and navigation
- Added reusable helper functions for displaying data in tabular format
- Improved user and combo package listing displays
- Fixed minor validation and error message formatting issues
2026-05-28 16:36:34 +05:30
joelthomastrenser 4657d3e8d1 Merged PR 1139: Fix: create missing directories before file creation
Fix: create missing directories before file creation

Changes:
- Added ensureDirectoryExists() helper using _mkdir()
- Automatically create missing directories before file operations
- Added FileHelper include in FileManager
- Removed placeholder files/README.md

Fixes #1793

Related work items: #1646, #1793
2026-05-28 10:35:54 +05:30
joelthomastrenser b1b6125d88 Fix: create missing directories before file creation
Changes:
- Added ensureDirectoryExists() helper using _mkdir()
- Automatically create missing directories before file operations
- Added FileHelper include in FileManager
- Removed placeholder files/README.md

Fixes #1793
2026-05-28 10:32:36 +05:30
joelthomastrenser e739ec6ee2 Merged PR 1134: Fix Invoice Deserialization Failure for PaymentMode::NOTSET
Changes:

- Added support for PaymentMode::NOTSET in payment mode string-to-enum conversion.
- Improved invoice table column labels and spacing for better readability in invoice display screens.

Fixes #1789

Related work items: #1646, #1789
2026-05-27 19:51:59 +05:30
joelthomastrenser f78e02ed3d Fix: handle NOTSET payment mode during invoice deserialization
Changes:

- Added support for PaymentMode::NOTSET in string-to-enum conversion
  to prevent invoice deserialization failures during system startup
- Improved invoice table column labels and spacing for better readability
  and alignment in invoice display screens

Fixes #1789
2026-05-27 18:47:49 +05:30
Avinash Rajesh 807490443e Merged PR 1128: Fix customer removal flow, admin inventory UI issues, and user display improvements
Changes:

- Refactored customer and technician removal flow:
  - Ensured linked job cards and service bookings are properly cancelled.
  - Centralized cancellation logic with processBookingCancellation helper for consistent notifications, technician reassignment, and inventory restoration.
  - Prevented duplicate inventory restocking during customer removal.
  - Preserved customer references and IDs correctly during booking cancellation.

- Admin inventory management improvements:
  - Added heading output to Remove Inventory Item submenu for clearer context.
  - Prevented service creation with empty inventory selection by adding validation and early return.
  - Improved combo package creation by simplifying null checks with modern idioms.
  - Enhanced inventory selection flow:
    - Clear screen and context header for better UX.
    - Prevent duplicate item selection by checking already chosen items.
    - Changed exit option from -1 to 0 for consistency.
    - Added pressEnter prompt after successful item addition.
  - Simplified pointer checks using concise conditions (e.g., if (item)).

- User management UI fix:
  - Updated active user display in Remove User menu to include Full Name column, ensuring administrators have complete visibility when managing users.

Fixes #1788
Fixes #1778
Fixes #1781

Related work items: #1778, #1781, #1788
2026-05-27 18:38:17 +05:30
47 changed files with 2495 additions and 1452 deletions
+2 -2
View File
@@ -427,5 +427,5 @@ FodyWeavers.xsd
*.msm
*.msp
# CSV Files
*.csv
# DAT Files
*.dat
@@ -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;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(ProjectDir)models;$(ProjectDir)controllers;$(ProjectDir)factories;$(ProjectDir)views;$(ProjectDir)services;$(ProjectDir)utilities;$(ProjectDir)core\patterns;$(ProjectDir)datastores;$(ProjectDir)datastores\sharedmemory;%(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;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(ProjectDir)models;$(ProjectDir)controllers;$(ProjectDir)factories;$(ProjectDir)views;$(ProjectDir)services;$(ProjectDir)utilities;$(ProjectDir)core\patterns;$(ProjectDir)datastores;$(ProjectDir)datastores\sharedmemory;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
@@ -129,6 +129,7 @@
<ClCompile Include="core\patterns\Observer.cpp" />
<ClCompile Include="core\patterns\Subject.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" />
@@ -139,7 +140,6 @@
<ClCompile Include="models\User.cpp" />
<ClCompile Include="services\AuthenticationManagementService.cpp" />
<ClCompile Include="services\InventoryManagementService.cpp" />
<ClCompile Include="services\NotificationManagementService.cpp" />
<ClCompile Include="services\PaymentManagementService.cpp" />
<ClCompile Include="services\ServiceManagementService.cpp" />
<ClCompile Include="services\UserManagementService.cpp" />
@@ -156,6 +156,13 @@
<ClInclude Include="core\patterns\Observer.h" />
<ClInclude Include="core\patterns\Subject.h" />
<ClInclude Include="datastores\DataStore.h" />
<ClInclude Include="datastores\DataStoreLockGuard.h" />
<ClInclude Include="datastores\sharedmemory\FileHeader.h" />
<ClInclude Include="datastores\sharedmemory\MappingInfo.h" />
<ClInclude Include="datastores\sharedmemory\RecordState.h" />
<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" />
@@ -174,7 +181,6 @@
<ClInclude Include="utilities\Config.h" />
<ClInclude Include="utilities\Enums.h" />
<ClInclude Include="utilities\FileHelper.h" />
<ClInclude Include="utilities\FileManager.h" />
<ClInclude Include="utilities\InputHelper.h" />
<ClInclude Include="utilities\Map.h" />
<ClInclude Include="utilities\OutputHelper.h" />
@@ -64,6 +64,12 @@
<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>
<Filter Include="Source Files\DataStores\SharedMemory">
<UniqueIdentifier>{7aa8722e-adfa-466e-8211-de63f3b7892b}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="Trenser.VehicleServiceSystem.cpp">
@@ -108,9 +114,6 @@
<ClCompile Include="datastores\DataStore.cpp">
<Filter>Source Files\DataStores</Filter>
</ClCompile>
<ClCompile Include="services\NotificationManagementService.cpp">
<Filter>Source Files\Services</Filter>
</ClCompile>
<ClCompile Include="core\patterns\Observer.cpp">
<Filter>Source Files\Core\Patterns</Filter>
</ClCompile>
@@ -141,6 +144,9 @@
<ClCompile Include="models\ComboPackage.cpp">
<Filter>Source Files\Models</Filter>
</ClCompile>
<ClCompile Include="datastores\sharedmemory\SharedMemory.cpp">
<Filter>Source Files\DataStores\SharedMemory</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="utilities\InputHelper.h">
@@ -236,9 +242,6 @@
<ClInclude Include="utilities\Config.h">
<Filter>Header Files\Utilities</Filter>
</ClInclude>
<ClInclude Include="utilities\FileManager.h">
<Filter>Header Files\Utilities</Filter>
</ClInclude>
<ClInclude Include="utilities\StringHelper.h">
<Filter>Header Files\Utilities</Filter>
</ClInclude>
@@ -251,5 +254,26 @@
<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>
</ItemGroup>
</Project>
@@ -6,6 +6,7 @@ Description: Implementation file containing the method definitions of the
Author: Trenser
Date:19-May-2026
*/
#include "ComboPackage.h"
#include "Controller.h"
#include "Enums.h"
@@ -392,9 +393,9 @@ Parameters:
Returns:
- void
*/
void Controller::completeJob(const std::string& jobID)
void Controller::updateJobStatus(const std::string& jobID)
{
m_serviceManagementService.completeJob(jobID);
m_serviceManagementService.updateJobStatus(jobID);
}
/*
@@ -462,6 +463,38 @@ util::Map<std::string, const Invoice*> Controller::getInvoicesByUser()
return userInvoicesReadOnly;
}
/*
Function: getAllInvoices
Description: Retrieves all invoices from the PaymentManagementService and returns them as a read-only map.
Parameters:
- none
Returns:
- util::Map<std::string, const Invoice*>: Map of invoice IDs to invoice objects
*/
util::Map<std::string, const Invoice*> Controller::getAllInvoices()
{
auto invoices = m_paymentManagementService.getAllInvoices();
util::Map<std::string, const Invoice*> readOnlyInvoice;
for (int iterator = 0; iterator < invoices.getSize(); iterator++)
{
readOnlyInvoice.insert(invoices.getKeyAt(iterator), invoices.getValueAt(iterator));
}
return readOnlyInvoice;
}
/*
Function: confirmPayment
Description: Delegates payment confirmation for a given invoice ID to the PaymentManagementService.
Parameters:
- invoiceID: std::string, ID of the invoice to confirm
Returns:
- void
*/
void Controller::confirmPayment(const std::string& invoiceID)
{
m_paymentManagementService.confirmPayment(invoiceID);
}
/*
Function: completePayment
Description: Completes payment for a specific invoice using the given payment mode.
@@ -555,63 +588,37 @@ void Controller::configureNotifications(bool paymentNotifications, bool serviceN
}
/*
Function: loadSystemData
Description: Loads all system data from persistent storage into memory.
Invokes the respective management services to load users, inventory items, services,
combo packages, service bookings, job cards, invoices, and observers.
Function: initialize
Description: Initializes the system and run system checks to ensure critical configurations, such as verifying admin existence.
Parameters:
- None
Returns:
- void
- bool
*/
void Controller::loadSystemData()
bool Controller::initialize()
{
m_userManagementService.loadUsers();
m_inventoryManagementService.loadInventoryItems();
m_serviceManagementService.loadServices();
m_serviceManagementService.loadComboPackages();
m_serviceManagementService.loadServiceBookings();
m_serviceManagementService.loadJobCards();
m_paymentManagementService.loadInvoices();
m_serviceManagementService.loadObservers();
m_paymentManagementService.loadObservers();
m_inventoryManagementService.loadObservers();
}
auto& dataStore = DataStore::getInstance();
/*
Function: saveSystemData
Description: Saves all system data from memory back to persistent storage.
Invokes the respective management services to save users, inventory items, services,
combo packages, service bookings, job cards, invoices, and observers.
Parameters:
- None
Returns:
- void
*/
void Controller::saveSystemData()
{
m_userManagementService.saveUsers();
m_inventoryManagementService.saveInventoryItems();
m_serviceManagementService.saveServices();
m_serviceManagementService.saveComboPackages();
m_serviceManagementService.saveServiceBookings();
m_serviceManagementService.saveJobCards();
m_paymentManagementService.saveInvoices();
m_serviceManagementService.saveObservers();
m_paymentManagementService.saveObservers();
m_inventoryManagementService.saveObservers();
}
/*
Function: runSystemChecks
Description: Runs system checks to ensure critical configurations, such as verifying admin existence.
Parameter: None
Return type: void
*/
void Controller::runSystemChecks()
{
if (!dataStore.initialize())
{
return false;
}
m_userManagementService.ensureAdminExists();
m_inventoryManagementService.sendLowStockAlerts();
m_paymentManagementService.sendPaymentReminders();
return true;
}
/*
Function: shutdown
Description: Shutdown the system, and do necessary cleanups
Parameters:
- None
Returns:
- void
*/
void Controller::shutdown()
{
auto& dataStore = DataStore::getInstance();
dataStore.shutdown();
}
@@ -59,16 +59,17 @@ public:
void createService(const std::string& name, const util::Vector<std::string>& inventoryItemIDs, double laborCost);
void removeService(const std::string& serviceID);
util::Map<std::string, const JobCard*> getJobCardsByUser();
void completeJob(const std::string& jobID);
void updateJobStatus(const std::string& jobID);
void removeUser(const std::string& userID);
void createComboPackage(const std::string& name, const util::Vector<std::string>& serviceIDs, double discountPercentage);
void removeComboPackage(const std::string& comboPackageID);
util::Map<std::string, const Invoice*> getInvoicesByUser();
util::Map<std::string, const Invoice*> getAllInvoices();
void confirmPayment(const std::string& invoiceID);
void completePayment(const std::string& invoiceID, util::PaymentMode paymentMode);
util::Vector<const Notification*> getNotifications();
void deleteNotification(const std::string& notificationID);
void configureNotifications(bool paymentNotifications, bool serviceNotifications);
void loadSystemData();
void saveSystemData();
void runSystemChecks();
bool initialize();
void shutdown();
};
@@ -7,11 +7,8 @@ Date: 19-May-2026
#pragma once
class Notification;
class Observer
{
public:
virtual ~Observer() = default;
virtual void addNotification(Notification* notification) = 0;
};
@@ -9,6 +9,194 @@ Date: 19-May-2026
*/
#include "DataStore.h"
#include "Config.h"
#include "SerializedRecords.h"
#include "FileHelper.h"
/*
Function: DataStore
Description: Constructs the DataStore singleton and initializes
internal handles to their default values.
Parameters:
- None
Returns:
- None
*/
DataStore::DataStore() :
m_globalMutex(NULL) {}
/*
Function: ~DataStore
Description: Destroys the DataStore singleton and releases all
cached application objects owned by the datastore.
This includes users, notifications, services,
combo packages, inventory items, service bookings,
job cards, and invoices.
Parameters:
- None
Returns:
- None
*/
DataStore::~DataStore()
{
clearCache(m_userCache);
clearCache(m_notificationCache);
clearCache(m_serviceCache);
clearCache(m_comboPackageCache);
clearCache(m_inventoryItemCache);
clearCache(m_serviceBookingCache);
clearCache(m_jobCardCache);
clearCache(m_invoiceCache);
}
/*
Function: initialize
Description: Initializes the shared-memory datastore.
Creates or opens the global datastore mutex,
configures all mapping metadata, and creates
or opens the file mappings used to persist
application data. After successful completion,
all datastore files are mapped into memory and
ready for use by the application.
Parameter: None
Return type: bool
- true : Initialization completed successfully.
- false : Failed to create the mutex or open
one or more file mappings.
*/
bool DataStore::initialize()
{
m_globalMutex = CreateMutexA(NULL, FALSE, "VehicleServiceSystemMutex");
if (m_globalMutex == NULL)
{
return false;
}
if (!lockDataStore())
{
CloseHandle(m_globalMutex);
m_globalMutex = NULL;
return false;
}
bool success = true;
do
{
util::ensureDirectoryExists(config::file::DIRECTORY);
m_users.fileName = config::file::USER_FILE;
m_users.recordSize = sizeof(SerializedUser);
m_notifications.fileName = config::file::NOTIFICATION_FILE;
m_notifications.recordSize = sizeof(SerializedNotification);
m_services.fileName = config::file::SERVICE_FILE;
m_services.recordSize = sizeof(SerializedService);
m_comboPackages.fileName = config::file::COMBOPACKAGE_FILE;
m_comboPackages.recordSize = sizeof(SerializedComboPackage);
m_inventoryItems.fileName = config::file::INVENTORYITEM_FILE;
m_inventoryItems.recordSize = sizeof(SerializedInventoryItem);
m_serviceBookings.fileName = config::file::SERVICEBOOKING_FILE;
m_serviceBookings.recordSize = sizeof(SerializedServiceBooking);
m_jobCards.fileName = config::file::JOBCARD_FILE;
m_jobCards.recordSize = sizeof(SerializedJobCard);
m_invoices.fileName = config::file::INVOICE_FILE;
m_invoices.recordSize = sizeof(SerializedInvoice);
m_serviceManagementObservers.fileName = config::file::SERVICEMANAGEMENTOBSERVERS;
m_serviceManagementObservers.recordSize = sizeof(SerializedObserver);
m_paymentManagementObservers.fileName = config::file::PAYMENTMANAGEMENTOBSERVERS;
m_paymentManagementObservers.recordSize = sizeof(SerializedObserver);
m_inventoryManagementObservers.fileName = config::file::INVENTORYMANAGEMENTOBSERVERS;
m_inventoryManagementObservers.recordSize = sizeof(SerializedObserver);
if (!SharedMemory::createOrOpenMapping(m_users))
{
success = false;
break;
}
if (!SharedMemory::createOrOpenMapping(m_notifications))
{
success = false;
break;
}
if (!SharedMemory::createOrOpenMapping(m_services))
{
success = false;
break;
}
if (!SharedMemory::createOrOpenMapping(m_comboPackages))
{
success = false;
break;
}
if (!SharedMemory::createOrOpenMapping(m_inventoryItems))
{
success = false;
break;
}
if (!SharedMemory::createOrOpenMapping(m_serviceBookings))
{
success = false;
break;
}
if (!SharedMemory::createOrOpenMapping(m_jobCards))
{
success = false;
break;
}
if (!SharedMemory::createOrOpenMapping(m_invoices))
{
success = false;
break;
}
if (!SharedMemory::createOrOpenMapping(m_serviceManagementObservers))
{
success = false;
break;
}
if (!SharedMemory::createOrOpenMapping(m_paymentManagementObservers))
{
success = false;
break;
}
if (!SharedMemory::createOrOpenMapping(m_inventoryManagementObservers))
{
success = false;
break;
}
} while (false);
unlockDataStore();
if (!success)
{
shutdown();
}
return success;
}
/*
Function: shutdown
Description: Releases all shared-memory resources owned by the
datastore. Closes every file mapping, unmaps all
mapped views, and releases the datastore mutex.
After this call, the datastore must be initialized
again before use.
Parameter: None
Return type: void
*/
void DataStore::shutdown()
{
SharedMemory::closeMapping(m_users);
SharedMemory::closeMapping(m_notifications);
SharedMemory::closeMapping(m_services);
SharedMemory::closeMapping(m_comboPackages);
SharedMemory::closeMapping(m_inventoryItems);
SharedMemory::closeMapping(m_serviceBookings);
SharedMemory::closeMapping(m_jobCards);
SharedMemory::closeMapping(m_invoices);
SharedMemory::closeMapping(m_serviceManagementObservers);
SharedMemory::closeMapping(m_paymentManagementObservers);
SharedMemory::closeMapping(m_inventoryManagementObservers);
if (m_globalMutex != NULL)
{
CloseHandle(m_globalMutex);
m_globalMutex = NULL;
}
}
/*
Function: getInstance
@@ -26,104 +214,392 @@ DataStore& DataStore::getInstance()
/*
Function: getUsers
Description: Retrieves the internal map of users.
Description: Retrieves all user records from the datastore.
Parameters:
- None
Returns:
- Reference to util::Map<std::string, User*> containing all users.
- util::Map<std::string, TrackedRecord<User>>: Collection of user records
*/
util::Map<std::string, User*>& DataStore::getUsers()
util::Map<std::string, TrackedRecord<User>>& DataStore::getUsers()
{
return m_users;
auto users = loadRecords<User, SerializedUser>(m_users);
refreshCache(m_userCache, users);
return m_userCache;
}
/*
Function: getNotifications
Description: Retrieves all notification records from the datastore.
Parameters:
- None
Returns:
- util::Map<std::string, TrackedRecord<Notification>>: Collection of notification records
*/
util::Map<std::string, TrackedRecord<Notification>>& DataStore::getNotifications()
{
auto notifications = loadRecords<Notification, SerializedNotification>(m_notifications);
refreshCache(m_notificationCache, notifications);
return m_notificationCache;
}
/*
Function: getServices
Description: Retrieves the internal map of services.
Description: Retrieves all service records from the datastore.
Parameters:
- None
Returns:
- Reference to util::Map<std::string, Service*> containing all services.
- util::Map<std::string, TrackedRecord<Service>>: Collection of service records
*/
util::Map<std::string, Service*>& DataStore::getServices()
util::Map<std::string, TrackedRecord<Service>>& DataStore::getServices()
{
return m_services;
return m_serviceCache;
}
/*
Function: getComboPackages
Description: Retrieves the internal map of combo packages.
Description: Retrieves all combo package records from the datastore.
Parameters:
- None
Returns:
- Reference to util::Map<std::string, ComboPackage*> containing all combo packages.
- util::Map<std::string, TrackedRecord<ComboPackage>>: Collection of combo package records
*/
util::Map<std::string, ComboPackage*>& DataStore::getComboPackages()
util::Map<std::string, TrackedRecord<ComboPackage>>& DataStore::getComboPackages()
{
return m_comboPackages;
}
/*
Function: getServiceBookings
Description: Retrieves the internal map of service bookings.
Parameters:
- None
Returns:
- Reference to util::Map<std::string, ServiceBooking*> containing all service bookings.
*/
util::Map<std::string, ServiceBooking*>& DataStore::getServiceBookings()
{
return m_serviceBookings;
}
/*
Function: getJobCards
Description: Retrieves the internal map of job cards.
Parameters:
- None
Returns:
- Reference to util::Map<std::string, JobCard*> containing all job cards.
*/
util::Map<std::string, JobCard*>& DataStore::getJobCards()
{
return m_jobCards;
return m_comboPackageCache;
}
/*
Function: getInventoryItems
Description: Retrieves the internal map of inventory items.
Description: Retrieves all inventory item records from the datastore.
Parameters:
- None
Returns:
- Reference to util::Map<std::string, InventoryItem*> containing all inventory items.
- util::Map<std::string, TrackedRecord<InventoryItem>>: Collection of inventory item records
*/
util::Map<std::string, InventoryItem*>& DataStore::getInventoryItems()
util::Map<std::string, TrackedRecord<InventoryItem>>& DataStore::getInventoryItems()
{
return m_inventoryItems;
return m_inventoryItemCache;
}
/*
Function: getServiceBookings
Description: Retrieves all service booking records from the datastore.
Parameters:
- None
Returns:
- util::Map<std::string, TrackedRecord<ServiceBooking>>: Collection of service booking records
*/
util::Map<std::string, TrackedRecord<ServiceBooking>>& DataStore::getServiceBookings()
{
return m_serviceBookingCache;
}
/*
Function: getJobCards
Description: Retrieves all job card records from the datastore.
Parameters:
- None
Returns:
- util::Map<std::string, TrackedRecord<JobCard>>: Collection of job card records
*/
util::Map<std::string, TrackedRecord<JobCard>>& DataStore::getJobCards()
{
return m_jobCardCache;
}
/*
Function: getInvoices
Description: Retrieves the internal map of invoices.
Description: Retrieves all invoice records from the datastore.
Parameters:
- None
Returns:
- Reference to util::Map<std::string, Invoice*> containing all invoices.
- util::Map<std::string, TrackedRecord<Invoice>>: Collection of invoice records
*/
util::Map<std::string, Invoice*>& DataStore::getInvoices()
util::Map<std::string, TrackedRecord<Invoice>>& DataStore::getInvoices()
{
return m_invoices;
return m_invoiceCache;
}
/*
Function: getPayments
Description: Retrieves the internal map of payments.
Function: getObservers
Description: Retrieves observer records from the specified observer mapping
and resolves them to User objects.
Parameters:
- mapping: Observer mapping to read from
Returns:
- util::Map<std::string, User*>: Collection of observer records
Throws:
- std::runtime_error if an observer references an invalid user ID
*/
util::Map<std::string, User*> DataStore::getObservers(MappingInfo& mapping)
{
auto& users = getUsers();
util::Map<std::string, User*> observers;
SharedMemory::ensureLatestMapping(mapping);
size_t recordCount = SharedMemory::getRecordCount(mapping);
for (size_t index = 0; index < recordCount; index++)
{
const SerializedObserver* observer = static_cast<SerializedObserver*>(SharedMemory::getRecordAddress(mapping, index));
int userIndex = users.find(observer->id);
if (userIndex == -1)
{
throw std::runtime_error("Invalid observer user ID");
}
User* user = users.getValueAt(userIndex).data;
observers.insert(user->getId(), user);
}
return observers;
}
/*
Function: getServiceManagementObservers
Description: Retrieves all service management observer records from the datastore.
Parameters:
- None
Returns:
- Reference to util::Map<std::string, Payment*> containing all payments.
- util::Map<std::string, User*>: Collection of observer records
*/
util::Map<std::string, Payment*>& DataStore::getPayments()
util::Map<std::string, User*> DataStore::getServiceManagementObservers()
{
return m_payments;
}
return getObservers(m_serviceManagementObservers);
}
/*
Function: getPaymentManagementObservers
Description: Retrieves all payment management observer records from the datastore.
Parameters:
- None
Returns:
- util::Map<std::string, User*>: Collection of observer records
*/
util::Map<std::string, User*> DataStore::getPaymentManagementObservers()
{
return getObservers(m_paymentManagementObservers);
}
/*
Function: getInventoryManagementObservers
Description: Retrieves all inventory management observer records from the datastore.
Parameters:
- None
Returns:
- util::Map<std::string, User*>: Collection of observer records
*/
util::Map<std::string, User*> DataStore::getInventoryManagementObservers()
{
return getObservers(m_inventoryManagementObservers);
}
/*
Function: saveUsers
Description: Persists all user records to the datastore.
Parameters:
- None
Returns:
- None
*/
void DataStore::saveUsers()
{
saveRecords<User, SerializedUser>(m_users, m_userCache);
}
/*
Function: saveNotifications
Description: Persists all notification records to the datastore.
Parameters:
- None
Returns:
- None
*/
void DataStore::saveNotifications()
{
saveRecords<Notification, SerializedNotification>(m_notifications, m_notificationCache);
}
/*
Function: saveServices
Description: Persists all service records to the datastore.
Parameters:
- None
Returns:
- None
*/
void DataStore::saveServices()
{
}
/*
Function: saveComboPackages
Description: Persists all combo package records to the datastore.
Parameters:
- None
Returns:
- None
*/
void DataStore::saveComboPackages()
{
}
/*
Function: saveInventoryItems
Description: Persists all inventory item records to the datastore.
Parameters:
- None
Returns:
- None
*/
void DataStore::saveInventoryItems()
{
}
/*
Function: saveServiceBookings
Description: Persists all service booking records to the datastore.
Parameters:
- None
Returns:
- None
*/
void DataStore::saveServiceBookings()
{
}
/*
Function: saveJobCards
Description: Persists all job card records to the datastore.
Parameters:
- None
Returns:
- None
*/
void DataStore::saveJobCards()
{
}
/*
Function: saveInvoices
Description: Persists all invoice records to the datastore.
Parameters:
- None
Returns:
- None
*/
void DataStore::saveInvoices()
{
}
/*
Function: saveObservers
Description: Persists observer records to the specified observer mapping.
Parameters:
- mapping: MappingInfo&, observer mapping to save to
- observers: util::Map<std::string, User*>&, collection of observer records
Returns:
- None
*/
void DataStore::saveObservers(MappingInfo& mapping, util::Map<std::string, User*>& observers)
{
size_t observerCount = static_cast<size_t>(observers.getSize());
size_t capacity = config::file::INITIAL_CAPACITY;
while (capacity < observerCount)
{
capacity *= config::file::GROWTH_FACTOR;
}
if (!SharedMemory::resizeMapping(mapping, capacity))
{
throw std::runtime_error("Failed to resize observer mapping");
}
SharedMemory::setRecordCount(mapping, observerCount);
for (size_t index = 0; index < observerCount; index++)
{
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));
*destination = serializedObserver;
}
}
/*
Function: saveServiceManagementObservers
Description: Persists all service management observer records to the datastore.
Parameters:
- observers: util::Map<std::string, User*>&, collection of observer records
Returns:
- None
*/
void DataStore::saveServiceManagementObservers(util::Map<std::string, User*>& observers)
{
saveObservers(m_serviceManagementObservers, observers);
}
/*
Function: savePaymentManagementObservers
Description: Persists all payment management observer records to the datastore.
Parameters:
- observers: util::Map<std::string, User*>&, collection of observer records
Returns:
- None
*/
void DataStore::savePaymentManagementObservers(util::Map<std::string, User*>& observers)
{
saveObservers(m_paymentManagementObservers, observers);
}
/*
Function: saveInventoryManagementObservers
Description: Persists all inventory management observer records to the datastore.
Parameters:
- observers: util::Map<std::string, User*>&, collection of observer records
Returns:
- None
*/
void DataStore::saveInventoryManagementObservers(util::Map<std::string, User*>& observers)
{
saveObservers(m_inventoryManagementObservers, observers);
}
/*
Function: lockDataStore
Description: Acquires the datastore mutex, providing
exclusive access to the shared-memory
datastore. This function blocks until
the mutex becomes available or an error
occurs.
Parameter: None
Return type:
bool
- true : Mutex successfully acquired.
- false : Failed to acquire the mutex.
*/
bool DataStore::lockDataStore()
{
if (m_globalMutex == NULL)
{
return false;
}
DWORD result = WaitForSingleObject(m_globalMutex, INFINITE);
return result == WAIT_OBJECT_0;
}
/*
Function: unlockDataStore
Description: Releases the datastore mutex after a
successful call to lockDataStore(),
allowing other processes to access the
shared-memory datastore.
Parameter: None
Return type:
bool
- true : Mutex successfully released.
- false : Failed to release the mutex.
*/
bool DataStore::unlockDataStore()
{
if (m_globalMutex == NULL)
{
return false;
}
return ReleaseMutex(m_globalMutex) != 0;
}
@@ -6,42 +6,241 @@ Date: 19-May-2026
*/
#pragma once
#include <windows.h>
#include <string>
#include "Map.h"
class User;
class Service;
class ComboPackage;
class ServiceBooking;
class JobCard;
class InventoryItem;
class Invoice;
class Payment;
#include "MappingInfo.h"
#include "TrackedRecord.h"
#include "SharedMemory.h"
#include "User.h"
#include "Notification.h"
#include "Service.h"
#include "ComboPackage.h"
#include "InventoryItem.h"
#include "ServiceBooking.h"
#include "JobCard.h"
#include "Invoice.h"
class DataStore
{
private:
util::Map<std::string, User*> m_users;
util::Map<std::string, Service*> m_services;
util::Map<std::string, ComboPackage*> m_comboPackages;
util::Map<std::string, ServiceBooking*> m_serviceBookings;
util::Map<std::string, JobCard*> m_jobCards;
util::Map<std::string, InventoryItem*> m_inventoryItems;
util::Map<std::string, Invoice*> m_invoices;
util::Map<std::string, Payment*> m_payments;
DataStore() {}
public:
static DataStore& getInstance();
DataStore();
~DataStore();
DataStore(const DataStore&) = delete;
DataStore& operator=(const DataStore&) = delete;
DataStore(DataStore&&) = delete;
DataStore& operator=(DataStore&&) = delete;
util::Map<std::string, User*>& getUsers();
util::Map<std::string, Service*>& getServices();
util::Map<std::string, ComboPackage*>& getComboPackages();
util::Map<std::string, ServiceBooking*>& getServiceBookings();
util::Map<std::string, JobCard*>& getJobCards();
util::Map<std::string, InventoryItem*>& getInventoryItems();
util::Map<std::string, Invoice*>& getInvoices();
util::Map<std::string, Payment*>& getPayments();
};
HANDLE m_globalMutex;
MappingInfo m_users;
MappingInfo m_notifications;
MappingInfo m_services;
MappingInfo m_comboPackages;
MappingInfo m_inventoryItems;
MappingInfo m_serviceBookings;
MappingInfo m_jobCards;
MappingInfo m_invoices;
MappingInfo m_serviceManagementObservers;
MappingInfo m_paymentManagementObservers;
MappingInfo m_inventoryManagementObservers;
util::Map<std::string, TrackedRecord<User>> m_userCache;
util::Map<std::string, TrackedRecord<Notification>> m_notificationCache;
util::Map<std::string, TrackedRecord<Service>> m_serviceCache;
util::Map<std::string, TrackedRecord<ComboPackage>> m_comboPackageCache;
util::Map<std::string, TrackedRecord<InventoryItem>> m_inventoryItemCache;
util::Map<std::string, TrackedRecord<ServiceBooking>> m_serviceBookingCache;
util::Map<std::string, TrackedRecord<JobCard>> m_jobCardCache;
util::Map<std::string, TrackedRecord<Invoice>> m_invoiceCache;
public:
static DataStore& getInstance();
bool initialize();
void shutdown();
util::Map<std::string, TrackedRecord<User>>& getUsers();
util::Map<std::string, TrackedRecord<Notification>>& getNotifications();
util::Map<std::string, TrackedRecord<Service>>& getServices();
util::Map<std::string, TrackedRecord<ComboPackage>>& getComboPackages();
util::Map<std::string, TrackedRecord<InventoryItem>>& getInventoryItems();
util::Map<std::string, TrackedRecord<ServiceBooking>>& getServiceBookings();
util::Map<std::string, TrackedRecord<JobCard>>& getJobCards();
util::Map<std::string, TrackedRecord<Invoice>>& getInvoices();
util::Map<std::string, User*> getServiceManagementObservers();
util::Map<std::string, User*> getPaymentManagementObservers();
util::Map<std::string, User*> getInventoryManagementObservers();
void saveUsers();
void saveNotifications();
void saveServices();
void saveComboPackages();
void saveInventoryItems();
void saveServiceBookings();
void saveJobCards();
void saveInvoices();
void saveServiceManagementObservers(util::Map<std::string, User*>& observers);
void savePaymentManagementObservers(util::Map<std::string, User*>& observers);
void saveInventoryManagementObservers(util::Map<std::string, User*>& observers);
bool lockDataStore();
bool unlockDataStore();
private:
template<typename TObject, typename TSerialized>
util::Map<std::string, TrackedRecord<TObject>> loadRecords(MappingInfo& mapping);
template<typename TObject, typename TSerialized>
void saveRecords(MappingInfo& mapping, util::Map<std::string, TrackedRecord<TObject>>& records);
template<typename TObject> void clearCache(util::Map<std::string, TrackedRecord<TObject>>&cache);
template<typename TObject> void refreshCache(util::Map<std::string, TrackedRecord<TObject>>&cache, util::Map<std::string, TrackedRecord<TObject>>&refreshedCache);
util::Map<std::string, User*> getObservers(MappingInfo& mapping);
void saveObservers(MappingInfo& mapping, util::Map<std::string, User*>& observers);
};
/*
Function: loadRecords
Description: Loads all serialized records from the specified
shared-memory mapping, deserializes them into
application objects, and returns them as a map
of tracked records. Each loaded record is marked
as CLEAN and assigned its corresponding slot index
within the mapping so that future modifications
can be written back efficiently.
Parameter:
- mapping: Reference to the mapping containing the
serialized records.
Return type:
util::Map<std::string, TrackedRecord<TObject>>
A map containing all loaded records keyed by
their unique identifier.
*/
template<typename TObject, typename TSerialized>
util::Map<std::string, TrackedRecord<TObject>> DataStore::loadRecords(MappingInfo& mapping)
{
util::Map<std::string, TrackedRecord<TObject>> records;
SharedMemory::ensureLatestMapping(mapping);
size_t recordCount = SharedMemory::getRecordCount(mapping);
for (size_t index = 0; index < recordCount; ++index)
{
TSerialized* serialized = static_cast<TSerialized*>(SharedMemory::getRecordAddress(mapping,index));
TObject* object = TObject::deserialize(*serialized);
TrackedRecord<TObject> trackedRecord;
trackedRecord.data = object;
trackedRecord.state = RecordState::CLEAN;
trackedRecord.slotIndex = index;
records.insert(object->getId(), trackedRecord);
}
return records;
}
/*
Function: saveRecords
Description: Persists all modified and newly added records
contained in the provided map to the specified
shared-memory mapping. Modified records overwrite
their existing slots, while new records are
appended to the end of the mapping. Records marked
as CLEAN are ignored.
Parameter:
- mapping: Reference to the mapping where records are
stored.
- records: Map containing the records to be persisted.
Return type: void
*/
template<typename TObject, typename TSerialized> void DataStore::saveRecords(MappingInfo& mapping, util::Map<std::string, TrackedRecord<TObject>>& records)
{
SharedMemory::ensureLatestMapping(mapping);
for (int index = 0; index < records.getSize(); ++index)
{
TrackedRecord<TObject>& record = records.getValueAt(index);
if (record.state == RecordState::CLEAN)
{
continue;
}
TSerialized serialized = record.data->serialize();
if (record.state == RecordState::MODIFIED)
{
TSerialized* destination = static_cast<TSerialized*>(SharedMemory::getRecordAddress(mapping, record.slotIndex));
if (destination)
{
*destination = serialized;
}
}
else if (record.state == RecordState::NEW_RECORD)
{
if (!SharedMemory::ensureCapacityForInsert(mapping))
{
continue;
}
size_t recordCount = SharedMemory::getRecordCount(mapping);
TSerialized* destination = static_cast<TSerialized*>(SharedMemory::getRecordAddress(mapping,recordCount));
*destination = serialized;
SharedMemory::setRecordCount(mapping, recordCount + 1);
record.slotIndex = recordCount;
}
record.state = RecordState::CLEAN;
}
}
/*
Function: clearCache
Description: Releases all objects owned by the cache and
clears the cache contents.
Parameters:
- cache: Cache to be cleared.
Returns:
- None
*/
template<typename TObject>
void DataStore::clearCache(util::Map<std::string, TrackedRecord<TObject>>&cache)
{
for (int index = 0; index < cache.getSize(); ++index)
{
delete cache.getValueAt(index).data;
cache.getValueAt(index).data = nullptr;
}
cache.clear();
}
/*
Function: refreshCache
Description: Refreshes the cache while preserving object addresses
for records that already exist. Existing objects are
updated in-place so that pointers held elsewhere remain
valid after the refresh.
Parameters:
- cache: Existing cache to refresh.
- refreshedCache: Newly loaded cache contents.
Returns:
- None
*/
template<typename TObject>
void DataStore::refreshCache(util::Map<std::string, TrackedRecord<TObject>>& cache, util::Map<std::string, TrackedRecord<TObject>>& refreshedCache)
{
util::Map<std::string, TrackedRecord<TObject>> oldCache = cache;
cache.clear();
for (int index = 0; index < refreshedCache.getSize(); ++index)
{
const std::string& id = refreshedCache.getKeyAt(index);
TrackedRecord<TObject>& refreshedRecord = refreshedCache.getValueAt(index);
int oldIndex = oldCache.find(id);
if (oldIndex != -1)
{
TrackedRecord<TObject>& oldRecord = oldCache.getValueAt(oldIndex);
*oldRecord.data = *refreshedRecord.data;
oldRecord.slotIndex = refreshedRecord.slotIndex;
oldRecord.state = refreshedRecord.state;
delete refreshedRecord.data;
refreshedRecord.data = oldRecord.data;
}
cache.insert(id, refreshedRecord);
}
for (int index = 0; index < oldCache.getSize(); ++index)
{
const std::string& id = oldCache.getKeyAt(index);
const TrackedRecord<TObject>& localTrackedRecord = oldCache.getValueAt(index);
if (cache.find(id) == -1)
{
if (localTrackedRecord.state == RecordState::NEW_RECORD)
{
cache.insert(id, localTrackedRecord);
}
else
{
delete localTrackedRecord.data;
}
}
}
}
@@ -0,0 +1,28 @@
/*
File: DataStoreLockGuard.h
Description: Defines the DataStoreLockGuard class used to manage DataStore
locking and unlocking automatically within a scope.
Author: Trenser
Date: 12-June-2026
*/
#pragma once
#include "DataStore.h"
class DataStoreLockGuard
{
public:
explicit DataStoreLockGuard(DataStore& dataStore)
: m_dataStore(dataStore)
{
m_dataStore.lockDataStore();
}
~DataStoreLockGuard()
{
m_dataStore.unlockDataStore();
}
DataStoreLockGuard(const DataStoreLockGuard&) = delete;
DataStoreLockGuard& operator=(const DataStoreLockGuard&) = delete;
private:
DataStore& m_dataStore;
};
@@ -0,0 +1,17 @@
/*
File: FileHeader.h
Description: Defines the FileHeader structure used to store
metadata for binary record files, including
record count and capacity.
Author: Trenser
Created: 10-June-2026
*/
#pragma once
#include <cstddef>
struct FileHeader
{
size_t recordCount;
size_t capacity;
};
@@ -0,0 +1,29 @@
/*
File: MappingInfo.h
Description: Defines the MappingInfo structure used for
managing Windows file mapping operations.
Stores handles, mapped view pointer,
file metadata, and capacity information.
Author: Trenser
Created: 10-June-2026
*/
#pragma once
#include <windows.h>
#include <string>
struct MappingInfo
{
HANDLE fileHandle;
HANDLE mappingHandle;
void* mappedView;
std::string fileName;
size_t recordSize;
size_t mappedCapacity;
MappingInfo()
: fileHandle(NULL),
mappingHandle(NULL),
mappedView(nullptr),
recordSize(0),
mappedCapacity(0) {}
};
@@ -0,0 +1,17 @@
/*
File: RecordState.h
Description: Defines the RecordState enumeration used to
represent the state of a record in storage.
States include CLEAN, NEW_RECORD, and MODIFIED.
Author: Trenser
Created: 10-June-2026
*/
#pragma once
enum class RecordState : int
{
CLEAN,
NEW_RECORD,
MODIFIED
};
@@ -0,0 +1,109 @@
/*
File: SerializedRecords.h
Description: Defines serialized structures for persistent storage
and retrieval of system entities including User,
Notification, Service, ComboPackage, InventoryItem,
ServiceBooking, JobCard, Invoice, and Observer.
These structures use fixed-size character arrays
and primitive types for binary serialization.
Author: Trenser
Created: 10-June-2026
*/
#pragma once
#include "Utility.h"
#include "Enums.h"
#include "Timestamp.h"
struct SerializedUser
{
char id[64];
char username[64];
char password[64];
char name[128];
char phone[32];
char email[128];
util::UserType userType;
util::State status;
};
struct SerializedNotification
{
char id[64];
char recipientUserId[64];
char title[128];
char message[1024];
util::Timestamp createdAt;
util::State state;
};
struct SerializedService
{
char id[64];
char name[128];
char inventoryItemIDs[1024];
double laborCost;
util::State status;
};
struct SerializedComboPackage
{
char id[64];
char packageName[128];
double discountPercentage;
char serviceIDs[1024];
util::State status;
};
struct SerializedInventoryItem
{
char id[64];
char partName[128];
int quantity;
double price;
util::State status;
};
struct SerializedServiceBooking
{
char id[64];
util::ServiceJobStatus status;
char serviceIDs[1024];
char customerId[64];
char vehicleNumber[64];
char vehicleBrand[64];
char vehicleModel[64];
char assignedTechnicianId[64];
double discountPercentage;
};
struct SerializedJobCard
{
char id[64];
char bookingId[64];
char serviceId[64];
char technicianId[64];
util::Timestamp assignedDate;
util::ServiceJobStatus status;
util::Timestamp completionDate;
};
struct SerializedInvoice
{
char id[64];
char bookingId[64];
util::Timestamp invoiceDate;
char partIDs[1024];
double laborCost;
double partsCost;
double discountPercentage;
double totalAmount;
util::Timestamp paymentDate;
util::PaymentMode paymentMethod;
util::PaymentStatus status;
};
struct SerializedObserver
{
char id[64];
};
@@ -0,0 +1,386 @@
/*
File: SharedMemory.cpp
Description: Implements shared memory utilities for managing
Windows file mapping operations. Provides functions
to create, open, resize, and close mappings, as well
as access headers, records, and ensure synchronization
across processes.
Author: Trenser
Created: 11-June-2026
*/
#include "SharedMemory.h"
#include "Windows.h"
#include "Config.h"
/*
Function: invalidateMapping
Description: Releases all mapping resources and resets the mapping to an invalid state.
Parameters:
- mapping: MappingInfo&, mapping information and handles
Returns:
- None
*/
static void invalidateMapping(MappingInfo& mapping)
{
if (mapping.mappedView != nullptr)
{
UnmapViewOfFile(mapping.mappedView);
mapping.mappedView = nullptr;
}
if (mapping.mappingHandle != NULL)
{
CloseHandle(mapping.mappingHandle);
mapping.mappingHandle = NULL;
}
if (mapping.fileHandle != INVALID_HANDLE_VALUE)
{
CloseHandle(mapping.fileHandle);
mapping.fileHandle = INVALID_HANDLE_VALUE;
}
mapping.mappedCapacity = 0;
}
/*
Function: createOrOpenMapping
Description: Creates or opens a file mapping and maps it into the process address space.
Parameters:
- mapping: MappingInfo&, mapping information and handles
Returns:
- bool: True if the mapping was successfully created/opened, otherwise false
*/
bool SharedMemory::createOrOpenMapping(MappingInfo& mapping)
{
if (mapping.recordSize == 0)
{
return false;
}
mapping.fileHandle =
CreateFileA(
mapping.fileName.c_str(),
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (mapping.fileHandle == INVALID_HANDLE_VALUE)
{
return false;
}
LARGE_INTEGER fileSize;
if (!GetFileSizeEx(mapping.fileHandle, &fileSize))
{
invalidateMapping(mapping);
return false;
}
bool isNewFile = (fileSize.QuadPart == 0);
const size_t initialCapacity = config::file::INITIAL_CAPACITY;
if (isNewFile)
{
LARGE_INTEGER newSize;
newSize.QuadPart = sizeof(FileHeader) + initialCapacity * mapping.recordSize;
if (!SetFilePointerEx(mapping.fileHandle, newSize, NULL, FILE_BEGIN))
{
invalidateMapping(mapping);
return false;
}
if (!SetEndOfFile(mapping.fileHandle))
{
invalidateMapping(mapping);
return false;
}
}
mapping.mappingHandle =
CreateFileMappingA(
mapping.fileHandle,
NULL,
PAGE_READWRITE,
0,
0,
NULL);
if (mapping.mappingHandle == NULL)
{
invalidateMapping(mapping);
return false;
}
mapping.mappedView =
MapViewOfFile(
mapping.mappingHandle,
FILE_MAP_ALL_ACCESS,
0,
0,
0);
if (mapping.mappedView == nullptr)
{
invalidateMapping(mapping);
return false;
}
FileHeader* header = getHeader(mapping);
if (header == nullptr)
{
invalidateMapping(mapping);
return false;
}
if (isNewFile)
{
header->recordCount = 0;
header->capacity = initialCapacity;
}
mapping.mappedCapacity = header->capacity;
return true;
}
/*
Function: closeMapping
Description: Unmaps and closes all resources associated with a file mapping.
Parameters:
- mapping: MappingInfo&, mapping to close
Returns:
- None
*/
void SharedMemory::closeMapping(MappingInfo& mapping)
{
invalidateMapping(mapping);
}
/*
Function: getHeader
Description: Returns the file header stored at the beginning of the mapped memory region.
Parameters:
- mapping: MappingInfo&, mapping information and handles
Returns:
- FileHeader*: Pointer to the file header, or nullptr if the mapping is not valid
*/
FileHeader* SharedMemory::getHeader(MappingInfo& mapping)
{
if (mapping.mappedView == nullptr)
{
return nullptr;
}
return reinterpret_cast<FileHeader*>(mapping.mappedView);
}
/*
Function: getRecordAddress
Description: Returns the address of a record at the specified index within the mapped memory region.
Parameters:
- mapping: MappingInfo&, mapping information and handles
- index: size_t, record index
Returns:
- void*: Pointer to the record, or nullptr if the mapping is not valid
*/
void* SharedMemory::getRecordAddress(MappingInfo& mapping, size_t index)
{
if (mapping.mappedView == nullptr)
{
return nullptr;
}
return reinterpret_cast<char*>(mapping.mappedView) + sizeof(FileHeader) + index * mapping.recordSize;
}
/*
Function: getRecordCount
Description: Returns the number of records currently stored in the mapping.
Parameters:
- mapping: MappingInfo&, mapping information and handles
Returns:
- size_t: Number of records in the mapping
*/
size_t SharedMemory::getRecordCount(MappingInfo& mapping)
{
FileHeader* header = getHeader(mapping);
if (header == nullptr)
{
return 0;
}
return header->recordCount;
}
/*
Function: setRecordCount
Description: Updates the number of records stored in the mapping.
Parameters:
- mapping: MappingInfo&, mapping information and handles
- count: size_t, new record count
Returns:
- None
*/
void SharedMemory::setRecordCount(MappingInfo& mapping, size_t count)
{
FileHeader* header = getHeader(mapping);
if (header == nullptr)
{
return;
}
header->recordCount = count;
}
/*
Function: getCapacity
Description: Returns the current capacity of the mapping.
Parameters:
- mapping: MappingInfo&, mapping information and handles
Returns:
- size_t: Maximum number of records that can be stored in the mapping
*/
size_t SharedMemory::getCapacity(MappingInfo& mapping)
{
FileHeader* header = getHeader(mapping);
if (header == nullptr)
{
return 0;
}
return header->capacity;
}
/*
Function: resizeMapping
Description: Resizes the file mapping to the specified capacity.
Parameters:
- mapping: MappingInfo&, mapping information and handles
- newCapacity: size_t, new mapping capacity
Returns:
- bool: True if the resize succeeded, otherwise false
*/
bool SharedMemory::resizeMapping(MappingInfo& mapping, size_t newCapacity)
{
FileHeader* header = getHeader(mapping);
if (header == nullptr)
{
invalidateMapping(mapping);
return false;
}
header->capacity = newCapacity;
if (!FlushViewOfFile(mapping.mappedView, sizeof(FileHeader)))
{
return false;
}
if (!UnmapViewOfFile(mapping.mappedView))
{
return false;
}
mapping.mappedView = nullptr;
CloseHandle(mapping.mappingHandle);
mapping.mappingHandle = NULL;
LARGE_INTEGER newSize;
newSize.QuadPart = sizeof(FileHeader) + newCapacity * mapping.recordSize;
if (!SetFilePointerEx(mapping.fileHandle, newSize, NULL, FILE_BEGIN))
{
invalidateMapping(mapping);
return false;
}
if (!SetEndOfFile(mapping.fileHandle))
{
invalidateMapping(mapping);
return false;
}
mapping.mappingHandle =
CreateFileMappingA(
mapping.fileHandle,
NULL,
PAGE_READWRITE,
0,
0,
NULL);
if (mapping.mappingHandle == NULL)
{
invalidateMapping(mapping);
return false;
}
mapping.mappedView =
MapViewOfFile(
mapping.mappingHandle,
FILE_MAP_ALL_ACCESS,
0,
0,
0);
if (mapping.mappedView == nullptr)
{
invalidateMapping(mapping);
return false;
}
mapping.mappedCapacity = newCapacity;
return true;
}
/*
Function: ensureCapacityForInsert
Description: Ensures that the mapping has space for at least one additional record, growing it if necessary.
Parameters:
- mapping: MappingInfo&, mapping information and handles
Returns:
- bool: True if capacity is available, otherwise false
*/
bool SharedMemory::ensureCapacityForInsert(MappingInfo& mapping)
{
size_t recordCount = getRecordCount(mapping);
size_t capacity = getCapacity(mapping);
if (recordCount < capacity)
{
return true;
}
return resizeMapping(mapping, capacity * config::file::GROWTH_FACTOR);
}
/*
Function: ensureLatestMapping
Description: Remaps the file if another process has resized it.
Parameters:
- mapping: MappingInfo&, mapping information and handles
Returns:
- bool: True if the mapping is valid and up to date, otherwise false
*/
bool SharedMemory::ensureLatestMapping(MappingInfo& mapping)
{
FileHeader* header = getHeader(mapping);
if (header == nullptr)
{
return false;
}
if (header->capacity == mapping.mappedCapacity)
{
return true;
}
if (!UnmapViewOfFile(mapping.mappedView))
{
invalidateMapping(mapping);
return false;
}
mapping.mappedView = nullptr;
CloseHandle(mapping.mappingHandle);
mapping.mappingHandle = NULL;
mapping.mappingHandle =
CreateFileMappingA(
mapping.fileHandle,
NULL,
PAGE_READWRITE,
0,
0,
NULL);
if (mapping.mappingHandle == NULL)
{
invalidateMapping(mapping);
return false;
}
mapping.mappedView =
MapViewOfFile(
mapping.mappingHandle,
FILE_MAP_ALL_ACCESS,
0,
0,
0);
if (mapping.mappedView == nullptr)
{
invalidateMapping(mapping);
return false;
}
header = getHeader(mapping);
if (header == nullptr)
{
invalidateMapping(mapping);
return false;
}
mapping.mappedCapacity = header->capacity;
return true;
}
@@ -0,0 +1,29 @@
/*
File: SharedMemory.h
Description: Declares functions for managing Windows file
mapping and shared memory operations. Provides
utilities for creating, resizing, and closing
mappings, as well as accessing headers and
record data.
Author: Trenser
Created: 10-June-2026
*/
#pragma once
#include <cstddef>
#include "MappingInfo.h"
#include "FileHeader.h"
namespace SharedMemory
{
bool createOrOpenMapping(MappingInfo& mapping);
void closeMapping(MappingInfo& mapping);
bool ensureLatestMapping(MappingInfo& mapping);
bool resizeMapping(MappingInfo& mapping, size_t newCapacity);
FileHeader* getHeader(MappingInfo& mapping);
void* getRecordAddress(MappingInfo& mapping, size_t index);
size_t getRecordCount(MappingInfo& mapping);
void setRecordCount(MappingInfo& mapping, size_t count);
size_t getCapacity(MappingInfo& mapping);
bool ensureCapacityForInsert(MappingInfo& mapping);
};
@@ -0,0 +1,33 @@
/*
File: TrackedRecord.h
Description: Defines the TrackedRecord template structure used
to manage objects with associated record state and
slot index. Supports tracking of CLEAN, NEW_RECORD,
and MODIFIED states for persistence and synchronization.
Author: Trenser
Created: 10-June-2026
*/
#pragma once
#include "RecordState.h"
static const size_t INVALID_SLOT = static_cast<size_t>(-1);
template<typename T>
struct TrackedRecord
{
T* data;
RecordState state;
size_t slotIndex;
TrackedRecord()
: data(nullptr),
state(RecordState::CLEAN),
slotIndex(INVALID_SLOT) {}
TrackedRecord(
T* object,
RecordState recordState,
size_t slot)
: data(object),
state(recordState),
slotIndex(slot) {}
};
@@ -1 +0,0 @@
Place files here.
@@ -12,6 +12,7 @@ Date: 19-May-2026
#include "Enums.h"
class Service;
struct SerializedComboPackage;
class ComboPackage
{
@@ -1,12 +1,13 @@
/*
File: Notification.cpp
Description: Implements the Notification class which represents system notifications in the Vehicle Service Management System.
Provides constructors, accessors, and mutators for notification details such as ID, recipient, title, message, and timestamp.
Provides constructors, accessors, and mutators for notification details such as ID, recipientID, title, message, and timestamp.
Author: Trenser
Date: 19-May-2026
*/
#include <sstream>
#include "SerializedRecords.h"
#include "Notification.h"
#include "StringHelper.h"
#include "Factory.h"
@@ -22,8 +23,8 @@ Returns:
- A new Notification object.
*/
Notification::Notification()
: m_id("NOT" + std::to_string(++m_uid)),
m_recipient(nullptr) {}
: m_id("NOT" + std::to_string(++m_uid)),
m_state(util::State::ACTIVE) {}
/*
Function: Notification
@@ -37,13 +38,14 @@ Parameters:
Returns:
- A new Notification object.
*/
Notification::Notification(const std::string& recipientUserId, User* recipient, const std::string& title, const std::string& message, const util::Timestamp& createdAt)
Notification::Notification(const std::string& recipientUserId, const std::string& title, const std::string& message, const util::Timestamp& createdAt)
: m_id("NOT" + std::to_string(++m_uid)),
m_recipientUserId(recipientUserId),
m_recipient(recipient),
m_title(title),
m_message(message),
m_createdAt(createdAt) {}
m_state(util::State::ACTIVE),
m_createdAt(createdAt) {
}
/*
Function: Notification (parameterized constructor with ID)
@@ -58,13 +60,13 @@ Parameters:
Returns:
- A new Notification object
*/
Notification::Notification(const std::string& id, const std::string& recipientUserId, const std::string& title, const std::string& message, const util::Timestamp& createdAt)
Notification::Notification(const std::string& id, const std::string& recipientUserId, const std::string& title, const std::string& message, const util::Timestamp& createdAt, const util::State& state)
: m_id(id),
m_recipientUserId(recipientUserId),
m_recipient(nullptr),
m_title(title),
m_message(message),
m_createdAt(createdAt)
m_createdAt(createdAt),
m_state(state)
{
int idNumber = util::extractNumber(m_id);
if (idNumber > m_uid)
@@ -79,7 +81,7 @@ Description: Retrieves the unique ID of the notification.
Returns:
- const std::string& representing the notification ID.
*/
const std::string& Notification::getId() const
const std::string& Notification::getId() const
{
return m_id;
}
@@ -95,17 +97,6 @@ const std::string& Notification::getRecipientUserId() const
return m_recipientUserId;
}
/*
Function: getRecipient
Description: Retrieves the pointer to the recipient user.
Returns:
- User* representing the recipient.
*/
User* Notification::getRecipient() const
{
return m_recipient;
}
/*
Function: getTitle
Description: Retrieves the title of the notification.
@@ -139,6 +130,17 @@ const util::Timestamp& Notification::getCreatedAt() const
return m_createdAt;
}
/*
Function: getState
Description: Retrieves the Notification state
Returns:
- const util::Timestamp& representing the creation timestamp.
*/
util::State Notification::getState() const
{
return m_state;
}
/*
Function: setId
Description: Sets the unique ID of the notification.
@@ -165,19 +167,6 @@ void Notification::setRecipientUserId(const std::string& recipientUserId)
m_recipientUserId = recipientUserId;
}
/*
Function: setRecipient
Description: Sets the recipient user pointer for the notification.
Parameters:
- recipient: Pointer to the User object.
Returns:
- void
*/
void Notification::setRecipient(User* recipient)
{
m_recipient = recipient;
}
/*
Function: setTitle
Description: Sets the title of the notification.
@@ -217,71 +206,54 @@ void Notification::setCreatedAt(const util::Timestamp& createdAt)
m_createdAt = createdAt;
}
/*
Function: setState
Description: Sets the Notification state.
Parameters:
- state: Notification state value.
Returns:
- void
*/
void Notification::setState(util::State state)
{
m_state = state;
}
/*
Function: serialize
Description: Serializes the notification into a CSV-formatted string.
Description: Serializes the Notification object into a SerializedNotification record.
Parameters:
- None
Returns:
- std::string: Serialized notification record
- SerializedNotification: Serialized representation of the notification
*/
std::string Notification::serialize() const
SerializedNotification Notification::serialize() const
{
std::ostringstream serializedNotification;
serializedNotification << m_id << ','
<< m_recipientUserId << ','
<< m_title << ','
<< m_message << ','
<< m_createdAt.toString();
return serializedNotification.str();
SerializedNotification serialized = {};
strcpy_s(serialized.id, sizeof(serialized.id), m_id.c_str());
strcpy_s(serialized.recipientUserId, sizeof(serialized.recipientUserId), m_recipientUserId.c_str());
strcpy_s(serialized.title, sizeof(serialized.title), m_title.c_str());
strcpy_s(serialized.message, sizeof(serialized.message), m_message.c_str());
serialized.createdAt = m_createdAt;
serialized.state = m_state;
return serialized;
}
/*
Function: deserialize
Description: Deserializes a CSV-formatted string into a Notification object.
Description: Deserializes a SerializedNotification record into a Notification object.
Parameters:
- record: const std::string&, serialized notification record
- serializedNotification: const SerializedNotification&, serialized notification record
Returns:
- Notification*: Pointer to the deserialized Notification object
Throws:
- std::runtime_error if timestamp parsing fails
*/
Notification* Notification::deserialize(const std::string& record)
Notification* Notification::deserialize(const SerializedNotification& serializedNotification)
{
std::string id, recipientUserId, title, message, createdAtTimestampString;
std::istringstream serializedNotification(record);
getline(serializedNotification, id, ',');
getline(serializedNotification, recipientUserId, ',');
getline(serializedNotification, title, ',');
getline(serializedNotification, message, ',');
getline(serializedNotification, createdAtTimestampString, ',');
util::Timestamp createdAtTimestamp;
try
{
createdAtTimestamp = util::Timestamp::fromString(createdAtTimestampString);
}
catch (...)
{
throw std::runtime_error("Invalid createdAt timestamp");
}
return Factory::getObject<Notification>(
id,
recipientUserId,
title,
message,
createdAtTimestamp
);
}
/*
Function: getHeaders
Description: Retrieves the CSV headers for notification serialization.
Parameters:
- None
Returns:
- std::string: Header string ("ID,RecipientID,Title,Message,Timestamp")
*/
std::string Notification::getHeaders()
{
return "ID,RecipientID,Title,Message,Timestamp";
serializedNotification.id,
serializedNotification.recipientUserId,
serializedNotification.title,
serializedNotification.message,
serializedNotification.createdAt,
serializedNotification.state);
}
@@ -9,8 +9,10 @@ Date: 19-May-2026
#pragma once
#include <string>
#include "Timestamp.h"
#include "Enums.h"
class User;
struct SerializedNotification;
class Notification
{
@@ -18,27 +20,26 @@ private:
static int m_uid;
std::string m_id;
std::string m_recipientUserId;
User* m_recipient;
std::string m_title;
std::string m_message;
util::Timestamp m_createdAt;
util::State m_state;
public:
Notification();
Notification(const std::string& recipientUserId, User* recipient, const std::string& title, const std::string& message, const util::Timestamp& createdAt);
Notification(const std::string& id, const std::string& recipientUserId, const std::string& title, const std::string& message, const util::Timestamp& createdAt);
Notification(const std::string& recipientUserId, const std::string& title, const std::string& message, const util::Timestamp& createdAt);
Notification(const std::string& id, const std::string& recipientUserId, const std::string& title, const std::string& message, const util::Timestamp& createdAt, const util::State& state);
const std::string& getId() const;
const std::string& getRecipientUserId() const;
User* getRecipient() const;
const std::string& getTitle() const;
const std::string& getMessage() const;
const util::Timestamp& getCreatedAt() const;
void setId(const std::string& id);
void setRecipientUserId(const std::string& recipientUserId);
void setRecipient(User* recipient);
void setTitle(const std::string& title);
void setMessage(const std::string& message);
void setCreatedAt(const util::Timestamp& createdAt);
std::string serialize() const;
static Notification* deserialize(const std::string&);
static std::string getHeaders();
util::State getState() const;
void setState(util::State state);
SerializedNotification serialize() const;
static Notification* deserialize(const SerializedNotification&);
};
@@ -8,6 +8,7 @@ Date: 19-May-2026
*/
#include <sstream>
#include "SerializedRecords.h"
#include "User.h"
#include "Notification.h"
#include "Enums.h"
@@ -28,7 +29,8 @@ Returns:
User::User()
: m_id("USR" + std::to_string(++m_uid)),
m_type(util::UserType::CUSTOMER),
m_status(util::State::ACTIVE) {}
m_status(util::State::ACTIVE) {
}
/*
Function: User
@@ -51,7 +53,8 @@ User::User(const std::string& userName, const std::string& password, const std::
m_phone(phone),
m_email(email),
m_type(role),
m_status(util::State::ACTIVE) {}
m_status(util::State::ACTIVE) {
}
/*
Function: User (parameterized constructor with ID)
@@ -86,23 +89,6 @@ User::User(const std::string& userId, const std::string& userName, const std::st
}
}
/*
Function: ~User
Description: Destructor that cleans up dynamically allocated notifications associated with the user.
Parameters:
- None
Returns:
- void
*/
User::~User()
{
auto values = m_notifications.getValues();
for (int index = 0; index < values.getSize(); index++)
{
delete values[index];
}
}
/*
Function: getId
Description: Retrieves the unique ID of the user.
@@ -169,17 +155,6 @@ const std::string& User::getEmail() const
return m_email;
}
/*
Function: getNotifications
Description: Retrieves the map of notifications associated with the user.
Returns:
- util::Map<std::string, Notification*>& representing the notifications.
*/
util::Map<std::string, Notification*>& User::getNotifications()
{
return m_notifications;
}
/*
Function: getUserType
Description: Retrieves the role of the user.
@@ -280,22 +255,6 @@ void User::setEmail(const std::string& email)
m_email = email;
}
/*
Function: addNotification
Description: Adds a new notification to the users notification map.
Parameters:
- notification: Pointer to the Notification object.
Returns:
- void
*/
void User::addNotification(Notification* notification)
{
if (notification)
{
m_notifications.insert(notification->getId(), notification);
}
}
/*
Function: setRole
Description: Sets the role of the user.
@@ -324,68 +283,43 @@ void User::setState(util::State status)
/*
Function: serialize
Description: Serializes the user into a CSV-formatted string.
Description: Serializes the User object into a SerializedUser record.
Parameters:
- None
Returns:
- std::string: Serialized user record
- SerializedUser: Serialized representation of the user
*/
std::string User::serialize() const
SerializedUser User::serialize() const
{
std::ostringstream serializedUser;
serializedUser << m_id << ','
<< m_userName << ','
<< m_password << ','
<< m_name << ','
<< m_phone << ','
<< m_email << ','
<< util::getUserTypeString(m_type) << ','
<< util::getStateString(m_status);
return serializedUser.str();
SerializedUser serialized = {};
strcpy_s(serialized.id, sizeof(serialized.id), m_id.c_str());
strcpy_s(serialized.username, sizeof(serialized.username), m_userName.c_str());
strcpy_s(serialized.password, sizeof(serialized.password), m_password.c_str());
strcpy_s(serialized.name, sizeof(serialized.name), m_name.c_str());
strcpy_s(serialized.phone, sizeof(serialized.phone), m_phone.c_str());
strcpy_s(serialized.email, sizeof(serialized.email), m_email.c_str());
serialized.userType = m_type;
serialized.status = m_status;
return serialized;
}
/*
Function: deserialize
Description: Deserializes a CSV-formatted string into a User object.
Description: Deserializes a SerializedUser record into a User object.
Parameters:
- record: const std::string&, serialized user record
- serializedUser: const SerializedUser&, serialized user record
Returns:
- User*: Pointer to the deserialized User object
*/
User* User::deserialize(const std::string& record)
User* User::deserialize(const SerializedUser& serializedUser)
{
std::string id, name, username, phone, password, email;
std::string userTypeString, stateString;
std::istringstream serializedUser(record);
getline(serializedUser, id, ',');
getline(serializedUser, username, ',');
getline(serializedUser, password, ',');
getline(serializedUser, name, ',');
getline(serializedUser, phone, ',');
getline(serializedUser, email, ',');
getline(serializedUser, userTypeString, ',');
getline(serializedUser, stateString);
util::UserType userType = util::getUserType(userTypeString);
util::State status = util::getState(stateString);
return Factory::getObject<User>(id,
username,
password,
name,
phone,
email,
userType,
status);
}
/*
Function: getHeaders
Description: Retrieves the CSV headers for user serialization.
Parameters:
- None
Returns:
- std::string: Header string ("ID,Username,Password,Name,Phone,Email,UserType,UserStatus")
*/
std::string User::getHeaders()
{
return "ID,Username,Password,Name,Phone,Email,UserType,UserStatus";
return Factory::getObject<User>(
serializedUser.id,
serializedUser.username,
serializedUser.password,
serializedUser.name,
serializedUser.phone,
serializedUser.email,
serializedUser.userType,
serializedUser.status);
}
@@ -14,6 +14,7 @@ Date: 19-May-2026
#include "Enums.h"
class Notification;
struct SerializedUser;
class User : public Observer
{
@@ -25,21 +26,19 @@ private:
std::string m_name;
std::string m_phone;
std::string m_email;
util::Map<std::string, Notification*> m_notifications;
util::UserType m_type;
util::State m_status;
public:
User();
User(const std::string& userName, const std::string& password, const std::string& name, const std::string& phone, const std::string& email, util::UserType role);
User(const std::string& userId, const std::string& userName, const std::string& password, const std::string& name, const std::string& phone, const std::string& email, util::UserType role, util::State status);
~User();
~User() = default;
const std::string& getId() const;
const std::string& getUserName() const;
const std::string& getPassword() const;
const std::string& getName() const;
const std::string& getPhone() const;
const std::string& getEmail() const;
util::Map<std::string, Notification*>& getNotifications();
util::UserType getUserType() const;
util::State getState() const;
void setId(const std::string& id);
@@ -48,10 +47,8 @@ public:
void setName(const std::string& name);
void setPhone(const std::string& phone);
void setEmail(const std::string& email);
void addNotification(Notification* notification) override;
void setRole(util::UserType role);
void setState(util::State status);
std::string serialize() const;
static User* deserialize(const std::string&);
static std::string getHeaders();
SerializedUser serialize() const;
static User* deserialize(const SerializedUser& serializedUser);
};
@@ -12,14 +12,13 @@ Date: 22-May-2026
#include "Config.h"
#include "Enums.h"
#include "Factory.h"
#include "FileManager.h"
#include "InventoryItem.h"
#include "InventoryManagementService.h"
#include "Timestamp.h"
#include "User.h"
#include "Utility.h"
#include "Vector.h"
#include "DataStoreLockGuard.h"
util::Map<std::string, User*> InventoryManagementService::m_observers{};
@@ -90,95 +89,6 @@ void InventoryManagementService::sendLowStockAlerts()
}
}
/*
Function: getObserverIDs
Description: Retrieves the IDs of all observers currently attached to the
InventoryManagementService.
Parameters:
- None
Returns:
- util::Vector<std::string>: Vector of observer user IDs
*/
util::Vector<std::string> InventoryManagementService::getObserverIDs()
{
util::Vector<std::string> observerIDs;
int numberOfObservers = m_observers.getSize();
for (int index = 0; index < numberOfObservers; index++)
{
User* observer = m_observers.getValueAt(index);
if (observer)
{
observerIDs.push_back(observer->getId());
}
}
return observerIDs;
}
/*
Function: loadInventoryItems
Description: Loads inventory items from persistent storage into the datastore.
Uses FileManager to deserialize inventory items from the configured file.
Parameters:
- None
Returns:
- void
*/
void InventoryManagementService::loadInventoryItems()
{
util::FileManager<InventoryItem> inventoryItemFileManager(config::file::INVENTORYITEM_FILE);
auto& inventoryItems = m_dataStore.getInventoryItems();
auto inventoryItemsMap = inventoryItemFileManager.load();
int numberOfInventoryItems = inventoryItemsMap.getSize();
for (int index = 0; index < numberOfInventoryItems; index++)
{
inventoryItems[inventoryItemsMap.getKeyAt(index)] = inventoryItemsMap.getValueAt(index);
}
}
/*
Function: saveInventoryItems
Description: Saves inventory items from the datastore to persistent storage.
Uses FileManager to serialize inventory items into the configured file.
Parameters:
- None
Returns:
- void
*/
void InventoryManagementService::saveInventoryItems()
{
util::FileManager<InventoryItem> inventoryItemFileManager(config::file::INVENTORYITEM_FILE);
auto& inventoryItems = m_dataStore.getInventoryItems();
inventoryItemFileManager.save(inventoryItems);
}
/*
Function: loadObservers
Description: Loads observer IDs from persistent storage and attaches corresponding
users as observers to the InventoryManagementService.
Parameters:
- None
Returns:
- void
*/
void InventoryManagementService::loadObservers()
{
util::loadObservers(config::file::INVENTORYMANAGEMENTOBSERVERS, this, m_dataStore);
}
/*
Function: saveObservers
Description: Saves the current observer IDs of the InventoryManagementService
to persistent storage for future retrieval.
Parameters:
- None
Returns:
- void
*/
void InventoryManagementService::saveObservers()
{
util::saveObservers(config::file::INVENTORYMANAGEMENTOBSERVERS, this);
}
/*
Function: addInventoryItem
Description: Creates a new inventory item using the Factory and inserts it
@@ -271,6 +181,9 @@ Returns:
*/
void InventoryManagementService::attach(User* user)
{
DataStoreLockGuard lock(m_dataStore);
m_observers.clear();
m_observers = m_dataStore.getInventoryManagementObservers();
if (user)
{
const std::string& userID = user->getId();
@@ -279,6 +192,7 @@ void InventoryManagementService::attach(User* user)
m_observers[userID] = user;
}
}
m_dataStore.saveInventoryManagementObservers(m_observers);
}
/*
@@ -291,6 +205,9 @@ Returns:
*/
void InventoryManagementService::detach(User* user)
{
DataStoreLockGuard lock(m_dataStore);
m_observers.clear();
m_observers = m_dataStore.getInventoryManagementObservers();
if (user)
{
const std::string& userID = user->getId();
@@ -299,6 +216,7 @@ void InventoryManagementService::detach(User* user)
m_observers.remove(userID);
}
}
m_dataStore.saveInventoryManagementObservers(m_observers);
}
/*
@@ -315,27 +233,27 @@ Throws:
*/
void InventoryManagementService::sendNotification(User* user, const std::string& title, const std::string& message)
{
if (user)
if (!user)
{
if (m_observers.find(user->getId()) != -1)
{
Notification* notification =
Factory::getObject<Notification>(
user->getId(),
user,
title,
message,
util::Timestamp()
);
if (notification)
{
user->addNotification(notification);
}
else
{
throw std::runtime_error("Failed to create notification");
}
}
return;
}
DataStoreLockGuard lock(m_dataStore);
m_observers = m_dataStore.getInventoryManagementObservers();
if (m_observers.find(user->getId()) == -1)
{
return;
}
Notification* notification = Factory::getObject<Notification>(
user->getId(),
title,
message,
util::Timestamp());
if (!notification)
{
throw std::runtime_error("Failed to create notification");
}
auto& trackedNotificationsMap = m_dataStore.getNotifications();
trackedNotificationsMap.insert(notification->getId(), util::createNewRecord(notification));
m_dataStore.saveNotifications();
}
@@ -21,7 +21,6 @@ class InventoryManagementService : public NotificationManagementService
private:
DataStore& m_dataStore;
static util::Map<std::string, User*> m_observers;
util::Vector<std::string> getObserverIDs() override;
public:
InventoryManagementService() : m_dataStore(DataStore::getInstance()) {}
util::Map<std::string, InventoryItem*> getInventoryItems();
@@ -33,8 +32,4 @@ public:
void sendNotification(User* user, const std::string& title, const std::string& message) override;
void attach(User* user) override;
void detach(User* user) override;
void loadInventoryItems();
void saveInventoryItems();
void loadObservers();
void saveObservers();
};
@@ -1 +0,0 @@
#include "NotificationManagementService.h"
@@ -19,5 +19,4 @@ public:
virtual void sendNotification(User* recipient, const std::string& title, const std::string& message) = 0;
virtual void attach(User* user) = 0;
virtual void detach(User* user) = 0;
virtual util::Vector<std::string> getObserverIDs() = 0;
};
@@ -11,7 +11,6 @@ Date: 20-May-2026
#include "Config.h"
#include "Enums.h"
#include "Factory.h"
#include "FileManager.h"
#include "InventoryItem.h"
#include "Invoice.h"
#include "JobCard.h"
@@ -21,6 +20,7 @@ Date: 20-May-2026
#include "Timestamp.h"
#include "User.h"
#include "Utility.h"
#include "DataStoreLockGuard.h"
util::Map<std::string, User*> PaymentManagementService::m_observers{};
@@ -34,6 +34,9 @@ Returns:
*/
void PaymentManagementService::attach(User* user)
{
DataStoreLockGuard lock(m_dataStore);
m_observers.clear();
m_observers = m_dataStore.getPaymentManagementObservers();
if (user)
{
const std::string& userID = user->getId();
@@ -42,6 +45,7 @@ void PaymentManagementService::attach(User* user)
m_observers[userID] = user;
}
}
m_dataStore.savePaymentManagementObservers(m_observers);
}
/*
@@ -54,6 +58,9 @@ Returns:
*/
void PaymentManagementService::detach(User* user)
{
DataStoreLockGuard lock(m_dataStore);
m_observers.clear();
m_observers = m_dataStore.getPaymentManagementObservers();
if (user)
{
const std::string& userID = user->getId();
@@ -62,6 +69,7 @@ void PaymentManagementService::detach(User* user)
m_observers.remove(userID);
}
}
m_dataStore.savePaymentManagementObservers(m_observers);
}
/*
@@ -78,28 +86,28 @@ Throws:
*/
void PaymentManagementService::sendNotification(User* user, const std::string& title, const std::string& message)
{
if (user)
if (!user)
{
if (m_observers.find(user->getId()) != -1)
{
Notification* notification =
Factory::getObject<Notification>(
user->getId(),
user,
title,
message,
util::Timestamp()
);
if (notification)
{
user->addNotification(notification);
}
else
{
throw std::runtime_error("Failed to create notification");
}
}
return;
}
DataStoreLockGuard lock(m_dataStore);
m_observers = m_dataStore.getPaymentManagementObservers();
if (m_observers.find(user->getId()) == -1)
{
return;
}
Notification* notification = Factory::getObject<Notification>(
user->getId(),
title,
message,
util::Timestamp());
if (!notification)
{
throw std::runtime_error("Failed to create notification");
}
auto& trackedNotificationsMap = m_dataStore.getNotifications();
trackedNotificationsMap.insert(notification->getId(), util::createNewRecord(notification));
m_dataStore.saveNotifications();
}
/*
@@ -140,123 +148,6 @@ void PaymentManagementService::sendPaymentReminders()
}
}
/*
Function: getObserverIDs
Description: Retrieves the IDs of all observers currently attached to the
PaymentManagementService.
Parameters:
- None
Returns:
- util::Vector<std::string>: Vector of observer user IDs
*/
util::Vector<std::string> PaymentManagementService::getObserverIDs()
{
util::Vector<std::string> observerIDs;
int numberOfObservers = m_observers.getSize();
for (int index = 0; index < numberOfObservers; index++)
{
User* observer = m_observers.getValueAt(index);
if (observer)
{
observerIDs.push_back(observer->getId());
}
}
return observerIDs;
}
/*
Function: loadInvoices
Description: Loads invoices from persistent storage into the datastore.
Validates associated service bookings and inventory parts before
attaching them to each invoice. Throws exceptions if invalid IDs
are encountered.
Parameters:
- None
Returns:
- void
Throws:
- std::runtime_error if a booking ID or part ID is invalid
*/
void PaymentManagementService::loadInvoices()
{
util::FileManager<Invoice> invoiceFileManager(config::file::INVOICE_FILE);
auto& invoices = m_dataStore.getInvoices();
auto& serviceBookings = m_dataStore.getServiceBookings();
auto& inventoryItems = m_dataStore.getInventoryItems();
auto invoicesMap = invoiceFileManager.load();
for (int invoiceIndex = 0; invoiceIndex < invoicesMap.getSize(); invoiceIndex++)
{
Invoice* invoice = invoicesMap.getValueAt(invoiceIndex);
int bookingIndex = serviceBookings.find(invoice->getBookingId());
if (bookingIndex == -1)
{
throw std::runtime_error("Invalid Booking ID");
}
ServiceBooking* booking = serviceBookings.getValueAt(bookingIndex);
invoice->setBooking(booking);
util::Map<std::string, InventoryItem*> invoiceParts;
auto& partIDs = invoice->getPartIDs();
for (int partIndex = 0; partIndex < partIDs.getSize(); partIndex++)
{
const std::string& partID = partIDs[partIndex];
int inventoryIndex = inventoryItems.find(partID);
if (inventoryIndex == -1)
{
throw std::runtime_error("Invalid Part ID");
}
invoiceParts[partID] = inventoryItems.getValueAt(inventoryIndex);
}
invoice->setParts(invoiceParts);
invoices[invoice->getId()] = invoice;
}
}
/*
Function: saveInvoices
Description: Saves invoices from the datastore to persistent storage.
Uses FileManager to serialize invoices into the configured file.
Parameters:
- None
Returns:
- void
*/
void PaymentManagementService::saveInvoices()
{
util::FileManager<Invoice> invoiceFileManager(config::file::INVOICE_FILE);
auto& invoices = m_dataStore.getInvoices();
invoiceFileManager.save(invoices);
}
/*
Function: loadObservers
Description: Loads observer IDs from persistent storage and attaches corresponding
users as observers to the PaymentManagementService.
Parameters:
- None
Returns:
- void
Throws:
- std::runtime_error if an observer ID is invalid (not found in datastore)
*/
void PaymentManagementService::loadObservers()
{
util::loadObservers(config::file::PAYMENTMANAGEMENTOBSERVERS, this, m_dataStore);
}
/*
Function: saveObservers
Description: Saves the current observer IDs of the PaymentManagementService
to persistent storage for future retrieval.
Parameters:
- None
Returns:
- void
*/
void PaymentManagementService::saveObservers()
{
util::saveObservers(config::file::PAYMENTMANAGEMENTOBSERVERS, this);
}
/*
Function: createInventoryItemsMap (static helper)
Description: Builds a map of inventory items required for a given service and adds them to the bookings inventory map.
@@ -294,7 +185,7 @@ void PaymentManagementService::generateInvoice(ServiceBooking* booking)
{
throw std::runtime_error("Invoice generation failed: booking is null.");
}
double totalLabourCost = 0, totalPartsCost = 0, totalServiceCost = 0;
double totalLaborCost = 0, totalPartsCost = 0, totalServiceCost = 0;
double discountPercentage = booking->getDiscountPercentage();
std::string bookingID = booking->getId();
util::Map<std::string, Service*> servicesInTheBookedService = booking->getServices();
@@ -303,9 +194,10 @@ void PaymentManagementService::generateInvoice(ServiceBooking* booking)
for (int iterator = 0; iterator < currentJobCards.getSize(); iterator++)
{
JobCard* currentJobCard = currentJobCards.getValueAt(iterator);
if (currentJobCard->getBookingId() == bookingID && currentJobCard->getStatus() != util::ServiceJobStatus::COMPLETED)
util::ServiceJobStatus currentJobCardStatus = currentJobCard->getStatus();
if (currentJobCard->getBookingId() == bookingID && currentJobCardStatus != util::ServiceJobStatus::CANCELLED && currentJobCardStatus != util::ServiceJobStatus::COMPLETED)
{
throw std::runtime_error("Invoice generation failed: not all job cards are completed for booking '" + bookingID + "'.");
throw std::runtime_error("Invoice generation failed: Not all job cards are completed for booking '" + bookingID + "'.");
}
}
for (int iterator = 0; iterator < servicesInTheBookedService.getSize(); iterator++)
@@ -314,13 +206,13 @@ void PaymentManagementService::generateInvoice(ServiceBooking* booking)
if (currentService)
{
createInventoryItemsMap(completeInventoryItemMapOfBooking, currentService);
totalLabourCost += currentService->getLaborCost();
totalLaborCost += currentService->getLaborCost();
totalPartsCost += util::calculatePartsCost(currentService);
}
}
totalServiceCost = totalLabourCost + totalPartsCost;
totalServiceCost = totalLaborCost + totalPartsCost;
totalServiceCost -= (totalServiceCost * (discountPercentage / 100));
Invoice* invoice = Factory::getObject<Invoice>(bookingID, booking, util::Timestamp(), totalLabourCost, completeInventoryItemMapOfBooking, totalPartsCost, discountPercentage, totalServiceCost, util::Timestamp(), util::PaymentMode::NOTSET, util::PaymentStatus::PENDING);
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);
}
@@ -367,15 +259,15 @@ void PaymentManagementService::completePayment(const std::string& invoiceID, uti
if (invoiceIndex != -1)
{
Invoice* invoice = currentInvoices.getValueAt(invoiceIndex);
if (invoice && invoice->getStatus() != util::PaymentStatus::COMPLETED)
if (invoice && invoice->getStatus() != util::PaymentStatus::PAID)
{
User* currentUser = invoice->getBooking()->getCustomer();
invoice->setPaymentMethod(paymentMode);
invoice->setPaymentDate(util::Timestamp());
invoice->setStatus(util::PaymentStatus::COMPLETED);
invoice->setStatus(util::PaymentStatus::PAID);
std::string title, message;
title = "Payment successful";
message = "Payment successful for invoice ID " + invoiceID;
message = "Payment successful for Invoice ID " + invoiceID;
sendNotification(currentUser, title, message);
}
}
@@ -383,4 +275,48 @@ void PaymentManagementService::completePayment(const std::string& invoiceID, uti
{
throw std::runtime_error("Payment failed: invalid invoice ID.");
}
}
/*
Function: getAllInvoice
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*>& PaymentManagementService::getAllInvoices()
{
return m_dataStore.getInvoices();
}
/*
Function: confirmPayment
Description: Confirms payment for a specific invoice. Updates payment date and status,
then sends a notification to the customer.
Parameters:
- invoiceID: std::string, ID of the invoice to confirm
Returns:
- void
Throws:
- std::runtime_error if the invoice ID is invalid
*/
void PaymentManagementService::confirmPayment(const std::string& invoiceID)
{
auto& currentInvoices = m_dataStore.getInvoices();
int invoiceIndex = currentInvoices.find(invoiceID);
if (invoiceIndex == -1)
{
throw std::runtime_error("Payment confirmation failed: invalid invoice ID.");
}
Invoice* invoice = currentInvoices.getValueAt(invoiceIndex);
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);
std::string title = "Payment Confirmed";
std::string message = "Payment Confirmed for Invoice ID " + invoiceID;
sendNotification(currentUser, title, message);
}
@@ -22,18 +22,15 @@ class PaymentManagementService : public NotificationManagementService
private:
DataStore& m_dataStore;
static util::Map<std::string, User*> m_observers;
util::Vector<std::string> getObserverIDs() override;
public:
PaymentManagementService() : m_dataStore(DataStore::getInstance()) {}
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();
void confirmPayment(const std::string& invoiceID);
void sendPaymentReminders();
void sendNotification(User* user, const std::string& title, const std::string& message) override;
void attach(User* user) override;
void detach(User* user) override;
void loadInvoices();
void saveInvoices();
void loadObservers();
void saveObservers();
};
@@ -14,7 +14,6 @@ Date:19-May-2026
#include "DataStore.h"
#include "Enums.h"
#include "Factory.h"
#include "FileManager.h"
#include "InventoryItem.h"
#include "JobCard.h"
#include "NotificationManagementService.h"
@@ -26,6 +25,7 @@ Date:19-May-2026
#include "User.h"
#include "UserManagementService.h"
#include "Utility.h"
#include "DataStoreLockGuard.h"
/*
Function: purchaseService
@@ -122,6 +122,9 @@ Returns:
*/
void ServiceManagementService::attach(User* user)
{
DataStoreLockGuard lock(m_dataStore);
m_observers.clear();
m_observers = m_dataStore.getServiceManagementObservers();
if (user)
{
const std::string& userID = user->getId();
@@ -130,6 +133,7 @@ void ServiceManagementService::attach(User* user)
m_observers[userID] = user;
}
}
m_dataStore.saveServiceManagementObservers(m_observers);
}
/*
@@ -142,6 +146,9 @@ Returns:
*/
void ServiceManagementService::detach(User* user)
{
DataStoreLockGuard lock(m_dataStore);
m_observers.clear();
m_observers = m_dataStore.getServiceManagementObservers();
if (user)
{
const std::string& userID = user->getId();
@@ -150,6 +157,7 @@ void ServiceManagementService::detach(User* user)
m_observers.remove(userID);
}
}
m_dataStore.saveServiceManagementObservers(m_observers);
}
/*
@@ -166,345 +174,28 @@ Throws:
*/
void ServiceManagementService::sendNotification(User* user, const std::string& title, const std::string& message)
{
if (user)
{
if (m_observers.find(user->getId()) != -1)
{
Notification* notification =
Factory::getObject<Notification>(
user->getId(),
user,
title,
message,
util::Timestamp()
);
if (notification)
{
user->addNotification(notification);
}
else
{
throw std::runtime_error("Failed to create notification");
}
}
}
}
/*
Function: getObserverIDs
Description: Retrieves the IDs of all observers currently attached to the
ServiceManagementService.
Parameters:
- None
Returns:
- util::Vector<std::string>: Vector of observer user IDs
*/
util::Vector<std::string> ServiceManagementService::getObserverIDs()
{
util::Vector<std::string> observerIDs;
int numberOfObservers = m_observers.getSize();
for (int index = 0; index < numberOfObservers; index++)
{
User* observer = m_observers.getValueAt(index);
if (observer)
{
observerIDs.push_back(observer->getId());
}
}
return observerIDs;
}
/*
Function: loadServices
Description: Loads services from persistent storage into the datastore.
Validates required inventory items and attaches them to each service.
Parameters:
- None
Returns:
- void
Throws:
- std::runtime_error if an inventory item ID is invalid
*/
void ServiceManagementService::loadServices()
{
util::FileManager<Service> serviceFileManager(config::file::SERVICE_FILE);
auto& services = m_dataStore.getServices();
auto& inventoryItems = m_dataStore.getInventoryItems();
auto servicesMap = serviceFileManager.load();
for (int serviceIndex = 0; serviceIndex < servicesMap.getSize(); serviceIndex++)
if (!user)
{
Service* service = servicesMap.getValueAt(serviceIndex);
services[service->getId()] = service;
util::Map<std::string, InventoryItem*> inventoryItemsMap;
auto& inventoryItemIDs = service->getRequiredInventoryItemIDs();
for (int inventoryItemIndex = 0; inventoryItemIndex < inventoryItemIDs.getSize(); inventoryItemIndex++)
{
const std::string& inventoryItemID = inventoryItemIDs[inventoryItemIndex];
int index = inventoryItems.find(inventoryItemID);
if (index == -1)
{
throw std::runtime_error("Invalid Inventory Item ID");
}
inventoryItemsMap[inventoryItemID] = inventoryItems.getValueAt(index);
}
service->setRequiredInventoryItems(inventoryItemsMap);
return;
}
}
/*
Function: saveServices
Description: Saves services from the datastore to persistent storage.
Uses FileManager to serialize services into the configured file.
Parameters:
- None
Returns:
- void
*/
void ServiceManagementService::saveServices()
{
util::FileManager<Service> serviceFileManager(config::file::SERVICE_FILE);
auto& services = m_dataStore.getServices();
serviceFileManager.save(services);
}
/*
Function: loadComboPackages
Description: Loads combo packages from persistent storage into the datastore.
Validates associated services and attaches them to each package.
Parameters:
- None
Returns:
- void
Throws:
- std::runtime_error if a service ID is invalid
*/
void ServiceManagementService::loadComboPackages()
{
util::FileManager<ComboPackage> comboPackageFileManager(config::file::COMBOPACKAGE_FILE);
auto& comboPackages = m_dataStore.getComboPackages();
auto& services = m_dataStore.getServices();
auto comboPackagesMap = comboPackageFileManager.load();
for (int packageIndex = 0; packageIndex < comboPackagesMap.getSize(); packageIndex++)
DataStoreLockGuard lock(m_dataStore);
m_observers = m_dataStore.getServiceManagementObservers();
if (m_observers.find(user->getId()) == -1)
{
ComboPackage* comboPackage = comboPackagesMap.getValueAt(packageIndex);
util::Map<std::string, Service*> packageServices;
auto& serviceIDs = comboPackage->getServiceIDs();
for (int serviceIndex = 0; serviceIndex < serviceIDs.getSize(); serviceIndex++)
{
const std::string& serviceID = serviceIDs[serviceIndex];
int serviceMapIndex = services.find(serviceID);
if (serviceMapIndex == -1)
{
throw std::runtime_error("Invalid Service ID");
}
packageServices[serviceID] = services.getValueAt(serviceMapIndex);
}
comboPackage->setServices(packageServices);
comboPackages[comboPackage->getId()] = comboPackage;
return;
}
}
/*
Function: saveComboPackages
Description: Saves combo packages from the datastore to persistent storage.
Uses FileManager to serialize combo packages into the configured file.
Parameters:
- None
Returns:
- void
*/
void ServiceManagementService::saveComboPackages()
{
util::FileManager<ComboPackage> comboPackageFileManager(config::file::COMBOPACKAGE_FILE);
auto& comboPackages = m_dataStore.getComboPackages();
comboPackageFileManager.save(comboPackages);
}
/*
Function: loadServiceBookings
Description: Loads service bookings from persistent storage into the datastore.
Validates associated services, customers, and technicians before
attaching them to each booking.
Parameters:
- None
Returns:
- void
Throws:
- std::runtime_error if a service ID, customer ID, or technician ID is invalid
- std::runtime_error if a user is not of the expected type (customer/technician)
*/
void ServiceManagementService::loadServiceBookings()
{
util::FileManager<ServiceBooking> bookingFileManager(config::file::SERVICEBOOKING_FILE);
auto& serviceBookings = m_dataStore.getServiceBookings();
auto& services = m_dataStore.getServices();
auto& users = m_dataStore.getUsers();
auto bookingsMap = bookingFileManager.load();
for (int bookingIndex = 0; bookingIndex < bookingsMap.getSize(); bookingIndex++)
Notification* notification = Factory::getObject<Notification>(
user->getId(),
title,
message,
util::Timestamp());
if (!notification)
{
ServiceBooking* booking = bookingsMap.getValueAt(bookingIndex);
util::Map<std::string, Service*> bookingServices;
auto& serviceIDs = booking->getServiceIDs();
for (int serviceIndex = 0; serviceIndex < serviceIDs.getSize(); serviceIndex++)
{
const std::string& serviceID = serviceIDs[serviceIndex];
int serviceMapIndex = services.find(serviceID);
if (serviceMapIndex == -1)
{
throw std::runtime_error("Invalid Service ID");
}
bookingServices[serviceID] = services.getValueAt(serviceMapIndex);
}
booking->setServices(bookingServices);
int customerIndex = users.find(booking->getCustomerId());
if (customerIndex == -1)
{
throw std::runtime_error("Invalid Customer ID");
}
User* customer = users.getValueAt(customerIndex);
if (customer->getUserType() != util::UserType::CUSTOMER)
{
throw std::runtime_error("User is not a customer");
}
booking->setCustomer(customer);
const std::string& technicianId = booking->getAssignedTechnicianId();
if (!technicianId.empty())
{
int technicianIndex = users.find(technicianId);
if (technicianIndex == -1)
{
throw std::runtime_error("Invalid Technician ID");
}
User* technician = users.getValueAt(technicianIndex);
if (technician->getUserType() != util::UserType::TECHNICIAN)
{
throw std::runtime_error("User is not a technician");
}
booking->setAssignedTechnician(technician);
}
serviceBookings[booking->getId()] = booking;
throw std::runtime_error("Failed to create notification");
}
}
/*
Function: saveServiceBookings
Description: Saves service bookings from the datastore to persistent storage.
Uses FileManager to serialize bookings into the configured file.
Parameters:
- None
Returns:
- void
*/
void ServiceManagementService::saveServiceBookings()
{
util::FileManager<ServiceBooking> bookingFileManager(config::file::SERVICEBOOKING_FILE);
auto& serviceBookings = m_dataStore.getServiceBookings();
bookingFileManager.save(serviceBookings);
}
/*
Function: loadJobCards
Description: Loads job cards from persistent storage into the datastore.
Validates associated bookings, services, and technicians before
attaching them to each job card.
Parameters:
- None
Returns:
- void
Throws:
- std::runtime_error if a booking ID, service ID, or technician ID is invalid
- std::runtime_error if a service does not belong to the booking
- std::runtime_error if a user is not a technician
*/
void ServiceManagementService::loadJobCards()
{
util::FileManager<JobCard> jobCardFileManager(config::file::JOBCARD_FILE);
auto& jobCards = m_dataStore.getJobCards();
auto& serviceBookings = m_dataStore.getServiceBookings();
auto& services = m_dataStore.getServices();
auto& users = m_dataStore.getUsers();
auto jobCardsMap = jobCardFileManager.load();
for (int jobCardIndex = 0; jobCardIndex < jobCardsMap.getSize(); jobCardIndex++)
{
JobCard* jobCard = jobCardsMap.getValueAt(jobCardIndex);
int bookingIndex = serviceBookings.find(jobCard->getBookingId());
if (bookingIndex == -1)
{
throw std::runtime_error("Invalid Booking ID");
}
ServiceBooking* booking = serviceBookings.getValueAt(bookingIndex);
jobCard->setBooking(booking);
int serviceIndex = services.find(jobCard->getServiceId());
if (serviceIndex == -1)
{
throw std::runtime_error("Invalid Service ID");
}
Service* service = services.getValueAt(serviceIndex);
if (booking->getServices().find(jobCard->getServiceId()) == -1)
{
throw std::runtime_error("Service does not belong to booking");
}
jobCard->setService(service);
int technicianIndex = users.find(jobCard->getTechnicianId());
if (technicianIndex == -1)
{
throw std::runtime_error("Invalid Technician ID");
}
User* technician = users.getValueAt(technicianIndex);
if (technician->getUserType() != util::UserType::TECHNICIAN)
{
throw std::runtime_error("User is not a technician");
}
jobCard->setTechnician(technician);
jobCards[jobCard->getId()] = jobCard;
}
}
/*
Function: saveJobCards
Description: Saves job cards from the datastore to persistent storage.
Uses FileManager to serialize job cards into the configured file.
Parameters:
- None
Returns:
- void
*/
void ServiceManagementService::saveJobCards()
{
util::FileManager<JobCard> jobCardFileManager(config::file::JOBCARD_FILE);
auto& jobCards = m_dataStore.getJobCards();
jobCardFileManager.save(jobCards);
}
/*
Function: loadObservers
Description: Loads observer IDs from persistent storage and attaches corresponding
users as observers to the ServiceManagementService.
Parameters:
- None
Returns:
- void
Throws:
- std::runtime_error if an observer ID is invalid (not found in datastore)
*/
void ServiceManagementService::loadObservers()
{
util::loadObservers(config::file::SERVICEMANAGEMENTOBSERVERS, this, m_dataStore);
}
/*
Function: saveObservers
Description: Saves the current observer IDs of the ServiceManagementService
to persistent storage for future retrieval.
Parameters:
- None
Returns:
- void
*/
void ServiceManagementService::saveObservers()
{
util::saveObservers(config::file::SERVICEMANAGEMENTOBSERVERS, this);
auto& trackedNotificationsMap = m_dataStore.getNotifications();
trackedNotificationsMap.insert(notification->getId(), util::createNewRecord(notification));
m_dataStore.saveNotifications();
}
/*
@@ -543,50 +234,65 @@ static void restoreInventory(ServiceBooking* booking)
/*
Function: processBookingCancellation
Description: Cancels jobs and updates the status of a given booking. Sends notifications to the
specified user, resets technician assignment if needed, and restores inventory items.
Parameter: ServiceBooking* booking - Pointer to the booking being cancelled
util::ServiceJobStatus newServiceBookingStatus - New status to assign to the booking
const std::string& notificationTitle - Title of the booking cancellation notification
const std::string& notificationMessage - Message body of the booking cancellation notification
User* notifyUser - User to notify about the cancellation
util::ServiceJobStatus jobCardStatus - New status to assign to associated job cards
const std::string& jobNotificationTitle - Title of the job cancellation notification
const std::string& jobNotificationMessage - Message body of the job cancellation notification
util::Map<std::string, JobCard*>& jobs - Collection of job cards to update
ServiceManagementService& currentService - Reference to the service for sending notifications
Description: Handles cancellation or reassignment of a service booking based on user type.
Cancels associated job cards, updates booking status, clears technician assignments,
restores inventory, and sends appropriate notifications.
Parameters:
ServiceBooking* booking - The booking to cancel or reset
util::Map<std::string, JobCard*>& jobs - Collection of job cards to update
ServiceManagementService& currentService - Service layer for notifications
util::UserType userType - Type of user initiating cancellation (CUSTOMER or TECHNICIAN)
Return type: void
*/
static void processBookingCancellation(ServiceBooking* booking,
util::ServiceJobStatus newServiceBookingStatus,
const std::string& notificationTitle,
const std::string& notificationMessage,
User* notifyUser,
util::ServiceJobStatus jobCardStatus,
const std::string& jobNotificationTitle,
const std::string& jobNotificationMessage,
util::Map<std::string, JobCard*>& jobs, ServiceManagementService& currentService)
util::Map<std::string, JobCard*>& jobs,
ServiceManagementService& currentService,
util::UserType userType)
{
if (!booking || !notifyUser)
if (!booking)
{
return;
}
for (int jobIterator = 0; jobIterator < jobs.getSize(); ++jobIterator)
{
JobCard* jobCard = jobs.getValueAt(jobIterator);
if (jobCard && jobCard->getBookingId() == booking->getId())
if (!jobCard || jobCard->getBookingId() != booking->getId() || jobCard->getStatus() == util::ServiceJobStatus::CANCELLED)
{
jobCard->setStatus(jobCardStatus);
currentService.sendNotification(notifyUser, jobNotificationTitle, jobNotificationMessage);
continue;
}
jobCard->setStatus(util::ServiceJobStatus::CANCELLED);
if (userType == util::UserType::CUSTOMER)
{
if (User* technician = booking->getAssignedTechnician())
{
const std::string jobTitle = "Job Cancelled";
const std::string jobMessage = "Your job card has been cancelled and the inventory has been restocked.";
currentService.sendNotification(technician, jobTitle, jobMessage);
}
}
}
booking->setStatus(newServiceBookingStatus);
currentService.sendNotification(notifyUser, notificationTitle, notificationMessage);
if (newServiceBookingStatus == util::ServiceJobStatus::PENDING)
if (userType == util::UserType::CUSTOMER)
{
booking->setAssignedTechnician(nullptr);
booking->setAssignedTechnicianId("");
booking->setStatus(util::ServiceJobStatus::CANCELLED);
if (User* technician = booking->getAssignedTechnician())
{
const std::string title = "Customer Service Cancelled";
const std::string message = "Your assigned job card has been cancelled and the inventory has been restocked.";
currentService.sendNotification(technician, title, message);
}
}
else if (userType == util::UserType::TECHNICIAN)
{
booking->setStatus(util::ServiceJobStatus::PENDING);
if (User* customer = booking->getCustomer())
{
const std::string title = "Technician Unavailable";
const std::string message = "Your booking has been reset to pending and we will reassign a new technician shortly.";
currentService.sendNotification(customer, title, message);
}
}
booking->setAssignedTechnician(nullptr);
booking->setAssignedTechnicianId("");
restoreInventory(booking);
}
@@ -624,21 +330,13 @@ void ServiceManagementService::cancelCustomerServiceBookings(const std::string&
{
continue;
}
if (booking->getStatus() != util::ServiceJobStatus::PENDING && booking->getStatus() != util::ServiceJobStatus::STARTED)
if (booking->getStatus() != util::ServiceJobStatus::PENDING &&
booking->getStatus() != util::ServiceJobStatus::STARTED &&
booking->getStatus() != util::ServiceJobStatus::IN_PROGRESS)
{
continue;
}
User* assignedTechnician = booking->getAssignedTechnician();
std::string titleToTechnician = "Customer Service Cancelled";
std::string messageToTechnician = "The customer has cancelled their service booking. Your assigned job card has been cancelled and the inventory has been restocked.";
std::string jobTitle = "Job Cancelled";
std::string jobMessage = "The job has been cancelled. Your job card has been cancelled and the inventory has been restocked.";
processBookingCancellation(booking,
util::ServiceJobStatus::CANCELLED,
titleToTechnician, messageToTechnician, assignedTechnician,
util::ServiceJobStatus::CANCELLED,
jobTitle, jobMessage, jobs, *this
);
processBookingCancellation(booking, jobs, *this, util::UserType::CUSTOMER);
}
}
@@ -676,7 +374,9 @@ void ServiceManagementService::cancelTechnicianJobs(const std::string& technicia
{
continue;
}
if (booking->getStatus() != util::ServiceJobStatus::PENDING && booking->getStatus() != util::ServiceJobStatus::STARTED)
if (booking->getStatus() != util::ServiceJobStatus::PENDING &&
booking->getStatus() != util::ServiceJobStatus::STARTED &&
booking->getStatus() != util::ServiceJobStatus::IN_PROGRESS)
{
continue;
}
@@ -685,14 +385,7 @@ void ServiceManagementService::cancelTechnicianJobs(const std::string& technicia
{
continue;
}
std::string title = "Technician Unavailable";
std::string message = "Your assigned technician is no longer available. Your booking has been reset to pending, and we will reassign a new technician shortly.";
processBookingCancellation(booking,
util::ServiceJobStatus::PENDING,
title, message, customer,
util::ServiceJobStatus::CANCELLED,
title, message, jobs, *this
);
processBookingCancellation(booking, jobs, *this, util::UserType::TECHNICIAN);
}
}
@@ -1080,7 +773,7 @@ static bool hasCompletedAllJobs(std::string bookingId, util::Map<std::string, Jo
JobCard* currentJob = currentAssignedJobs.getValueAt(iterator);
if (currentJob->getBookingId() == bookingId)
{
if (currentJob->getStatus() == util::ServiceJobStatus::STARTED)
if (currentJob->getStatus() != util::ServiceJobStatus::COMPLETED && currentJob->getStatus() != util::ServiceJobStatus::CANCELLED)
{
return false;
}
@@ -1090,18 +783,19 @@ static bool hasCompletedAllJobs(std::string bookingId, util::Map<std::string, Jo
}
/*
Function: completeJob
Description: Marks a job card as completed for the authenticated technician.
If all job cards in the booking are completed, marks the booking as completed
and generates an invoice.
Function: updateJobStatus
Description:
Updates the status of a job card assigned to the currently authenticated technician.
- If the job is STARTED, it moves to IN_PROGRESS.
- If the job is IN_PROGRESS, it moves to COMPLETED.
When all jobs in a service booking are completed, the booking status is updated,
an invoice is generated, and a notification is sent to the customer.
Parameters:
- jobID: std::string, ID of the job card
- jobID: const std::string&, unique identifier of the job card to update.
Returns:
- void
Throws:
- std::runtime_error if technician is not authenticated, job card not found, or job already completed
- void
*/
void ServiceManagementService::completeJob(const std::string& jobID)
void ServiceManagementService::updateJobStatus(const std::string& jobID)
{
AuthenticationManagementService authenticationManagementService;
PaymentManagementService paymentManagementService;
@@ -1126,26 +820,30 @@ void ServiceManagementService::completeJob(const std::string& jobID)
}
if (currentJob->getStatus() == util::ServiceJobStatus::STARTED)
{
currentJob->setStatus(util::ServiceJobStatus::COMPLETED);
currentJob->setStatus(util::ServiceJobStatus::IN_PROGRESS);
jobStatusUpdated = true;
}
else if (currentJob->getStatus() == util::ServiceJobStatus::IN_PROGRESS)
{
currentJob->setStatus(util::ServiceJobStatus::COMPLETED);
jobStatusUpdated = true;
serviceBookingCompleted = hasCompletedAllJobs(currentJob->getBookingId(), currentAssignedJobs);
if (serviceBookingCompleted)
{
currentJob->getBooking()->setStatus(util::ServiceJobStatus::COMPLETED);
paymentManagementService.generateInvoice(currentJob->getBooking());
std::string title = "Service Booking completed. Invoice Generated.";
std::string message = "Services completed for the booking and invoice generated.";
sendNotification(currentJob->getBooking()->getCustomer(), title, message);
}
}
}
else
{
throw std::runtime_error("Failed to complete the job, some error occurred or job already completed.");
throw std::runtime_error("Failed to update job status. Job may already be completed.");
}
if (!jobStatusUpdated)
{
throw std::runtime_error("Failed to complete the job, some error occurred or job already completed.");
}
serviceBookingCompleted = hasCompletedAllJobs(currentJob->getBookingId(), currentAssignedJobs);
if (serviceBookingCompleted)
{
currentJob->getBooking()->setStatus(util::ServiceJobStatus::COMPLETED);
paymentManagementService.generateInvoice(currentJob->getBooking());
std::string title = "Service Booking completed,Invoice Generated.";
std::string message = "Services completed for the booking and invoice generated.";
sendNotification(currentJob->getBooking()->getCustomer(), title, message);
throw std::runtime_error("Failed to update job status. Job may already be completed.");
}
}
@@ -23,7 +23,6 @@ class ServiceManagementService : public NotificationManagementService
private:
DataStore& m_dataStore;
static util::Map<std::string, User*> m_observers;
util::Vector<std::string> getObserverIDs() override;
public:
ServiceManagementService() : m_dataStore(DataStore::getInstance()) {}
util::Map<std::string, Service*> getServices();
@@ -37,7 +36,7 @@ public:
void createService(const std::string& name, const util::Vector<std::string>& inventoryItemIDs, double laborCost);
void removeService(const std::string& serviceID);
util::Map<std::string, JobCard*> getJobCards(const std::string& technicianID);
void completeJob(const std::string& jobID);
void updateJobStatus(const std::string& jobID);
void cancelCustomerServiceBookings(const std::string& customerID);
void cancelTechnicianJobs(const std::string& technicianID);
void createComboPackage(const std::string& packageName, const util::Vector<std::string>& serviceIDs, double discountPercentage);
@@ -45,14 +44,4 @@ public:
void sendNotification(User* user, const std::string& title, const std::string& message) override;
void attach(User* user) override;
void detach(User* user) override;
void loadServices();
void saveServices();
void loadComboPackages();
void saveComboPackages();
void loadServiceBookings();
void saveServiceBookings();
void loadJobCards();
void saveJobCards();
void loadObservers();
void saveObservers();
};
@@ -11,7 +11,6 @@ Date:19-May-2026
#include "Config.h"
#include "Enums.h"
#include "Factory.h"
#include "FileManager.h"
#include "InventoryManagementService.h"
#include "Notification.h"
#include "PaymentManagementService.h"
@@ -20,6 +19,9 @@ Date:19-May-2026
#include "UserManagementService.h"
#include "Vector.h"
#include "Validator.h"
#include "Utility.h"
#include "TrackedRecord.h"
#include "DataStoreLockGuard.h"
/*
Function: ensureAdminExists
@@ -31,12 +33,13 @@ Return type: void
*/
void UserManagementService::ensureAdminExists()
{
DataStoreLockGuard lock(m_dataStore);
auto& usersMap = m_dataStore.getUsers();
int usersMapSize = usersMap.getSize();
bool isAdminFound = false;
for (int index = 0; index < usersMapSize; index++)
{
User* user = usersMap.getValueAt(index);
User* user = usersMap.getValueAt(index).data;
if (user && user->getUserType() == util::UserType::ADMIN)
{
isAdminFound = true;
@@ -73,7 +76,9 @@ void UserManagementService::createUser(const std::string& username, const std::s
InventoryManagementService inventoryManagementService;
PaymentManagementService paymentManagementService;
ServiceManagementService serviceManagementService;
auto& usersMap = m_dataStore.getUsers();
DataStoreLockGuard lock(m_dataStore);
auto& trackedUsersMap = m_dataStore.getUsers();
auto usersMap = util::getObjects(trackedUsersMap);
if (util::isUsernameDuplicate(username, usersMap))
{
throw std::runtime_error("Username already exists");
@@ -87,13 +92,14 @@ void UserManagementService::createUser(const std::string& username, const std::s
throw std::runtime_error("Phone already exists");
}
User* newUser = Factory::getObject<User>(username, password, name, phone, email, type);
usersMap.insert(newUser->getId(), newUser);
trackedUsersMap.insert(newUser->getId(), util::createNewRecord(newUser));
paymentManagementService.attach(newUser);
serviceManagementService.attach(newUser);
if (newUser->getUserType() == util::UserType::ADMIN)
{
inventoryManagementService.attach(newUser);
}
m_dataStore.saveUsers();
}
/*
@@ -107,19 +113,24 @@ Return type: void
*/
void UserManagementService::updateUserDetails(const std::string& userID, const std::string& email, const std::string& phone)
{
auto& usersMap = m_dataStore.getUsers();
int index = usersMap.find(userID);
DataStoreLockGuard lock(m_dataStore);
auto& trackedUsersMap = m_dataStore.getUsers();
auto usersMap = util::getObjects(trackedUsersMap);
int index = trackedUsersMap.find(userID);
if (index == -1)
{
throw std::runtime_error("User does not exist!\n");
}
User* user = usersMap.getValueAt(index);
User* user = trackedUsersMap.getValueAt(index).data;
bool isModified = false;
if (email != user->getEmail())
{
if (util::isEmailDuplicate(email, usersMap))
{
throw std::runtime_error("Email already exists!\n");
}
user->setEmail(email);
isModified = true;
}
if (phone != user->getPhone())
{
@@ -127,9 +138,14 @@ void UserManagementService::updateUserDetails(const std::string& userID, const s
{
throw std::runtime_error("Phone number already exists!\n");
}
user->setPhone(phone);
isModified = true;
}
if (isModified)
{
trackedUsersMap.getValueAt(index).state = RecordState::MODIFIED;
m_dataStore.saveUsers();
}
user->setEmail(email);
user->setPhone(phone);
}
/*
@@ -144,20 +160,25 @@ Throws:
*/
util::Vector<Notification*> UserManagementService::getUserNotifications(const std::string& userID)
{
auto& usersMap = m_dataStore.getUsers();
if (usersMap.find(userID) == -1)
DataStoreLockGuard lock(m_dataStore);
auto& trackedUsersMap = m_dataStore.getUsers();
if (trackedUsersMap.find(userID) == -1)
{
throw std::runtime_error("No user found with given UserID");
}
User* user = usersMap[userID];
User* user = trackedUsersMap[userID].data;
if (user)
{
auto& notifications = user->getNotifications();
int numberOfNotifications = notifications.getSize();
auto& trackedNotificationMap = m_dataStore.getNotifications();
int numberOfNotifications = trackedNotificationMap.getSize();
util::Vector<Notification*> notificationsVector;
for (int index = 0; index < numberOfNotifications; index++)
{
notificationsVector.push_back(notifications.getValueAt(index));
Notification* notification = trackedNotificationMap.getValueAt(index).data;
if (notification->getRecipientUserId() == userID && notification->getState() == util::State::ACTIVE)
{
notificationsVector.push_back(notification);
}
}
return notificationsVector;
}
@@ -169,97 +190,39 @@ util::Vector<Notification*> UserManagementService::getUserNotifications(const st
/*
Function: deleteNotification
Description: Deletes a specific notification associated with a given user ID.
Description: Marks a specific notification associated with a given user
as inactive.
Parameters:
- notificationID: The unique ID of the notification to be deleted.
- userID: The unique ID of the user whose notification is to be deleted.
Returns:
- void
Throws:
- std::runtime_error if no user is found with the given UserID or if no notification is found with the given NotificationID.
- std::runtime_error if no user is found with the given UserID or
if no notification is found with the given NotificationID.
*/
void UserManagementService::deleteNotification(const std::string& notificationID, const std::string& userID)
{
auto& usersMap = m_dataStore.getUsers();
if (usersMap.find(userID) == -1)
DataStoreLockGuard lock(m_dataStore);
auto& trackedUsersMap = m_dataStore.getUsers();
auto& trackedNotificationsMap = m_dataStore.getNotifications();
int userIndex = trackedUsersMap.find(userID);
if (userIndex == -1)
{
throw std::runtime_error("No user found with given UserID");
}
User* user = usersMap[userID];
auto& notifications = user->getNotifications();
if (notifications.find(notificationID) == -1)
int notificationIndex = trackedNotificationsMap.find(notificationID);
if (notificationIndex == -1)
{
throw std::runtime_error("No notification found with given NotificationID");
}
notifications.remove(notificationID);
}
/*
Function: loadUsers
Description: Loads users and notifications from persistent storage into the datastore.
Validates that each notifications recipient exists and attaches the
notification to the corresponding user.
Parameters:
- None
Returns:
- void
Throws:
- std::runtime_error if a notification recipient user ID is invalid
*/
void UserManagementService::loadUsers()
{
util::FileManager<User> userFileManager(config::file::USER_FILE);
util::FileManager<Notification> notificationFileManager(config::file::NOTIFICATION_FILE);
auto& users = m_dataStore.getUsers();
auto usersMap = userFileManager.load();
auto notificationsMap = notificationFileManager.load();
int numberOfUsers = usersMap.getSize();
int numberOfNotifications = notificationsMap.getSize();
for (int index = 0; index < numberOfUsers; index++)
{
users[usersMap.getKeyAt(index)] = usersMap.getValueAt(index);
}
for (int index = 0; index < numberOfNotifications; index++)
{
Notification* notification = notificationsMap.getValueAt(index);
const std::string& recipientUserId = notification->getRecipientUserId();
int userIndex = users.find(recipientUserId);
if (userIndex == -1)
{
throw std::runtime_error("Invalid recipient user ID");
}
User* user = users.getValueAt(userIndex);
user->addNotification(notification);
}
}
/*
Function: saveUsers
Description: Saves users and their notifications from the datastore to persistent storage.
Collects notifications from all users into a single map before saving.
Parameters:
- None
Returns:
- void
*/
void UserManagementService::saveUsers()
{
util::FileManager<User> userFileManager(config::file::USER_FILE);
util::FileManager<Notification> notificationFileManager(config::file::NOTIFICATION_FILE);
auto& users = m_dataStore.getUsers();
util::Map<std::string, Notification*> notifications;
for (int userIndex = 0; userIndex < users.getSize(); userIndex++)
{
User* user = users.getValueAt(userIndex);
auto& userNotifications = user->getNotifications();
for (int notificationIndex = 0; notificationIndex < userNotifications.getSize(); notificationIndex++)
{
notifications[userNotifications.getKeyAt(notificationIndex)] =
userNotifications.getValueAt(notificationIndex);
}
}
userFileManager.save(users);
notificationFileManager.save(notifications);
Notification* notification = trackedNotificationsMap.getValueAt(notificationIndex).data;
if (notification->getRecipientUserId() == userID)
{
notification->setState(util::State::INACTIVE);
trackedNotificationsMap.getValueAt(notificationIndex).state = RecordState::MODIFIED;
m_dataStore.saveNotifications();
}
}
/*
@@ -270,7 +233,9 @@ Return type: util::Map<std::string, User*>
*/
util::Map<std::string, User*> UserManagementService::getUsers()
{
return m_dataStore.getUsers();
DataStoreLockGuard lock(m_dataStore);
auto users = util::getObjects(m_dataStore.getUsers());
return users;
}
/*
@@ -281,10 +246,12 @@ Return type: User*
*/
User* UserManagementService::getUser(const std::string& userID)
{
int index = m_dataStore.getUsers().find(userID);
DataStoreLockGuard lock(m_dataStore);
auto& trackedUsersMap = m_dataStore.getUsers();
int index = trackedUsersMap.find(userID);
if (index != -1)
{
return m_dataStore.getUsers().getValueAt(index);
return trackedUsersMap.getValueAt(index).data;
}
return nullptr;
}
@@ -300,35 +267,53 @@ void UserManagementService::removeUser(const std::string& userID)
InventoryManagementService inventoryManagementService;
PaymentManagementService paymentManagementService;
ServiceManagementService serviceManagementService;
int index = m_dataStore.getUsers().find(userID);
DataStoreLockGuard lock(m_dataStore);
auto& trackedUsersMap = m_dataStore.getUsers();
int index = trackedUsersMap.find(userID);
if (index != -1)
{
User* user = m_dataStore.getUsers().getValueAt(index);
User* user = trackedUsersMap.getValueAt(index).data;
if (user != nullptr)
{
if (user->getUserType() == util::UserType::CUSTOMER)
{
serviceManagementService.cancelCustomerServiceBookings(userID);
serviceManagementService.cancelCustomerServiceBookings(userID);
}
if (user->getUserType() == util::UserType::TECHNICIAN)
{
serviceManagementService.cancelTechnicianJobs(userID);
}
user->setState(util::State::INACTIVE);
inventoryManagementService.detach(user);
paymentManagementService.detach(user);
serviceManagementService.detach(user);
user->setState(util::State::INACTIVE);
trackedUsersMap.getValueAt(index).state = RecordState::MODIFIED;
m_dataStore.saveUsers();
}
}
}
util::Map<std::string, User*> UserManagementService::getUsers(util::UserType type)
/*
Function: getUsers
Description: Retrieves all active users of the specified type from
the DataStore.
Parameters:
- type: The user type to filter by
(ADMIN, CUSTOMER, or TECHNICIAN).
Returns:
- util::Map<std::string, User*>:
Collection of active users matching the specified type,
keyed by user ID.
*/
util::Map<std::string, User*> UserManagementService::getUsers(util::UserType type)
{
util::Map<std::string, User*>& currentUsers = m_dataStore.getUsers();
DataStoreLockGuard lock(m_dataStore);
auto& trackedUsersMap = m_dataStore.getUsers();
util::Map<std::string, User*> currentUsers = util::getObjects(trackedUsersMap);
util::Map<std::string, User*> filteredUsersMap;
for (int iterator = 0; iterator < currentUsers.getSize(); iterator++)
for (int index = 0; index < currentUsers.getSize(); index++)
{
User* currentUser = currentUsers.getValueAt(iterator);
User* currentUser = currentUsers.getValueAt(index);
if (currentUser && currentUser->getState() == util::State::ACTIVE && currentUser->getUserType() == type)
{
filteredUsersMap.insert(currentUser->getId(), currentUser);
@@ -6,6 +6,7 @@ Description: Header file declaring the UserManagementService class, which manage
Author: Trenser
Date:19-May-2026
*/
#pragma once
#include <string>
#include "Map.h"
@@ -30,6 +31,4 @@ public:
util::Vector<Notification*> getUserNotifications(const std::string& userID);
void deleteNotification(const std::string& notificationID, const std::string& userID);
void ensureAdminExists();
void loadUsers();
void saveUsers();
};
@@ -28,16 +28,19 @@ namespace config
namespace file
{
constexpr const char* INVENTORYITEM_FILE = "files/InventoryItem.csv";
constexpr const char* USER_FILE = "files/User.csv";
constexpr const char* NOTIFICATION_FILE = "files/Notification.csv";
constexpr const char* SERVICE_FILE = "files/Service.csv";
constexpr const char* COMBOPACKAGE_FILE = "files/ComboPackage.csv";
constexpr const char* SERVICEBOOKING_FILE = "files/ServiceBooking.csv";
constexpr const char* JOBCARD_FILE = "files/JobCard.csv";
constexpr const char* INVOICE_FILE = "files/Invoice.csv";
constexpr const char* SERVICEMANAGEMENTOBSERVERS = "files/ServiceManagementObservers.csv";
constexpr const char* PAYMENTMANAGEMENTOBSERVERS = "files/PaymentManagementObservers.csv";
constexpr const char* INVENTORYMANAGEMENTOBSERVERS = "files/InventoryManagementObservers.csv";
const size_t INITIAL_CAPACITY = 100;
const size_t GROWTH_FACTOR = 2;
constexpr const char* DIRECTORY = "files/";
constexpr const char* INVENTORYITEM_FILE = "files/InventoryItem.dat";
constexpr const char* USER_FILE = "files/User.dat";
constexpr const char* NOTIFICATION_FILE = "files/Notification.dat";
constexpr const char* SERVICE_FILE = "files/Service.dat";
constexpr const char* COMBOPACKAGE_FILE = "files/ComboPackage.dat";
constexpr const char* SERVICEBOOKING_FILE = "files/ServiceBooking.dat";
constexpr const char* JOBCARD_FILE = "files/JobCard.dat";
constexpr const char* INVOICE_FILE = "files/Invoice.dat";
constexpr const char* SERVICEMANAGEMENTOBSERVERS = "files/ServiceManagementObservers.dat";
constexpr const char* PAYMENTMANAGEMENTOBSERVERS = "files/PaymentManagementObservers.dat";
constexpr const char* INVENTORYMANAGEMENTOBSERVERS = "files/InventoryManagementObservers.dat";
}
}
@@ -12,35 +12,37 @@ Date: 19-May-2026
namespace util
{
enum class UserType
enum class UserType : int
{
ADMIN,
TECHNICIAN,
CUSTOMER
};
enum class PaymentMode
enum class PaymentMode : int
{
ONLINE,
OFFLINE,
NOTSET
};
enum class PaymentStatus
enum class PaymentStatus : int
{
PENDING,
COMPLETED
COMPLETED,
PAID
};
enum class ServiceJobStatus
enum class ServiceJobStatus : int
{
PENDING,
STARTED,
COMPLETED,
IN_PROGRESS,
CANCELLED
};
enum class State
enum class State : int
{
ACTIVE,
INACTIVE
@@ -137,6 +139,10 @@ namespace util
{
return PaymentMode::OFFLINE;
}
if (value == "NOTSET")
{
return PaymentMode::NOTSET;
}
throw std::invalid_argument("Invalid PaymentMode string");
}
@@ -156,6 +162,8 @@ namespace util
return "PENDING";
case PaymentStatus::COMPLETED:
return "COMPLETED";
case PaymentStatus::PAID:
return "PAID";
}
throw std::invalid_argument("Invalid PaymentStatus");
}
@@ -205,6 +213,8 @@ namespace util
return "COMPLETED";
case ServiceJobStatus::CANCELLED:
return "CANCELLED";
case ServiceJobStatus::IN_PROGRESS:
return "IN_PROGRESS";
}
throw std::invalid_argument("Invalid ServiceJobStatus");
}
@@ -237,6 +247,10 @@ namespace util
{
return ServiceJobStatus::CANCELLED;
}
if (value == "IN_PROGRESS")
{
return ServiceJobStatus::IN_PROGRESS;
}
throw std::invalid_argument("Invalid ServiceJobStatus string");
}
@@ -11,69 +11,36 @@ Date: 22-May-2026
#include <fstream>
#include <string>
#include <stdexcept>
#include <direct.h>
#include "Vector.h"
namespace util
{
/*
Function: loadRecords
Description: Loads records from a given file path into a vector of strings.
Skips the header line if present. Creates the file if it does not exist.
Function: ensureDirectoryExists
Description: Creates all missing directories present in the given file path.
Iteratively parses the path and creates each directory level
using _mkdir() before file operations are performed.
Parameters:
- filePath: const std::string&, path to the file
Returns:
- util::Vector<std::string>: Vector containing all records (excluding header)
Throws:
- None (creates file if missing)
*/
inline util::Vector<std::string> loadRecords(const std::string& filePath)
{
util::Vector<std::string> records;
std::ifstream file(filePath);
if (!file.is_open())
{
std::ofstream newFile(filePath);
newFile.close();
file.open(filePath);
}
std::string line;
bool isHeader = true;
while (std::getline(file, line))
{
if (isHeader)
{
isHeader = false;
continue;
}
records.push_back(line);
}
return records;
}
/*
Function: saveRecords
Description: Saves records to a given file path. Overwrites existing content
and writes a header line followed by all records.
Parameters:
- filePath: const std::string&, path to the file
- records: const util::Vector<std::string>&, vector of records to save
- filePath: const std::string&, relative or absolute file path
Returns:
- void
Throws:
- std::runtime_error if the file cannot be opened for writing
- None (_mkdir failures are intentionally ignored if directory already exists)
*/
inline void saveRecords(const std::string& filePath, const util::Vector<std::string>& records)
inline void ensureDirectoryExists(const std::string& filePath)
{
std::ofstream file(filePath, std::ios::trunc);
if (!file.is_open())
size_t position = 0;
while ((position = filePath.find('/', position)) != std::string::npos)
{
throw std::runtime_error("Failed to open file " + filePath);
}
file << "Values" << '\n';
int numberOfRecords = records.getSize();
for (int index = 0; index < numberOfRecords; index++)
{
file << records[index] << '\n';
std::string directory = filePath.substr(0, position);
if (!directory.empty())
{
(void)_mkdir(directory.c_str());
}
position++;
}
}
}
}
@@ -1,117 +0,0 @@
/*
File: FileManager.h
Description: Declares and implements a generic FileManager template class for
loading and saving objects to and from files. Uses serialization
and deserialization methods defined in the object type T.
Provides persistence support for system entities such as Users,
Services, InventoryItems, etc.
Author: Trenser
Date: 22-May-2026
*/
#pragma once
#include <stdexcept>
#include <string>
#include <fstream>
#include "Vector.h"
#include "Map.h"
namespace util
{
template <typename T> using objects = util::Map<std::string, T*>;
template <typename T>
class FileManager
{
private:
std::string m_filePath;
public:
FileManager() : m_filePath("") {}
FileManager(const std::string& filePath) : m_filePath(filePath) {}
objects<T> load();
void save(const objects<T>&);
};
/*
Function: load
Description: Loads records from the file into a map of objects.
Skips the header line, deserializes each record into an object of type T,
and stores them in a map keyed by object ID.
Parameters:
- None
Returns:
- util::Map<std::string, T*> containing deserialized objects
Throws:
- std::runtime_error if deserialization fails for any record
*/
template <typename T>
objects<T> FileManager<T>::load()
{
objects<T> records;
std::ifstream file(m_filePath);
if (!file.is_open())
{
std::ofstream newFile(m_filePath);
newFile.close();
file.open(m_filePath);
}
util::Vector<std::string> lines;
std::string line;
while (std::getline(file, line))
{
lines.push_back(line);
}
int numberOfLines = lines.getSize();
bool isHeader = true;
for (int lineIndex = 0; lineIndex < numberOfLines; lineIndex++)
{
const auto& record = lines[lineIndex];
if (isHeader)
{
isHeader = false;
continue;
}
auto object = T::deserialize(record);
if (!object)
{
throw std::runtime_error("Failed to deserialize record");
}
records[object->getId()] = object;
}
return records;
}
/*
Function: save
Description: Saves records to the file. Serializes each object of type T into a string,
writes a header line, and then writes all serialized records to the file.
Parameters:
- records: const util::Map<std::string, T*>&, map of objects to save
Returns:
- void
Throws:
- std::runtime_error if the file cannot be opened for writing
*/
template <typename T>
void FileManager<T>::save(const objects<T>& records)
{
util::Vector<std::string> lines;
lines.push_back(T::getHeaders());
int numberOfRecords = records.getSize();
for (int recordIndex = 0; recordIndex < numberOfRecords; recordIndex++)
{
const auto& record = records.getValueAt(recordIndex);
lines.push_back(record->serialize());
}
std::ofstream file(m_filePath, std::ios::trunc);
if (!file.is_open())
{
throw std::runtime_error("Failed to open file " + m_filePath);
}
int numberOfLines = lines.getSize();
for (int lineIndex = 0; lineIndex < numberOfLines; lineIndex++)
{
file << lines[lineIndex] << '\n';
}
}
}
@@ -10,6 +10,7 @@
#include <limits>
#include <string>
#include <stdexcept>
#include <conio.h>
namespace util
{
@@ -27,7 +28,7 @@ namespace util
if (!(std::cin >> value))
{
std::cin.clear();
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
std::cin.ignore((std::numeric_limits<std::streamsize>::max)(), '\n');
throw std::runtime_error("Invalid console input");
}
}
@@ -54,6 +55,48 @@ namespace util
value = cleanedValue;
}
/*
* Function: readPassword
* Description: Reads a password from console without echoing characters;
* displays '*' for each character typed, handles backspace,
* and cleans commas from the result.
* Parameters:
* value - reference to a string where the password will be stored
* Returns:
* void - no return value
*/
inline void readPassword(std::string& value)
{
value.clear();
char currentCharacter;
while ((currentCharacter = _getch()) != '\r')
{
if (currentCharacter == '\b')
{
if (!value.empty())
{
value.pop_back();
std::cout << "\b \b";
}
}
else
{
value += currentCharacter;
std::cout << '*';
}
}
std::cout << std::endl;
std::string cleanedValue;
for (int iterator = 0; iterator < value.length(); iterator++)
{
if (value[iterator] != ',')
{
cleanedValue += value[iterator];
}
}
value = cleanedValue;
}
/*
* Function: pressEnter
* Description: Pauses execution until the user presses Enter.
@@ -54,49 +54,79 @@ namespace util
return cost;
}
/*
Function: loadObservers
Description: Loads observer IDs from a file and attaches the corresponding users
to the notification management service. Validates that each observer ID
exists in the datastore before attaching.
Parameters:
- filePath: const std::string&, path to the file containing observer IDs
- service: NotificationManagementService*, pointer to the notification service
- dataStore: DataStore&, reference to the datastore containing users
Returns:
- void
Throws:
- std::runtime_error if an observer ID is invalid (not found in datastore)
*/
inline void loadObservers(const std::string& filePath, NotificationManagementService* service, DataStore& dataStore)
{
auto observerIDs = util::loadRecords(filePath);
auto& users = dataStore.getUsers();
for (int index = 0; index < observerIDs.getSize(); index++)
{
const std::string& observerID = observerIDs[index];
int userIndex = users.find(observerID);
if (userIndex == -1)
{
throw std::runtime_error("Invalid Observer ID");
}
service->attach(users.getValueAt(userIndex));
}
}
template<typename TObject>
Map<std::string, TObject*> getObjects(const Map<std::string, TrackedRecord<TObject>>& trackedRecords);
/*
Function: saveObservers
Description: Saves the current observer IDs from the notification management service
to a file for persistence.
Parameters:
- filePath: const std::string&, path to the file where observer IDs will be saved
- service: NotificationManagementService*, pointer to the notification service
Returns:
- void
*/
inline void saveObservers(const std::string& filePath, NotificationManagementService* service)
template<typename TObject>
Map<std::string, const TObject*> getConstObjects(const Map<std::string, TrackedRecord<TObject>>& trackedRecords);
template<typename TObject>
TrackedRecord<TObject> createNewRecord(TObject* object);
}
/*
Function: getObjects
Description: Extracts the object pointers from a tracked-record
collection and returns them as a map keyed by the
same identifiers.
Parameters:
- trackedRecords: Collection of tracked records.
Returns:
- Map<std::string, TObject*>: Collection of object pointers.
*/
template<typename TObject>
util::Map<std::string, TObject*> util::getObjects(const util::Map<std::string, TrackedRecord<TObject>>& trackedRecords)
{
util::Map<std::string, TObject*> objects;
for (int index = 0; index < trackedRecords.getSize(); ++index)
{
auto observerIDs = service->getObserverIDs();
util::saveRecords(filePath, observerIDs);
const std::string& key = trackedRecords.getKeyAt(index);
TObject* object = trackedRecords.getValueAt(index).data;
objects.insert(key, object);
}
return objects;
}
/*
Function: getConstObjects
Description: Extracts the object pointers from a tracked-record
collection and returns them as a read-only map
keyed by the same identifiers.
Parameters:
- trackedRecords: Collection of tracked records.
Returns:
- Map<std::string, const TObject*>:
Collection of read-only object pointers.
*/
template<typename TObject>
util::Map<std::string, const TObject*> util::getConstObjects(
const util::Map<std::string, TrackedRecord<TObject>>& trackedRecords)
{
util::Map<std::string, const TObject*> objects;
for (int index = 0; index < trackedRecords.getSize(); ++index)
{
const std::string& key = trackedRecords.getKeyAt(index);
const TObject* object = trackedRecords.getValueAt(index).data;
objects.insert(key, object);
}
return objects;
}
/*
Function: createNewRecord
Description: Creates a tracked record for a newly created
object. The record is initialized with
NEW_RECORD state.
Parameters:
- object: Pointer to the newly created object.
Returns:
- TrackedRecord<TObject>: Initialized tracked record.
*/
template<typename TObject>
TrackedRecord<TObject> util::createNewRecord(TObject* object)
{
TrackedRecord<TObject> record;
record.data = object;
record.state = RecordState::NEW_RECORD;
return record;
}
@@ -116,15 +116,13 @@ bool util::isPasswordValid(const std::string& password)
* usersMap - map of user objects keyed by identifier
* Returns:
* bool - true if the username is already in use by an active user, false otherwise
* Notes:
* - Only considers users with state util::State::ACTIVE
*/
bool util::isUsernameDuplicate(const std::string& username, const util::Map<std::string, User*>& usersMap)
{
int index = usersMap.findIf(
[&](const std::string&, User* user)
{
return (user->getUserName() == username && user->getState() == util::State::ACTIVE);
return (user->getUserName() == username);
}
);
return index != -1;
@@ -42,15 +42,19 @@ void AdminMenu::showMenu()
<< "\n3. Remove Inventory Item"
<< "\n4. Check Stock Availability"
<< "\n5. Assign Job to Technician"
<< "\n6. Add Technician"
<< "\n7. Remove Customer/Technician"
<< "\n8. Create Service"
<< "\n9. Remove Service"
<< "\n10. Create Combo Package"
<< "\n11. Remove Combo Package"
<< "\n12. View Notifications"
<< "\n13. Change Password"
<< "\n14. Logout"
<< "\n6. Display Users"
<< "\n7. Add Technician"
<< "\n8. Remove Customer/Technician"
<< "\n9. Display Services"
<< "\n10. Create Service"
<< "\n11. Remove Service"
<< "\n12. Display Combo Packages"
<< "\n13. Create Combo Package"
<< "\n14. Remove Combo Package"
<< "\n15. View Notifications"
<< "\n16. Change Password"
<< "\n17. Confirm Payment"
<< "\n18. Logout"
<< "\nEnter a choice: ";
util::read(choice);
if (!handleOperation(choice))
@@ -92,30 +96,42 @@ bool AdminMenu::handleOperation(int choice)
assignJob();
break;
case 6:
addTechnician();
displayUsers();
break;
case 7:
removeUser();
addTechnician();
break;
case 8:
createService();
removeUser();
break;
case 9:
removeService();
displayServices();
break;
case 10:
createComboPackages();
createService();
break;
case 11:
removeComboPackage();
removeService();
break;
case 12:
viewNotifications();
displayComboPackages();
break;
case 13:
changePassword();
createComboPackages();
break;
case 14:
removeComboPackage();
break;
case 15:
viewNotifications();
break;
case 16:
changePassword();
break;
case 17:
confirmPayment();
break;
case 18:
logout();
return false;
default:
@@ -383,6 +399,24 @@ void AdminMenu::assignJob()
util::pressEnter();
}
/*
Function: displayServices()
Description: Display all active services
Parameters:
- None
Returns:
- void
*/
void AdminMenu::displayServices()
{
util::clear();
std::cout << "List of all Services\n";
util::Map<std::string, const Service*> currentServices = m_controller.getServices();
util::Map<std::string, const Service*> currentActiveServices = filterActiveServices(currentServices);
displayAllServices(currentActiveServices);
util::pressEnter();
}
/*
Function: createService
Description: Allows the admin to create a new service by selecting inventory items and specifying labor cost.
@@ -434,7 +468,7 @@ void AdminMenu::removeService()
if (selectedServiceID != "")
{
m_controller.removeService(selectedServiceID);
std::cout << "Service removed sucessfully.\n\n";
std::cout << "Service removed successfully.\n\n";
}
else
{
@@ -443,6 +477,77 @@ void AdminMenu::removeService()
util::pressEnter();
}
/*
Function: displayUsers
Description: Displays all users.
Parameter: None
Return type: void
*/
void AdminMenu::displayUsers()
{
util::clear();
auto listOfUsers = m_controller.getUsers();
auto listOfActiveUsers = filterActiveUsers(listOfUsers);
int activeUserCount = listOfActiveUsers.getSize();
std::cout << "List of all Users\n";
if (activeUserCount < 1)
{
std::cout << "No Active users." << std::endl;
util::pressEnter();
return;
}
displayAllUsers(listOfActiveUsers);
util::pressEnter();
}
/*
Function: confirmPayment
Description: Confirms payment for a selected invoice. Validates invoice status, updates payment date,
sets status to COMPLETED, and sends a notification to the customer.
Parameters:
- invoiceID: std::string, ID of the invoice to confirm
Returns:
- void
*/
void AdminMenu::confirmPayment()
{
util::clear();
std::cout << "Confirm Payment\n";
auto invoiceList = m_controller.getAllInvoices();
if (invoiceList.isEmpty())
{
std::cout << "No pending invoices available for confirmation.";
util::pressEnter();
return;
}
bool hasConfirmableInvoice = false;
for (int index = 0; index < invoiceList.getSize(); ++index)
{
const Invoice* invoice = invoiceList.getValueAt(index);
if (invoice && invoice->getStatus() == util::PaymentStatus::PAID)
{
hasConfirmableInvoice = true;
break;
}
}
if (!hasConfirmableInvoice)
{
std::cout << "No invoices awaiting confirmation.\n";
util::pressEnter();
return;
}
std::string selectedID = selectInvoiceFromUserForPayment(invoiceList, util::PaymentStatus::PAID);
if (selectedID == "")
{
std::cout << "Payment failed.\n";
util::pressEnter();
return;
}
m_controller.confirmPayment(selectedID);
std::cout << "Payment Confirmed successfully.\n";
util::pressEnter();
}
/*
Function: addTechnician
Description: Adds a new technician after validating username, password, email, and phone number.
@@ -507,12 +612,12 @@ void AdminMenu::removeUser()
util::pressEnter();
return;
}
displayAllActiveUsers(listOfActiveUsers, activeUserCount);
displayAllUsers(listOfActiveUsers);
std::cout << "Enter the index of the user to delete : ";
util::read(indexChoice);
if (indexChoice < 1 || indexChoice > activeUserCount)
{
std::cout << "Error Invaild index.\n" << std::endl;
std::cout << "Error invalid index.\n" << std::endl;
util::pressEnter();
return;
}
@@ -526,6 +631,24 @@ void AdminMenu::removeUser()
util::pressEnter();
}
/*
Function: displayComboPackages()
Description: Display all active combo packages
Parameters:
- None
Returns:
- void
*/
void AdminMenu::displayComboPackages()
{
util::clear();
std::cout << "List of all Combo Packages\n";
util::Map<std::string, const ComboPackage*> currentComboPackages = m_controller.getComboPackages();
util::Map<std::string, const ComboPackage*> currentActiveComboPackages = filterComboPackages(currentComboPackages);
displayAllComboPackages(currentActiveComboPackages);
util::pressEnter();
}
/*
Function: createComboPackages
Description: Creates a new combo package by selecting two active services and applying a discount.
@@ -24,10 +24,14 @@ public:
void removeInventoryItem();
void checkStockAvailability();
void assignJob();
void displayServices();
void createService();
void removeService();
void displayUsers();
void confirmPayment();
void addTechnician();
void removeUser();
void displayComboPackages();
void createComboPackages();
void removeComboPackage();
void viewNotifications();
@@ -30,7 +30,6 @@ Description: Displays the customer menu and handles user input until logout is s
Parameter: None
Return type: void
*/
void CustomerMenu::showMenu()
{
while (true)
@@ -338,7 +337,7 @@ void CustomerMenu::completePayments()
util::pressEnter();
return;
}
std::string selectedID = selectInvoiceFromUserForPayment(currentInvoices);
std::string selectedID = selectInvoiceFromUserForPayment(currentInvoices, util::PaymentStatus::PENDING);
if (selectedID == "")
{
std::cout << "Payment failed.\n";
@@ -29,6 +29,41 @@ Date: 21-May-2026
#include "Validator.h"
#include "Vector.h"
/*
Function: displayAllServices
Description: Displays all active services
Parameters:
- currentServices: util::Map<std::string, const Service*>, available services
Returns:
- void;
*/
inline void displayAllServices(util::Map<std::string, const Service*> currentServices)
{
if (currentServices.getSize() == 0)
{
std::cout << "No Services Currently Available.\n";
return;
}
std::cout << std::left
<< std::setw(12) << "Service ID"
<< std::setw(35) << "Name"
<< std::setw(10) << "Labor Cost"
<< std::endl;
for (int iterator = 0; iterator < currentServices.getSize(); iterator++)
{
const Service* currentService = currentServices.getValueAt(iterator);
if (currentService == nullptr || currentService->getState() == util::State::INACTIVE)
{
continue;
}
std::cout << std::left
<< std::setw(12) << currentService->getId()
<< std::setw(35) << util::truncateString(currentService->getName(), 30)
<< std::setw(10) << currentService->getLaborCost()
<< std::endl;
}
}
/*
Function: selectServicesToRemove
Description: Allows selection of a service to remove by index.
@@ -49,7 +84,7 @@ inline std::string selectServicesToRemove(util::Map<std::string, const Service*>
std::cout << std::left
<< std::setw(6) << "Index"
<< std::setw(12) << "Service ID"
<< std::setw(20) << "Name"
<< std::setw(35) << "Name"
<< std::setw(10) << "Labor Cost"
<< std::endl;
for (int iterator = 0; iterator < currentServices.getSize(); iterator++)
@@ -62,7 +97,7 @@ inline std::string selectServicesToRemove(util::Map<std::string, const Service*>
std::cout << std::left
<< std::setw(6) << currentIndex
<< std::setw(12) << currentService->getId()
<< std::setw(20) << currentService->getName()
<< std::setw(35) << util::truncateString(currentService->getName(), 30)
<< std::setw(10) << currentService->getLaborCost()
<< std::endl;
currentServicesMap.insert(currentIndex++, currentService);
@@ -329,14 +364,18 @@ inline const User* selectTechnician(util::Map<int, const User*>& currentAvailabl
}
/*
Function: selectInvoiceFromUserForPayment
Description: Lists all pending invoices for the customer and allows selection by index.
Function: selectInvoiceFromUserForPayment
Description: Displays a list of invoices filtered by the required payment status.
Allows the user to select an invoice by index and returns the corresponding invoice ID.
Parameters:
- currentInvoices: util::Map<std::string, const Invoice*>&, map of customer invoices
- currentInvoices: const util::Map<std::string, const Invoice*>&,
map of all invoices keyed by invoice ID
- requiredStatus: util::PaymentStatus,
the status to filter invoices (e.g., PENDING, PAID, COMPLETED)
Returns:
- std::string: ID of the selected invoice, or empty string if none selected
- std::string: ID of the selected invoice, or empty string if none selected or invalid index
*/
inline std::string selectInvoiceFromUserForPayment(const util::Map<std::string, const Invoice*>& currentInvoices)
inline std::string selectInvoiceFromUserForPayment(const util::Map<std::string, const Invoice*>& currentInvoices, util::PaymentStatus requiredStatus)
{
int currentIndex = 1, choice;
util::Map<int, const Invoice*> pendingInvoicesForPayment;
@@ -354,7 +393,7 @@ inline std::string selectInvoiceFromUserForPayment(const util::Map<std::string,
for (int iterator = 0; iterator < currentInvoices.getSize(); iterator++)
{
const Invoice* currentInvoice = currentInvoices.getValueAt(iterator);
if (currentInvoice && currentInvoice->getStatus() == util::PaymentStatus::PENDING)
if (currentInvoice && currentInvoice->getStatus() == requiredStatus)
{
const User* currentTechnician = currentInvoice->getBooking()->getAssignedTechnician();
std::cout << std::left
@@ -453,12 +492,12 @@ inline const Invoice* selectInvoiceToDisplay(util::Map<std::string, const Invoic
<< std::left
<< std::setw(10) << "Index"
<< std::setw(12) << "BookingID"
<< std::setw(15) << "VehicleNumber"
<< std::setw(20) << "TechnicianName"
<< std::setw(15) << "TotalAmount"
<< std::setw(25) << "InvoiceDate"
<< std::setw(15) << "PaymentStatus"
<< std::setw(15) << "PaymentMode"
<< std::setw(20) << "Vehicle Number"
<< std::setw(20) << "Technician Name"
<< std::setw(15) << "Total Amount"
<< std::setw(25) << "Invoice Date"
<< std::setw(20) << "Payment Status"
<< std::setw(15) << "Payment Mode"
<< std::endl;
for (int iterator = 0; iterator < currentInvoices.getSize(); iterator++)
{
@@ -471,11 +510,11 @@ inline const Invoice* selectInvoiceToDisplay(util::Map<std::string, const Invoic
std::cout << std::left
<< std::setw(10) << currentIndex
<< std::setw(12) << currentInvoice->getBookingId()
<< std::setw(15) << currentInvoice->getBooking()->getVehicleNumber()
<< std::setw(20) << currentInvoice->getBooking()->getVehicleNumber()
<< std::setw(20) << ((currentTechnician && !currentTechnician->getName().empty()) ? currentTechnician->getName() : "NULL")
<< std::setw(15) << currentInvoice->getTotalAmount()
<< std::setw(25) << currentInvoice->getInvoiceDate().toString()
<< std::setw(15) << util::getPaymentStatusString(currentInvoice->getStatus())
<< std::setw(20) << util::getPaymentStatusString(currentInvoice->getStatus())
<< std::setw(15) << util::getPaymentModeString(currentInvoice->getPaymentMethod())
<< std::endl;
currentInvoicesIndexMap.insert(currentIndex++, currentInvoice);
@@ -592,7 +631,6 @@ inline void displayInvoices(util::Map<std::string, const Invoice*> currentUserIn
std::cout << "Unable to fetch the selected invoice\n";
doRun = false;
}
} while (doRun);
}
}
@@ -611,7 +649,7 @@ inline util::Map<std::string, const JobCard*> filterStartedJobCards(util::Map<st
for (int iterator = 0; iterator < assignedJobCards.getSize(); iterator++)
{
const JobCard* currentJobCard = assignedJobCards.getValueAt(iterator);
if (currentJobCard && currentJobCard->getStatus() == util::ServiceJobStatus::STARTED)
if (currentJobCard && (currentJobCard->getStatus() == util::ServiceJobStatus::STARTED || currentJobCard->getStatus() == util::ServiceJobStatus::IN_PROGRESS))
{
startedJobCards.insert(currentJobCard->getId(), currentJobCard);
}
@@ -619,25 +657,105 @@ inline util::Map<std::string, const JobCard*> filterStartedJobCards(util::Map<st
return startedJobCards;
}
/*
Function: filterJobCards
Description:
Filters the given list of job cards and returns only those
whose status matches the specified ServiceJobStatus.
Parameters:
- assignedJobCards: util::Map<std::string, const JobCard*>&
Map of job card IDs to JobCard pointers assigned to the technician.
- selectedJobStatus: util::ServiceJobStatus
The status type to filter job cards by.
Returns:
- util::Map<std::string, const JobCard*>
A map containing only job cards with the specified status.
*/
inline util::Map<std::string, const JobCard*> filterJobCards(util::Map<std::string, const JobCard*>& assignedJobCards, util::ServiceJobStatus selectedJobStatus)
{
util::Map<std::string, const JobCard*> startedJobCards;
for (int iterator = 0; iterator < assignedJobCards.getSize(); iterator++)
{
const JobCard* currentJobCard = assignedJobCards.getValueAt(iterator);
if (currentJobCard && currentJobCard->getStatus() == selectedJobStatus)
{
startedJobCards.insert(currentJobCard->getId(), currentJobCard);
}
}
return startedJobCards;
}
/*
Function: displayAllJobs
Description: Displays all Jobs assigned to a Technician
Parameters:
- assignedJobCards: util::Map<std::string, const JobCard*>&, job cards assigned to the technician
Returns:
- std::string: ID of the selected job card, or empty string if none selected
*/
inline void displayAllJobs(util::Map<std::string, const JobCard*>& assignedJobCards)
{
if (assignedJobCards.getSize() == 0)
{
std::cout << "No active jobs assigned.\n";
return;
}
std::cout << std::endl;
std::cout << std::left
<< std::setw(12) << "BookingID"
<< std::setw(12) << "JobID"
<< std::setw(20) << "ServiceName"
<< std::setw(12) << "ServiceID"
<< std::setw(12) << "Status"
<< std::endl;
for (int iterator = 0; iterator < assignedJobCards.getSize(); iterator++)
{
const JobCard* currentJobCard = assignedJobCards.getValueAt(iterator);
if (currentJobCard && (currentJobCard->getStatus() == util::ServiceJobStatus::STARTED || currentJobCard->getStatus() == util::ServiceJobStatus::IN_PROGRESS))
{
std::cout << std::left << std::setw(12) << currentJobCard->getBookingId()
<< std::setw(12) << currentJobCard->getId()
<< std::setw(20) << util::truncateString(currentJobCard->getService()->getName(), 15)
<< std::setw(12) << currentJobCard->getServiceId()
<< std::setw(12) << util::getServiceJobStatusString(currentJobCard->getStatus())
<< std::endl;
}
}
}
/*
Function: selectJobCardToComplete
Description: Lists all incomplete job cards assigned to the technician and allows selection by index.
Parameters:
- assignedJobCards: util::Map<std::string, const JobCard*>&, job cards assigned to the technician
- incompleteJobCards: util::Map<int, const JobCard*>&, map of incomplete job cards indexed for selection
Returns:
- std::string: ID of the selected job card, or empty string if none selected
*/
inline std::string selectJobCardToComplete(util::Map<std::string, const JobCard*>& assignedJobCards)
inline std::string selectJobCardToUpdate(util::Map<std::string, const JobCard*>& assignedJobCards, util::ServiceJobStatus selectedJobStatusType)
{
util::Map<int, const JobCard* > incompleteJobCards;
if (assignedJobCards.getSize() == 0)
{
std::cout << "No started jobs available to complete.\n";
std::cout << "\nNo jobs available.\n\n";
return "";
}
int currentIndex = 1;
int choice;
if (selectedJobStatusType == util::ServiceJobStatus::STARTED)
{
util::clear();
std::cout << "Select a job to mark as In Progress\n";
}
else if (selectedJobStatusType == util::ServiceJobStatus::IN_PROGRESS)
{
util::clear();
std::cout << "Select a job to mark as Completed\n";
}
else
{
std::cout << "Unable to update completed or pending jobs.\n\n";
return "";
}
std::cout << std::endl;
std::cout << std::left
<< std::setw(6) << "Index"
@@ -645,22 +763,24 @@ inline std::string selectJobCardToComplete(util::Map<std::string, const JobCard*
<< std::setw(12) << "JobID"
<< std::setw(20) << "ServiceName"
<< std::setw(12) << "ServiceID"
<< std::setw(12) << "JobStatus"
<< std::endl;
for (int iterator = 0; iterator < assignedJobCards.getSize(); iterator++)
{
const JobCard* currentJobCard = assignedJobCards.getValueAt(iterator);
if (currentJobCard && (currentJobCard->getStatus() == util::ServiceJobStatus::STARTED))
if (currentJobCard && (currentJobCard->getStatus() == selectedJobStatusType))
{
std::cout << std::left << std::setw(6) << currentIndex
<< std::setw(12) << currentJobCard->getBookingId()
<< std::setw(12) << currentJobCard->getId()
<< std::setw(20) << currentJobCard->getService()->getName()
<< std::setw(20) << util::truncateString(currentJobCard->getService()->getName(), 15)
<< std::setw(12) << currentJobCard->getServiceId()
<< std::setw(12) << util::getServiceJobStatusString(currentJobCard->getStatus())
<< std::endl;
incompleteJobCards.insert(currentIndex++, currentJobCard);
}
}
std::cout << "Select the Job Card to complete (Index): ";
std::cout << "Enter the job index to update: ";
util::read(choice);
int selectedJobCardIndex = incompleteJobCards.find(choice);
if (selectedJobCardIndex != -1)
@@ -671,7 +791,7 @@ inline std::string selectJobCardToComplete(util::Map<std::string, const JobCard*
else
{
std::cout << "Invalid index.\n";
std::cout << "Failed to complete jobs.\n\n";
std::cout << "Failed to update job.\n\n";
return "";
}
}
@@ -794,7 +914,7 @@ inline void changePasswordHelper(Controller& controller)
util::clear();
std::cout << "Change Password\n";
std::cout << "Enter new password: ";
util::read(newPassword);
util::readPassword(newPassword);
if (!util::isPasswordValid(newPassword))
{
std::cout << "Error: Password is not strong enough!\n";
@@ -808,7 +928,7 @@ inline void changePasswordHelper(Controller& controller)
continue;
}
std::cout << "Confirm new password: ";
util::read(confirmedPassword);
util::readPassword(confirmedPassword);
if (confirmedPassword != newPassword)
{
std::cout << "Passwords are different. Try again\n";
@@ -844,14 +964,14 @@ inline util::Map<std::string, const User*> filterActiveUsers(const util::Map<std
}
/*
Function: displayAllActiveUsers
Function: displayAllUsers
Description: Displays all active users in a tabular format with index, ID, username, and type.
Parameter: util::Map<std::string, const User*>& activeUsers - active users list
int activeUserCount - number of active users
Return type: void
*/
inline void displayAllActiveUsers(util::Map<std::string, const User*>& activeUsers, int activeUserCount)
inline void displayAllUsers(util::Map<std::string, const User*>& activeUsers)
{
int activeUserCount = activeUsers.getSize();
std::cout << std::left << std::setw(10) << "Index"
<< std::setw(15) << "User ID"
<< std::setw(25) << "Username"
@@ -986,6 +1106,43 @@ inline util::Map<std::string, const ComboPackage*> filterComboPackages(util::Map
return activeComboPackages;
}
/*
Function: displayAllComboPackages
Description: Displays all active combo packages
Parameters:
- currentComboPackages: util::Map<std::string, const ComboPackage*>, available combo packages
Returns:
- void;
*/
inline void displayAllComboPackages(util::Map<std::string, const ComboPackage*> comboPackages)
{
std::cout << std::endl;
if (comboPackages.getSize() == 0)
{
std::cout << "No active combo packages available." << std::endl;
return;
}
std::cout << std::left
<< std::setw(15) << "Combo ID"
<< std::setw(35) << "Combo Name"
<< std::setw(15) << "Estimate Cost"
<< std::endl;
for (int index = 0; index < comboPackages.getSize(); index++)
{
const ComboPackage* currentComboPackage = comboPackages.getValueAt(index);
if (currentComboPackage && 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;
}
}
/*
Function: selectComboPackageFromPackages
Description: Displays active combo packages and allows the customer to select one by index.
@@ -1007,7 +1164,7 @@ 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->getState() != util::State::ACTIVE)
if (currentComboPackage && currentComboPackage->getState() != util::State::ACTIVE)
{
continue;
}
@@ -1188,21 +1345,21 @@ inline void displayComboPackagesWithIndex(util::Map<int, const ComboPackage*>& c
const ComboPackage* currentComboPackage = currentComboPackageIndexMap.getValueAt(iterator);
if (currentComboPackage == nullptr)
{
throw std::runtime_error("Error accessing the combopackage.\n");
throw std::runtime_error("Error accessing the combo package.\n");
}
if (iterator == 0)
{
std::cout << std::left
<< std::setw(8) << "Index"
<< std::setw(10) << "ID"
<< std::setw(20) << "Package Name"
<< std::setw(35) << "Package Name"
<< std::setw(15) << "Discount (%)"
<< "\n";
}
std::cout << std::left
<< std::setw(8) << currentComboPackageIndexMap.getKeyAt(iterator)
<< std::setw(10) << currentComboPackage->getId()
<< std::setw(20) << currentComboPackage->getPackageName()
<< std::setw(35) << util::truncateString(currentComboPackage->getPackageName(), 30)
<< std::setw(15) << currentComboPackage->getDiscountPercentage()
<< "\n";
}
@@ -34,10 +34,11 @@ void TechnicianMenu::showMenu()
int choice;
util::clear();
std::cout << "Technician Menu"
<< "\n1. Mark Job as Completed"
<< "\n2. View Notifications"
<< "\n3. Change Password"
<< "\n4. Logout"
<< "\n1. Display My Jobs"
<< "\n2. Update Job Status"
<< "\n3. View Notifications"
<< "\n4. Change Password"
<< "\n5. Logout"
<< "\nEnter a choice: ";
util::read(choice);
if (!handleOperation(choice))
@@ -64,15 +65,18 @@ bool TechnicianMenu::handleOperation(int choice)
switch (choice)
{
case 1:
completeJob();
displayJobs();
break;
case 2:
viewNotifications();
updateJobStatus();
break;
case 3:
changePassword();
viewNotifications();
break;
case 4:
changePassword();
break;
case 5:
logout();
return false;
default:
@@ -83,25 +87,62 @@ bool TechnicianMenu::handleOperation(int choice)
}
/*
Function: completeJob
Description: Allows the technician to mark a selected job card as completed.
Function: displayJobs
Description: Displays all Jobs assigned to a Technician
Parameters:
- None
Returns:
- void
*/
void TechnicianMenu::displayJobs()
{
util::clear();
std::cout << "My Jobs\n";
util::Map<std::string, const JobCard*> assignedJobCards = m_controller.getJobCardsByUser();
util::Map<std::string, const JobCard*> jobCards = filterStartedJobCards(assignedJobCards);
displayAllJobs(jobCards);
util::pressEnter();
}
/*
Function: updateJobStatus
Description: Allows the technician to update a selected job card.
Validates selection and updates job status through the controller.
Parameters:
- None
Returns:
- void
*/
void TechnicianMenu::completeJob()
void TechnicianMenu::updateJobStatus()
{
util::clear();
std::cout << "Complete Job\n";
std::cout << "Update Job Status\n";
int choice;
std::string selectedJobID;
util::ServiceJobStatus selectedJobStatus = util::ServiceJobStatus::PENDING;
util::Map<std::string, const JobCard*> assignedJobCards = m_controller.getJobCardsByUser();
util::Map<std::string, const JobCard*> startedJobCards = filterStartedJobCards(assignedJobCards);
std::string selectedJobID = selectJobCardToComplete(startedJobCards);
std::cout << "Select the type of job you want to update:\n1.Started\n2.In Progress\nChoice: ";
util::read(choice);
if (choice == 1)
{
selectedJobStatus = util::ServiceJobStatus::STARTED;
}
else if (choice == 2)
{
selectedJobStatus = util::ServiceJobStatus::IN_PROGRESS;
}
else
{
std::cout << "Invalid choice. Please try again.\n";
util::pressEnter();
return;
}
util::Map<std::string, const JobCard*> selectedTypeJobCard = filterJobCards(assignedJobCards, selectedJobStatus);
selectedJobID = selectJobCardToUpdate(selectedTypeJobCard, selectedJobStatus);
if (!selectedJobID.empty())
{
m_controller.completeJob(selectedJobID);
std::cout << "\nJob marked as completed.\n\n";
m_controller.updateJobStatus(selectedJobID);
std::cout << "\nJob status updated.\n\n";
}
util::pressEnter();
}
@@ -17,8 +17,9 @@ private:
bool handleOperation(int choice);
public:
void showMenu();
void completeJob();
void displayJobs();
void updateJobStatus();
void viewNotifications();
void logout();
void changePassword();
};
};
@@ -27,8 +27,11 @@ void UserInterface::run()
{
try
{
m_controller.loadSystemData();
m_controller.runSystemChecks();
if (!m_controller.initialize())
{
std::cout << "Error: Failed to initialize the system!";
return;
}
bool isMenuActive = true;
while (isMenuActive)
{
@@ -49,7 +52,7 @@ void UserInterface::run()
util::pressEnter();
}
}
m_controller.saveSystemData();
m_controller.shutdown();
}
catch (const std::invalid_argument& exception)
{
@@ -106,7 +109,7 @@ void UserInterface::login()
std::cout << "Enter username: ";
util::read(username);
std::cout << "Enter password: ";
util::read(password);
util::readPassword(password);
if (m_controller.login(username, password))
{
const User* authenticatedUser = m_controller.getAuthenticatedUser();
@@ -167,7 +170,7 @@ void UserInterface::registerCustomer()
return;
}
std::cout << "Enter password: ";
util::read(password);
util::readPassword(password);
if (!util::isPasswordValid(password))
{
std::cout << "Error: Password is invalid!";
@@ -185,4 +188,4 @@ void UserInterface::registerCustomer()
m_controller.createCustomer(username, name, password, email, phone);
std::cout << "Registration is successful";
util::pressEnter();
}
}
@@ -27,4 +27,4 @@ public:
void run();
void login();
void registerCustomer();
};
};