Compare commits

...

50 Commits

Author SHA1 Message Date
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
Avinash Rajesh b7bc1f574d Implement Review Fixes 2026-05-27 17:32:36 +05:30
Avinash Rajesh b25b3d59cf Fix Admin Menu Remove User Incomplete Data
- Updated active user display to include Full Name column.
- Ensured both header and row output show full name alongside ID, username, and user type.
- Improved clarity of Remove User submenu by presenting complete user information.

Fixes #1788
2026-05-27 17:12:23 +05:30
Avinash Rajesh c1bd2a6ef1 Fix Admin Inventory Management UI Issues and Workflow Enhancements
- Added heading output to Remove Inventory Item submenu for clearer user 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)).

Fixes #1778
2026-05-27 17:12:23 +05:30
Avinash Rajesh 859f7bbeaa Fix Customer Removal and Cancellation Handling Issues
- Refactored customer and technician removal flow to ensure linked job cards and service bookings are properly cancelled.
- Added inventory restoration logic to avoid duplicate restocking when cancelling bookings.
- Introduced processBookingCancellation helper for consistent cancellation handling, notifications, and technician reassignment.
- Updated UserManagementService::removeUser to invoke appropriate cancellation routines based on user type.
- Ensured customer references and IDs are preserved correctly during booking cancellation.

Fixes #1781
2026-05-27 17:12:20 +05:30
Jissin Mathew d6cc6fc04f Merged PR 1129: Fix view invoices, combo packages, and technician menu UI issues
Changes
- Added tabular invoice list with selection before full details
- Displayed Payment Mode in detailed invoice view
- Removed duplicate pressEnter() calls and improved console messages
- Changed combo package bookings to start with PENDING status
- Added filterComboPackages to list only active packages
- Improved CustomerMenu combo package selection with clearer messages and formatting
- Enhanced output with console clearing, spacing, and success feedback
- Added truncateString utility for consistent display of long names
- Updated filter functions to use reference parameters for efficiency
- Cleared console before displaying Complete Job screen
- Improved submenu header and formatting for clarity
- Prevented table headers from showing when no jobs exist
- Refined job completion flow with consistent messages

Fixes #1779
Fixes #1782
Fixes #1784

Related work items: #1779, #1782, #1784
2026-05-27 17:09:18 +05:30
Jissin Mathew 62cd7dcc89 Implement Review Fixes 2026-05-27 17:09:03 +05:30
Jissin Mathew c67bf962c1 Fix Customer Menu View Invoices UI and Navigation Issues
- Added tabular invoice list for better navigation
- Enabled user to select an invoice before viewing full details
- Displayed Payment Mode in detailed invoice view
- Removed duplicate pressEnter() calls
- Improved console messages and formatting for clarity

Fixes #1784
2026-05-27 16:55:14 +05:30
Jissin Mathew efd8b32a5f Fix Combo Package Status and Creation Flow Issues
- Changed combo package bookings to start with PENDING status
- Added filterComboPackages to list only active packages
- Improved CustomerMenu combo package selection with clearer messages and formatting
- Enhanced output with console clearing, spacing, and success feedback
- Added truncateString utility for consistent display of long names
- Updated filter functions to use reference parameters for efficiency

Fixes #1779
2026-05-27 16:53:30 +05:30
Jissin Mathew 01596e825e Fix Technician Menu Complete Job UI Issues
- Cleared console before displaying Complete Job screen
- Improved submenu header and formatting for clarity
- Prevented table headers from showing when no jobs exist
- Refined job completion flow with consistent messages

Fixes #1782
2026-05-27 16:53:29 +05:30
joelthomastrenser fb50aeba47 Merged PR 1127: Fix notification UI formatting, payment flow issues, and observer cleanup
Changes:

- Fix notification table alignment issues in admin notification screens
- Add proper spacing for notification title column display
- Remove unnecessary tab spacing from Configure Notification Preferences heading
- Change ServiceBooking ID prefix to avoid conflict with Service IDs
- Remove unnecessary newline characters from service booking completion notifications
- Detach removed users from all service observer lists during user removal
- Fix Complete Payments screen clearing immediately after heading display
- Improve table spacing in Complete Payments screen
- Prevent invalid payment mode selection from defaulting to OFFLINE mode

Fixes #1780
Fixes #1783
Fixes #1777
Fixes #1786

Related work items: #1777, #1780, #1783, #1786
2026-05-27 16:49:20 +05:30
joelthomastrenser defee9aa15 Fix ServiceBooking ID prefix and notification messages
- change ServiceBooking ID prefix from `SRV` to `SBK`
- add customer notification when technician is assigned to booking
- fix typo in job completion exception message
- remove unnecessary newline characters from completion notifications

Fixes #1780
2026-05-27 16:02:23 +05:30
joelthomastrenser 67e5917a57 Fix: improve complete payments table formatting and payment mode validation
- Increased spacing between invoice table columns for better readability
- Updated invoice table headers with clearer labels
- Prevented screen clear before invoice listing display
- Changed payment mode selection to re-prompt on invalid input instead of defaulting to offline mode
- Removed unnecessary blank line before service listing display

Fixes #1786
2026-05-27 13:02:40 +05:30
joelthomastrenser 83e2bed432 Fix: improve notification display formatting and add global exception handling
- add truncateString utility for long notification titles
- improve notification table alignment
- remove unnecessary heading spacing in notification preferences
- add application-level exception handling in UserInterface::run()

Fixes #1777
2026-05-27 12:19:34 +05:30
joelthomastrenser a9c8ec93b7 Fix: detach removed users from service observers
- detach users from InventoryManagementService observers during removal
- detach users from PaymentManagementService observers during removal
- detach users from ServiceManagementService observers during removal

Fixes #1783
2026-05-27 11:10:59 +05:30
Jissin Mathew 13b7d6d40a Merged PR 1114: Fix Admin Menu, Service Management, Technician Assignment, and Invoice Display Issues
**Changes:**

- Fixed booking status flow (PENDING → STARTED) during job card creation
- Updated purchaseService to initialize bookings with PENDING status
- Filtered active users and services before listing/selection
- Improved Assign Job flow: pending bookings only, clearer technician prompts, consistent feedback
- Enhanced View Invoices: reference parameter, empty items handling, improved formatting
- Added filterActiveServices helper for consistent active service filtering
- Fixed Remove Service to also deactivate dependent combo packages
- Improved Create Combo Packages: cancel option, confirmation step, duplicate/edge case handling
- Updated Remove Combo Package to handle empty states gracefully without exceptions

Related work items: #1646, #1740, #1744, #1745, #1747, #1749, #1752
2026-05-26 21:26:37 +05:30
joelthomastrenser 665e83d56a Implement review fixes 2026-05-26 21:25:02 +05:30
Jissin Mathew 8268b90d82 Fix Service Removal and Combo Package Management Issues
- Added ComboPackage dependency check in removeService; deactivates related packages when a service is removed.
- Added filterActiveServices helper to list only active services.
- Updated removeService and removeComboPackage with headings, empty-state checks, and consistent success/error messages.
- Enhanced createComboPackages:
  - Uses active services only.
  - Added cancel option and confirmation after first selection.
  - Prevented duplicate selection and infinite loop when limited services exist.
  - Improved feedback when all available services are selected.
- Updated selectServicesToRemove and selectComboPackage to handle empty states gracefully without exceptions.

Fixes #1749
2026-05-26 21:13:40 +05:30
Jissin Mathew 7646ce6644 Fix Assign Job to Technician issues and View invoices issues
- Assign Job to Technician:
  - Added heading for clearer user guidance.
  - Filtered only pending service bookings for assignment.
  - Improved technician listing and selection with clearer prompts.
  - Ensured booking status transitions correctly from PENDING to STARTED when job cards are created.
  - Enhanced feedback messages for technician availability and job card creation.

- View Invoices:
  - Added heading "View Invoices" for better UI consistency.
  - Updated displayInvoices to take map by reference for efficiency.
  - Improved formatting of invoice details with consistent spacing and line breaks.
  - Added handling for empty invoice parts list (shows "No inventory items used").
  - Enhanced error messages when encountering null invoices.

Fixes #1745
Fixes #1752
2026-05-26 21:13:13 +05:30
Jissin Mathew 05499e4890 Fix Create Service Submenu
- Added heading "Create Service" to submenu for clearer user guidance.
- Ensured console clears before service creation for consistent UI behavior.
- Filtered active inventory items before selection to avoid showing inactive items.
- Updated prompts with improved formatting and spacing (e.g., labour cost input, success message).
- Refactored selectInventoryItems:
  - Added "Select Required Items" heading for clarity.
  - Skipped null or inactive items during listing.
  - Improved empty inventory message ("No Items Present, Inventory empty").
  - Enhanced success and error messages with consistent line breaks.
  - Simplified logic for item selection and exit handling.

Fixes #1747
2026-05-26 21:12:17 +05:30
Jissin Mathew 3b82648e45 Fix Add Technician submenu
- Updated AdminMenu::addTechnician:
  - Added heading "Add Technician" for better user guidance.
  - Removed setw formatting, replaced with simpler prompts.
  - Ensured console clears before operation for consistency.
  - Improved error messages for password, email, and phone validation with line breaks for readability.
  - Enhanced success message with consistent spacing.
- Minor cleanup in TechnicianMenu.cpp by adding a blank line after file header for formatting consistency.

Fixed #1744
2026-05-26 21:12:16 +05:30
Jissin Mathew 937ba2e7cf Fix Add Inventory Item Issues
- Added heading "Add Inventory Item" to submenu for clearer user guidance.
- Updated option text from "Add Quantity" to "Restock Item".
- Ensured console clearing (util::clear) before add item and restock operations.
- Reformatted prompts with consistent spacing and line breaks (e.g., "Enter Item Details", "Select Item to Restock").
- Added default case handling in AdminMenu for invalid choices.
- Enhanced MenuHelper output:
  - Extra line breaks for readability.
  - Improved error messages ("Invalid index selected", "No active items available").
  - Clearer success message when updating stock with new quantity.

Fixes #1740
2026-05-26 21:12:16 +05:30
Avinash Rajesh f63e4056f2 Merged PR 1110: Fix user management, customer menu, inventory, notification, and validation issues
Changes:
- Added validation in AdminMenu::viewStockLevels and improved stock level formatting with headers, column widths, and pressEnter prompts
- Enhanced AdminMenu::checkStockAvailability with header, clear screen, and missing item validation
- Updated AdminMenu::removeUser with header and excluded ADMIN users from active list
- Improved CustomerMenu::completePayments with pending invoice checks, error handling, and clearer headers
- Refined selectInvoiceFromUserForPayment in MenuHelper.h (inline, clear screen, updated headers)
- Added "View Service History" header in CustomerMenu::viewServiceHistory and widened table columns
- Improved notification handling: added headers, empty list validation, pressEnter prompts, and adjusted column widths
- Simplified notification titles in Inventory, Payment, and Service Management services
- Strengthened UserManagementService::updateUserDetails with duplicate checks (email, phone), clearer errors, and Validator integration
- Implemented new duplicate validation functions (isUsernameDuplicate, isPhoneDuplicate, isEmailDuplicate) in Validator utilities
- Updated CustomerMenu::updateDetails with header, improved error/success message formatting, and validation integration

Related work items: #1646, #1739, #1741, #1742, #1746, #1748, #1750
2026-05-26 21:05:38 +05:30
joelthomastrenser febfa45e4a Implement review fixes
Changes:

 - Strengthened UserManagementService::updateUserDetails by checking duplicates only when email/phone are changed, preventing false errors
 - Updated AdminMenu::viewStockLevels header text from "View Stock Level" to "View Stock Levels" for consistency
 - Cleaned up CustomerMenu::updateDetails by removing unused user list retrieval and improving header/message formatting
2026-05-26 21:03:22 +05:30
Avinash Rajesh 33cbb1dac3 Fix Update Profile and User Validation Issues
- Included Validator.h in UserManagementService.cpp for duplicate checks.
- Enhanced updateUserDetails in UserManagementService to validate:
  - Throw error if user does not exist.
  - Throw error if email already exists among active users.
  - Throw error if phone number already exists among active users.
- Implemented new duplicate validation functions in Validator.cpp:
  - isUsernameDuplicate
  - isPhoneDuplicate
  - isEmailDuplicate
- Declared new duplicate validation functions in Validator.h.
- Updated CustomerMenu::updateDetails:
  - Added "Update Details" header for clarity.
  - Improved error messages with newline formatting.
  - Added success message with newline formatting.

Fixes #1746
2026-05-26 20:24:36 +05:30
Avinash Rajesh 80b91f3f1b Fix View Service History and View Notification Issue
- Updated sendNotification in InventoryManagementService, PaymentManagementService, and ServiceManagementService to use only the provided title instead of prefixing with service name.
- Added "View Service History" header in CustomerMenu::viewServiceHistory for clarity.
- Adjusted column widths in service history table for better alignment:
  - Booking ID column widened to 15.
  - Vehicle Brand, Vehicle Number, Vehicle Model, Discount %, and Status columns widened to 20.
- Updated output formatting in CustomerMenu::viewServiceHistory to match new widths.
- Added "View and Delete Notification" header in MenuHelper::viewAndDeleteNotification for clarity.
- Moved empty notification validation from selectNotification to viewAndDeleteNotification:
  - Displays "No notifications available." message.
  - Added util::pressEnter() prompt before returning when no notifications exist.
- Increased Notification title column width from 30 to 35 in selectNotification for improved readability.

Fixes #1748
2026-05-26 20:23:18 +05:30
Avinash Rajesh a87af89a8a Fix Complete Payment Issue
- Added "Complete Payments" header in CustomerMenu::completePayments for clarity.
- Implemented validation to check if invoices list is empty and display appropriate message.
- Added logic to verify presence of pending invoices before proceeding with payment.
- Enhanced error handling to show "Payment failed" with pressEnter prompt when no invoice is selected.
- Updated selectInvoiceFromUserForPayment in MenuHelper.h:
  - Changed function to inline and added util::clear() at start.
  - Updated column headers to "Technician ID" and "Technician Name" for clarity.
- Removed temporary AdminMenu.cpp file from enc_temp_folder.

Fixes #1750
2026-05-26 20:23:16 +05:30
Avinash Rajesh f1ca8c2a58 Fix Remove User Issue
- Added "Remove User" header in AdminMenu::removeUser for clarity.
- Updated filterActiveUsers in MenuHelper.h to exclude ADMIN users from the active user list.

Fixes #1742
2026-05-26 20:23:16 +05:30
Avinash Rajesh d55bbb6349 Fix Check stock availability Issue and Add error message
- Added "Check Stock Availability" header in AdminMenu::checkStockAvailability for clarity.
- Updated prompt text to "Enter the Item ID" for consistency.
- Added util::clear() after reading Item ID to refresh the screen before displaying results.
- Implemented validation to show "Item not Found" message when the entered ID does not exist in inventory.
Fix #1741
2026-05-26 20:23:15 +05:30
Avinash Rajesh ae488f5670 Fix View Stock Level and improve error messages
- Added validation in AdminMenu::viewStockLevels to check for empty inventory before displaying.
- Added validation in AdminMenu::viewStockLevels to ensure only active items are shown.
- Added "View Stock Level" header for clarity.
- Adjusted column widths for Quantity and Price to 15 for better alignment.
- Added final newline and util::pressEnter() prompt after displaying stock levels.
Fixes  #1739
2026-05-26 20:23:15 +05:30
joelthomastrenser d8f7e46180 Merged PR 1108: Fix authentication, registration, customer menu, utility, and structural issues
Changes:

- Fixed login error handling and authentication validation issues
- Added duplicate checks during customer registration
- Fixed missing headers and message formatting issues in Login/Register screens
- Improved common utility and console interaction behavior
- Fixed Select Individual Service issues (booking status and empty service handling)
- Fixed Select Combo Package issues (header and empty combo package handling)
- Fixed low stock alert loop condition bug
- Updated MenuHelper.h functions from static to inline

Related work items: #1646, #1736, #1737, #1738, #1743, #1751, #1753, #1754
2026-05-26 20:14:40 +05:30
joelthomastrenser 3d7944f77d Impelement review fixes 2026-05-26 20:12:57 +05:30
joelthomastrenser 9439202c5a Fix: improve combo package selection UI and empty state handling
- add Select a Combo Package header
- show message when no combo packages are available
- improve combo package table header labels

Fixes #1743
2026-05-26 18:04:05 +05:30
joelthomastrenser e6faa63b88 Fix: improve customer service selection flow and booking status
- Set new service bookings to PENDING instead of STARTED
- Show message when no services are available
- Add Select a Service header
- Add Enter Vehicle Details header
- Improve service selection screen flow

Fixes #1753
2026-05-26 17:46:11 +05:30
joelthomastrenser 31e660bc9e Fix: add duplicate user validation and update register customer UI
- add username duplicate validation
- add email duplicate validation
- add phone duplicate validation
- move duplicate checks to Validator utility
- add Register Customer header
- update registration success message

Fixes #1737
2026-05-26 16:47:11 +05:30
joelthomastrenser 5fd0a47459 Fix off-by-one error in sendLowStockAlerts loop
Fixes #1751
2026-05-26 14:22:41 +05:30
joelthomastrenser fb509ccb3f Change static functions to inline in MenuHelper
Fixes #1754
2026-05-26 12:40:27 +05:30
joelthomastrenser 8d07b11eae Fix login validation and improve error messages
- Check if user account is inactive before allowing login
- Add password confirmation in change password dialog
- Prevent changing password to the same value
- Standardize error messages to say "Invalid index" instead of "Invalid choice"
- Add blank line before system pause prompt
- Add "Change Password" header to change password screen
- Show error message when login fails

Fixes #1738
Fixes #1736
2026-05-26 12:18:38 +05:30
Jissin Mathew 1e11903cca Merged PR 1069: fix: cleanup and small improvements
fix: cleanup and small improvements

- added empty check in inventory alerts
- corrected parts cost calculation
- removed unused getUser method
- updated default admin password
- fixed missing includes in menus
- cleaned up MenuHelper comments
2026-05-25 20:15:26 +05:30
Jissin Mathew 089fe496b2 Merged PR 1042: Service-Management-1553, Payment-Management-1598
**Service Management**
**SER001 - Assign Job to Technician:** Allows admins to assign service jobs to technicians for efficient work distribution.
**SER002 - Create Service:** Allows admins to create new services that customers can select and book.
**SER003 - Remove Service:** Allows admins to remove outdated services from the system.
**SER004 - View Service History:** Allows customers to view the status and history of their booked services.
**SER005 - Update Service Status:** Allows technicians to update service progress so customers can track it.

**Payment Management**
**PAY001 - Generate Invoice:** Automatically creates invoices with service, parts, and labor cost details for customer payments.
**PAY002 - Complete Payments:** Allows customers to complete pending payments and receive confirmation.
**PAY003 - View Invoices:** Allows customers to view invoice details for their purchased services.

Related work items: #1553, #1577, #1578, #1579, #1580, #1581, #1598, #1599, #1600, #1601, #1655, #1656, #1679, #1680
2026-05-25 20:07:59 +05:30
24 changed files with 1443 additions and 523 deletions
@@ -392,9 +392,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);
}
/*
@@ -409,10 +409,8 @@ void Controller::removeUser(const std::string& userID)
User* user = m_userManagementService.getUser(userID);
if (!user)
{
throw std::runtime_error("Error User not Found.\n");
throw std::runtime_error("Error: User not Found.\n");
}
m_serviceManagementService.cancelCustomerServiceBookings(userID);
m_serviceManagementService.cancelTechnicianJobs(userID);
m_userManagementService.removeUser(userID);
}
@@ -59,7 +59,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, 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);
@@ -1 +0,0 @@
Place files here.
@@ -24,9 +24,10 @@ Parameters: None
Returns: A new ServiceBooking object.
*/
ServiceBooking::ServiceBooking()
: m_id("SRV" + std::to_string(++m_uid)),
: m_id("SBK" + std::to_string(++m_uid)),
m_customer(nullptr),
m_assignedTechnician(nullptr),
m_status(util::ServiceJobStatus::PENDING),
m_discountPercentage(0.0) {}
/*
@@ -56,7 +57,7 @@ ServiceBooking::ServiceBooking(
const std::string& vehicleModel,
double discountPercentage
)
: m_id("SRV" + std::to_string(++m_uid)),
: m_id("SBK" + std::to_string(++m_uid)),
m_status(status),
m_services(services),
m_customerId(customerId),
@@ -80,7 +80,7 @@ void InventoryManagementService::sendLowStockAlerts()
{
throw std::runtime_error("The system has no admins present!");
}
for (int index = 0; index <= inventoryItemsSize; index++)
for (int index = 0; index < inventoryItemsSize; index++)
{
InventoryItem* inventoryItem = inventoryItems.getValueAt(index);
if (inventoryItem && inventoryItem->getQuantity() < config::threshold::INVENTORY_LOW_STOCK_THRESHOLD)
@@ -323,7 +323,7 @@ void InventoryManagementService::sendNotification(User* user, const std::string&
Factory::getObject<Notification>(
user->getId(),
user,
"InventoryManagementService: " + title,
title,
message,
util::Timestamp()
);
@@ -86,7 +86,7 @@ void PaymentManagementService::sendNotification(User* user, const std::string& t
Factory::getObject<Notification>(
user->getId(),
user,
"PaymentManagementService: " + title,
title,
message,
util::Timestamp()
);
@@ -294,7 +294,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 +303,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 +315,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);
}
@@ -375,7 +376,7 @@ void PaymentManagementService::completePayment(const std::string& invoiceID, uti
invoice->setStatus(util::PaymentStatus::COMPLETED);
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);
}
}
@@ -23,7 +23,6 @@ Date:19-May-2026
#include "ServiceBooking.h"
#include "ServiceManagementService.h"
#include "Timestamp.h"
#include "Timestamp.h"
#include "User.h"
#include "UserManagementService.h"
#include "Utility.h"
@@ -61,7 +60,7 @@ void ServiceManagementService::purchaseService(const util::Vector<std::string>&
Service* service = servicesMap.getValueAt(serviceIndex);
selectedServices[service->getId()] = service;
}
ServiceBooking* serviceBooking = Factory::getObject<ServiceBooking>(util::ServiceJobStatus::STARTED, selectedServices, authenticatedUser->getId(), authenticatedUser, vehicleNumber, vehicleBrand, vehicleModel, 0);
ServiceBooking* serviceBooking = Factory::getObject<ServiceBooking>(util::ServiceJobStatus::PENDING, selectedServices, authenticatedUser->getId(), authenticatedUser, vehicleNumber, vehicleBrand, vehicleModel, 0);
if (serviceBooking == nullptr)
{
throw std::runtime_error("Failed to create service booking");
@@ -100,7 +99,7 @@ void ServiceManagementService::purchaseComboPackage(const std::string& comboPack
}
const ComboPackage* comboPackage = comboPackagesMap[comboPackageID];
util::Map<std::string, Service*> selectedServices = comboPackage->getServices();
ServiceBooking* serviceBooking = Factory::getObject<ServiceBooking>(util::ServiceJobStatus::STARTED, selectedServices, authenticatedUser->getId(), authenticatedUser, vehicleNumber, vehicleBrand, vehicleModel, comboPackage->getDiscountPercentage());
ServiceBooking* serviceBooking = Factory::getObject<ServiceBooking>(util::ServiceJobStatus::PENDING, selectedServices, authenticatedUser->getId(), authenticatedUser, vehicleNumber, vehicleBrand, vehicleModel, comboPackage->getDiscountPercentage());
if (serviceBooking == nullptr)
{
throw std::runtime_error("Failed to create combo package service booking");
@@ -175,7 +174,7 @@ void ServiceManagementService::sendNotification(User* user, const std::string& t
Factory::getObject<Notification>(
user->getId(),
user,
"ServiceManagementService: " + title,
title,
message,
util::Timestamp()
);
@@ -508,6 +507,89 @@ void ServiceManagementService::saveObservers()
util::saveObservers(config::file::SERVICEMANAGEMENTOBSERVERS, this);
}
/*
Function: restoreInventory
Description: Restores inventory quantities for all required items in the services associated
with a given booking. Each item's quantity is incremented by a fixed value.
Parameter: ServiceBooking* booking - Pointer to the booking whose inventory items need to be restored
Return type: void
*/
static void restoreInventory(ServiceBooking* booking)
{
const int INCREMENT_VALUE = 1;
if (!booking)
{
return;
}
const auto& services = booking->getServices();
for (int serviceIterator = 0; serviceIterator < services.getSize(); ++serviceIterator)
{
Service* service = services.getValueAt(serviceIterator);
if (!service)
{
continue;
}
const auto& items = service->getRequiredInventoryItems();
for (int InventoryIterator = 0; InventoryIterator < items.getSize(); ++InventoryIterator)
{
InventoryItem* item = items.getValueAt(InventoryIterator);
if (item)
{
item->setQuantity(item->getQuantity() + INCREMENT_VALUE);
}
}
}
}
/*
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
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)
{
if (!booking || !notifyUser)
{
return;
}
for (int jobIterator = 0; jobIterator < jobs.getSize(); ++jobIterator)
{
JobCard* jobCard = jobs.getValueAt(jobIterator);
if (jobCard && jobCard->getBookingId() == booking->getId())
{
jobCard->setStatus(jobCardStatus);
currentService.sendNotification(notifyUser, jobNotificationTitle, jobNotificationMessage);
}
}
booking->setStatus(newServiceBookingStatus);
currentService.sendNotification(notifyUser, notificationTitle, notificationMessage);
if (newServiceBookingStatus == util::ServiceJobStatus::PENDING)
{
booking->setAssignedTechnician(nullptr);
booking->setAssignedTechnicianId("");
}
restoreInventory(booking);
}
/*
Function: cancelCustomerServiceBookings
Description: Cancels all service bookings associated with a given customer or technician.
@@ -516,69 +598,47 @@ Description: Cancels all service bookings associated with a given customer or te
Parameter: const std::string& userID - ID of the customer or technician
Return type: void
*/
void ServiceManagementService::cancelCustomerServiceBookings(const std::string& userID)
void ServiceManagementService::cancelCustomerServiceBookings(const std::string& customerID)
{
const int INCREMENT_VALUE = 1;
auto& users = m_dataStore.getUsers();
int userIndex = users.find(userID);
int userIndex = users.find(customerID);
if (userIndex == -1)
{
throw std::runtime_error("User not found: " + userID);
throw std::runtime_error("User not found: " + customerID);
}
User* user = users.getValueAt(userIndex);
if (user == nullptr)
User* customer = users.getValueAt(userIndex);
if (!customer)
{
throw std::runtime_error("User not found: " + userID);
throw std::runtime_error("User not found: " + customerID);
}
util::UserType type = user->getUserType();
auto& bookings = DataStore::getInstance().getServiceBookings();
for (int bookingIterator = 0; bookingIterator < bookings.getSize(); bookingIterator++)
auto& bookings = m_dataStore.getServiceBookings();
auto& jobs = m_dataStore.getJobCards();
for (int iteratorOne = 0; iteratorOne < bookings.getSize(); iteratorOne++)
{
ServiceBooking* booking = bookings.getValueAt(bookingIterator);
if (booking != nullptr &&
(booking->getCustomerId() == userID || booking->getAssignedTechnicianId() == userID))
ServiceBooking* booking = bookings.getValueAt(iteratorOne);
if (!booking)
{
if (booking->getStatus() == util::ServiceJobStatus::PENDING ||
booking->getStatus() == util::ServiceJobStatus::STARTED)
{
if (type == util::UserType::CUSTOMER)
{
booking->setStatus(util::ServiceJobStatus::CANCELLED);
booking->setCustomer(nullptr);
booking->setCustomerId("");
User* assignedTechnician = booking->getAssignedTechnician();
std::string title = "Customer Service Cancelled";
std::string message = "The customer has cancelled their service booking. Your assigned job card has been cancelled and the inventory has been restocked.";
sendNotification(assignedTechnician, title, message);
}
else if (type == util::UserType::TECHNICIAN)
{
booking->setStatus(util::ServiceJobStatus::PENDING);
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.";
sendNotification(booking->getCustomer(), title, message);
}
booking->setAssignedTechnician(nullptr);
booking->setAssignedTechnicianId("");
const auto& ListOfServices = booking->getServices();
for (int serviceIterator = 0; serviceIterator < ListOfServices.getSize(); serviceIterator++)
{
Service* service = ListOfServices.getValueAt(serviceIterator);
if (service != nullptr)
{
const auto& items = service->getRequiredInventoryItems();
for (int itemIterator = 0; itemIterator < items.getSize(); itemIterator++)
{
InventoryItem* item = items.getValueAt(itemIterator);
if (item != nullptr)
{
item->setQuantity(item->getQuantity() + INCREMENT_VALUE);
}
}
}
}
}
continue;
}
if (booking->getCustomerId() != customerID)
{
continue;
}
if (booking->getStatus() != util::ServiceJobStatus::PENDING && booking->getStatus() != util::ServiceJobStatus::STARTED)
{
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
);
}
}
@@ -591,34 +651,48 @@ Return type: void
*/
void ServiceManagementService::cancelTechnicianJobs(const std::string& technicianID)
{
const int INCREMENT_VALUE = 1;
auto& jobs = m_dataStore.getJobCards();
for (int jobIterator = 0; jobIterator < jobs.getSize(); jobIterator++)
auto& users = m_dataStore.getUsers();
int userIndex = users.find(technicianID);
if (userIndex == -1)
{
JobCard* job = jobs.getValueAt(jobIterator);
if (job != nullptr && job->getTechnicianId() == technicianID)
throw std::runtime_error("User not found: " + technicianID);
}
User* technician = users.getValueAt(userIndex);
if (!technician)
{
throw std::runtime_error("User not found: " + technicianID);
}
auto& bookings = m_dataStore.getServiceBookings();
auto& jobs = m_dataStore.getJobCards();
for (int iteratorOne = 0; iteratorOne < bookings.getSize(); iteratorOne++)
{
ServiceBooking* booking = bookings.getValueAt(iteratorOne);
if (!booking)
{
if (job->getStatus() == util::ServiceJobStatus::PENDING || job->getStatus() == util::ServiceJobStatus::STARTED)
{
job->setStatus(util::ServiceJobStatus::CANCELLED);
std::string title = "Job Cancelled";
std::string message = "The Job has cancelled. Your job card has been cancelled and the inventory has been restocked.";
sendNotification(job->getTechnician(), title, message);
Service* service = job->getService();
if (service != nullptr)
{
const auto& items = service->getRequiredInventoryItems();
for (int itemIterator = 0; itemIterator < items.getSize(); itemIterator++)
{
InventoryItem* item = items.getValueAt(itemIterator);
if (item != nullptr)
{
item->setQuantity(item->getQuantity() + INCREMENT_VALUE);
}
}
}
}
continue;
}
std::string technicianId = booking->getAssignedTechnicianId();
if (technicianId != technicianID)
{
continue;
}
if (booking->getStatus() != util::ServiceJobStatus::PENDING && booking->getStatus() != util::ServiceJobStatus::STARTED)
{
continue;
}
User* customer = booking->getCustomer();
if (!customer)
{
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
);
}
}
@@ -817,6 +891,10 @@ void ServiceManagementService::createJobCard(const std::string& bookingID, const
}
currentBooking->setAssignedTechnician(selectedTechnician);
currentBooking->setAssignedTechnicianId(selectedTechnician->getId());
if (currentBooking->getStatus() == util::ServiceJobStatus::PENDING)
{
currentBooking->setStatus(util::ServiceJobStatus::STARTED);
}
std::string title = "Job card created";
std::string message = "Job card created for the service and you are assigned for that.";
JobCard* jobCard = Factory::getObject<JobCard>(bookingID, currentBooking, currentService, serviceID, technicianID, selectedTechnician, util::Timestamp(), util::ServiceJobStatus::STARTED, util::Timestamp());
@@ -829,6 +907,9 @@ void ServiceManagementService::createJobCard(const std::string& bookingID, const
{
throw std::runtime_error("Failed to create job card.");
}
title = "Technician assigned";
message = "A technician has been assigned to your Service Booking with ID " + bookingID;
sendNotification(currentBooking->getCustomer(), title, message);
}
/*
@@ -906,9 +987,27 @@ Throws:
void ServiceManagementService::removeService(const std::string& serviceID)
{
util::Map<std::string, Service*>& currentServices = m_dataStore.getServices();
util::Map<std::string, ComboPackage*>& currentComboPackages = m_dataStore.getComboPackages();
if (currentServices.find(serviceID) != -1)
{
currentServices.getValueAt(currentServices.find(serviceID))->setState(util::State::INACTIVE);
for (int iterator = 0; iterator < currentComboPackages.getSize(); iterator++)
{
ComboPackage* currentComboPackage = currentComboPackages.getValueAt(iterator);
if (currentComboPackage && currentComboPackage->getState() == util::State::ACTIVE)
{
util::Map<std::string, Service*> currentServices = currentComboPackage->getServices();
for (int iterator = 0; iterator < currentServices.getSize(); iterator++)
{
auto currentService = currentServices.getValueAt(iterator);
if (currentService->getId() == serviceID)
{
currentComboPackage->setState(util::State::INACTIVE);
break;
}
}
}
}
}
else
{
@@ -981,7 +1080,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;
}
@@ -991,18 +1090,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;
@@ -1027,26 +1127,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 occured 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 occured 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.\n";
std::string message = "Services completed for the booking and invoice generated.\n";
sendNotification(currentJob->getBooking()->getCustomer(), title, message);
throw std::runtime_error("Failed to update job status. Job may already be completed.");
}
}
@@ -37,7 +37,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);
@@ -19,7 +19,7 @@ Date:19-May-2026
#include "User.h"
#include "UserManagementService.h"
#include "Vector.h"
#include "Validator.h"
/*
Function: ensureAdminExists
@@ -74,16 +74,18 @@ void UserManagementService::createUser(const std::string& username, const std::s
PaymentManagementService paymentManagementService;
ServiceManagementService serviceManagementService;
auto& usersMap = m_dataStore.getUsers();
int index = usersMap.findIf(
[&](const std::string&, User* user)
{
return user->getUserName() == username;
}
);
if (index != -1)
if (util::isUsernameDuplicate(username, usersMap))
{
throw std::runtime_error("Username already exists");
}
if (util::isEmailDuplicate(email, usersMap))
{
throw std::runtime_error("Email already exists");
}
if (util::isPhoneDuplicate(phone, usersMap))
{
throw std::runtime_error("Phone already exists");
}
User* newUser = Factory::getObject<User>(username, password, name, phone, email, type);
usersMap.insert(newUser->getId(), newUser);
paymentManagementService.attach(newUser);
@@ -109,9 +111,23 @@ void UserManagementService::updateUserDetails(const std::string& userID, const s
int index = usersMap.find(userID);
if (index == -1)
{
throw std::runtime_error("User does not exist!");
throw std::runtime_error("User does not exist!\n");
}
User* user = usersMap.getValueAt(index);
if (email != user->getEmail())
{
if (util::isEmailDuplicate(email, usersMap))
{
throw std::runtime_error("Email already exists!\n");
}
}
if (phone != user->getPhone())
{
if (util::isPhoneDuplicate(phone, usersMap))
{
throw std::runtime_error("Phone number already exists!\n");
}
}
user->setEmail(email);
user->setPhone(phone);
}
@@ -281,13 +297,27 @@ Return type: void
*/
void UserManagementService::removeUser(const std::string& userID)
{
InventoryManagementService inventoryManagementService;
PaymentManagementService paymentManagementService;
ServiceManagementService serviceManagementService;
int index = m_dataStore.getUsers().find(userID);
if (index != -1)
{
User* user = m_dataStore.getUsers().getValueAt(index);
if (user != nullptr)
{
if (user->getUserType() == util::UserType::CUSTOMER)
{
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);
}
}
}
@@ -299,7 +329,7 @@ util::Map<std::string, User*> UserManagementService::getUsers(util::UserType typ
for (int iterator = 0; iterator < currentUsers.getSize(); iterator++)
{
User* currentUser = currentUsers.getValueAt(iterator);
if (currentUser->getUserType() == type)
if (currentUser && currentUser->getState() == util::State::ACTIVE && currentUser->getUserType() == type)
{
filteredUsersMap.insert(currentUser->getId(), currentUser);
}
@@ -37,6 +37,7 @@ namespace util
PENDING,
STARTED,
COMPLETED,
IN_PROGRESS,
CANCELLED
};
@@ -137,6 +138,10 @@ namespace util
{
return PaymentMode::OFFLINE;
}
if (value == "NOTSET")
{
return PaymentMode::NOTSET;
}
throw std::invalid_argument("Invalid PaymentMode string");
}
@@ -205,6 +210,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 +244,10 @@ namespace util
{
return ServiceJobStatus::CANCELLED;
}
if (value == "IN_PROGRESS")
{
return ServiceJobStatus::IN_PROGRESS;
}
throw std::invalid_argument("Invalid ServiceJobStatus string");
}
@@ -11,10 +11,39 @@ Date: 22-May-2026
#include <fstream>
#include <string>
#include <stdexcept>
#include <direct.h>
#include "Vector.h"
namespace util
{
/*
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&, relative or absolute file path
Returns:
- void
Throws:
- None (_mkdir failures are intentionally ignored if directory already exists)
*/
inline void ensureDirectoryExists(const std::string& filePath)
{
size_t position = 0;
while ((position = filePath.find('/', position)) != std::string::npos)
{
std::string directory = filePath.substr(0, position);
if (!directory.empty())
{
(void)_mkdir(directory.c_str());
}
position++;
}
}
/*
Function: loadRecords
Description: Loads records from a given file path into a vector of strings.
@@ -32,6 +61,7 @@ namespace util
std::ifstream file(filePath);
if (!file.is_open())
{
ensureDirectoryExists(filePath);
std::ofstream newFile(filePath);
newFile.close();
file.open(filePath);
@@ -15,6 +15,7 @@ Date: 22-May-2026
#include <fstream>
#include "Vector.h"
#include "Map.h"
#include "FileHelper.h"
namespace util
{
@@ -51,6 +52,7 @@ namespace util
std::ifstream file(m_filePath);
if (!file.is_open())
{
ensureDirectoryExists(m_filePath);
std::ofstream newFile(m_filePath);
newFile.close();
file.open(m_filePath);
@@ -62,6 +62,7 @@ namespace util
*/
inline void pressEnter()
{
std::cout << std::endl;
system("pause");
}
}
@@ -21,4 +21,28 @@ namespace util
{
std::cout << "\x1B[2J\x1B[H" << std::flush;
}
/*
Function: truncateString
Description:
Truncates a string if its length exceeds the given maximum length.
The truncated string ends with "..." to indicate omitted characters.
Parameters:
- text: const std::string&, input string to truncate
- maxLength: size_t, maximum allowed length of the returned string
Returns:
- std::string: Original string if within limit, otherwise truncated string with "..."
*/
inline std::string truncateString(const std::string& text, size_t maxLength)
{
if (text.length() <= maxLength)
{
return text;
}
if (maxLength <= 3)
{
return std::string(maxLength, '.');
}
return text.substr(0, maxLength - 3) + "...";
}
}
@@ -106,4 +106,70 @@ bool util::isPasswordValid(const std::string& password)
}
return hasUpper && hasLower && hasDigit && hasSpecial;
}
/*
* Function: isUsernameDuplicate
* Description: Checks if the given username already exists among active users.
* Parameters:
* username - string containing the username to validate
* 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 index != -1;
}
/*
* Function: isPhoneDuplicate
* Description: Checks if the given phone number already exists among active users.
* Parameters:
* phone - string containing the phone number to validate
* usersMap - map of user objects keyed by identifier
* Returns:
* bool - true if the phone number is already in use by an active user, false otherwise
* Notes:
* - Only considers users with state util::State::ACTIVE
*/
bool util::isPhoneDuplicate(const std::string& phone, const util::Map<std::string, User*>& usersMap)
{
int index = usersMap.findIf(
[&](const std::string&, User* user)
{
return (user->getPhone() == phone && user->getState() == util::State::ACTIVE);
}
);
return index != -1;
}
/*
* Function: isEmailDuplicate
* Description: Checks if the given email address already exists among active users.
* Parameters:
* email - string containing the email address to validate
* usersMap - map of user objects keyed by identifier
* Returns:
* bool - true if the email address is already in use by an active user, false otherwise
* Notes:
* - Only considers users with state util::State::ACTIVE
*/
bool util::isEmailDuplicate(const std::string& email, const util::Map<std::string, User*>& usersMap)
{
int index = usersMap.findIf(
[&](const std::string&, User* user)
{
return (user->getEmail() == email && user->getState() == util::State::ACTIVE);
}
);
return index != -1;
}
@@ -9,10 +9,15 @@
#include<string>
#include<algorithm>
#include<cctype>
#include "Map.h"
#include "User.h"
namespace util
{
bool isPhoneNumberValid(const std::string&);
bool isEmailValid(const std::string&);
bool isPasswordValid(const std::string&);
bool isUsernameDuplicate(const std::string&, const util::Map<std::string, User*>&);
bool isPhoneDuplicate(const std::string&, const util::Map<std::string, User*>&);
bool isEmailDuplicate(const std::string&, const util::Map<std::string, User*>&);
}
@@ -42,15 +42,18 @@ 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. Logout"
<< "\nEnter a choice: ";
util::read(choice);
if (!handleOperation(choice))
@@ -92,30 +95,39 @@ 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:
logout();
return false;
default:
@@ -158,10 +170,33 @@ void AdminMenu::viewStockLevels()
{
util::clear();
auto inventoryItems = m_controller.getInventoryItems();
bool hasActiveItems = false;
std::cout << "View Stock Levels" << std::endl;
if (inventoryItems.isEmpty())
{
std::cout << "No items found in Inventory.\n";
util::pressEnter();
return;
}
for (int index = 0; index < inventoryItems.getSize(); index++)
{
const InventoryItem* item = inventoryItems.getValueAt(index);
if (item->getState() == util::State::ACTIVE)
{
hasActiveItems = true;
break;
}
}
if (!hasActiveItems)
{
std::cout << "No active Inventory Item found.\n";
util::pressEnter();
return;
}
std::cout << std::left << std::setw(15) << "Item ID"
<< std::setw(25) << "Part Name"
<< std::setw(10) << "Quantity"
<< std::setw(10) << "Price"
<< std::setw(15) << "Quantity"
<< std::setw(15) << "Price"
<< std::endl;
for (int iterator = 0; iterator < inventoryItems.getSize(); ++iterator)
{
@@ -172,12 +207,14 @@ void AdminMenu::viewStockLevels()
{
std::cout << std::left << std::setw(15) << item->getId()
<< std::setw(25) << item->getPartName()
<< std::setw(10) << item->getQuantity()
<< std::setw(10) << item->getPrice()
<< std::setw(15) << item->getQuantity()
<< std::setw(15) << item->getPrice()
<< std::endl;
}
}
}
std::cout << "\n";
util::pressEnter();
}
/*
@@ -193,13 +230,15 @@ void AdminMenu::addInventoryItem()
int choice, quantity;
double price;
std::string partName;
std::cout << "1. Add new item \n2. Add Quantity\nEnter your choice : ";
std::cout << "Add Inventory Item\n";
std::cout << "1. Add new item \n2. Restock Item\n\nEnter your choice : ";
util::read(choice);
switch (choice)
{
case 1:
case 1:
{
std::cout << "--------Enter Item Details----------\n";
util::clear();
std::cout << "Enter Item Details\n";
std::cout << "Part Name : ";
util::read(partName);
std::cout << "Quantity : ";
@@ -207,15 +246,21 @@ void AdminMenu::addInventoryItem()
std::cout << "Price : ";
util::read(price);
m_controller.addInventoryItem(partName, quantity, price);
std::cout << "New Item " << partName << " added to the Inventory.\n";
std::cout << "\nNew Item " << partName << " added to the Inventory.\n\n";
break;
}
case 2:
{
util::clear();
std::cout << "Select Item to Restock\n";
auto inventoryItems = m_controller.getInventoryItems();
addQuantityToItem(inventoryItems, m_controller);
break;
}
default:
{
std::cout << "\nEnter a valid choice.\n\n";
}
}
util::pressEnter();
}
@@ -230,6 +275,7 @@ Return type: void
void AdminMenu::removeInventoryItem()
{
util::clear();
std::cout << "Remove Inventory Item\n";
auto inventoryItems = m_controller.getInventoryItems();
auto activeItems = filterActiveItems(inventoryItems);
int activeItemsSize = activeItems.getSize();
@@ -273,8 +319,10 @@ void AdminMenu::checkStockAvailability()
{
util::clear();
std::string itemId;
std::cout << "Enter the Item Id : ";
std::cout << "Check Stock Availability \n";
std::cout << "Enter the Item ID : ";
util::read(itemId);
util::clear();
const InventoryItem* selectedItem = m_controller.getInventoryItem(itemId);
if (selectedItem != nullptr)
{
@@ -287,6 +335,10 @@ void AdminMenu::checkStockAvailability()
std::cout << "Quantity : " << selectedItem->getQuantity() << "\n";
}
}
else
{
std::cout << "Item not Found" << std::endl;
}
util::pressEnter();
}
@@ -302,14 +354,16 @@ Returns:
void AdminMenu::assignJob()
{
util::clear();
std::cout << "Assign Job to Technician\n";
std::string selectedService;
bool hasPendingService = false;
auto currentBookings = m_controller.getServiceBookings();
auto pendingServiceBookings = filterActiveServiceBookings(currentBookings);
auto availableTechnicians = m_controller.getUsers(util::UserType::TECHNICIAN);
int bookingsSize = currentBookings.getSize();
int bookingsSize = pendingServiceBookings.getSize();
util::Map<int, const ServiceBooking*> serviceBookingsMap;
util::Map<int, const User*> currentAvailableTechniciansMap;
if (listServiceBookings(currentBookings, bookingsSize, serviceBookingsMap))
if (listServiceBookings(pendingServiceBookings, bookingsSize, serviceBookingsMap))
{
const ServiceBooking* selectedService = selectPendingServiceBookings(serviceBookingsMap);
if (selectedService)
@@ -325,14 +379,37 @@ void AdminMenu::assignJob()
{
m_controller.createJobCard(selectedService->getId(), selectedTechnician->getId(), servicesInBooking.getValueAt(iterator)->getId());
}
std::cout << "Job card created for each service and technician successfully assigned.\n\n";
}
}
else
{
std::cout << "No technicians are currently available.";
std::cout << "No technicians are currently available.\n\n";
}
}
}
else
{
std::cout << "No pending service bookings available.\n\n";
}
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();
}
@@ -347,17 +424,24 @@ Returns:
void AdminMenu::createService()
{
util::clear();
std::cout << "Create Service\n";
std::string serviceName;
double labourCost;
std::cout << "Enter the service name: ";
util::read(serviceName);
util::Map<std::string, const InventoryItem*> currentInventoryItems = m_controller.getInventoryItems();
util::Map<std::string, const InventoryItem*> activeInventoryItems = filterActiveItems(currentInventoryItems);
util::Vector<std::string> selectedInventoryItems;
selectInventoryItems(currentInventoryItems,selectedInventoryItems);
std::cout << "Enter the labour cost: ";
selectInventoryItems(activeInventoryItems,selectedInventoryItems);
if (selectedInventoryItems.isEmpty())
{
util::pressEnter();
return;
}
std::cout << "\nEnter the labour cost: ";
util::read(labourCost);
m_controller.createService(serviceName, selectedInventoryItems, labourCost);
std::cout << "Service created sucessfully.\n";
std::cout << "\nService created sucessfully.\n\n";
util::pressEnter();
}
@@ -372,21 +456,46 @@ Returns:
void AdminMenu::removeService()
{
util::clear();
std::cout << "Remove Service\n";
std::string selectedServiceID;
util::Map<std::string, const Service*> currentServices = m_controller.getServices();
selectedServiceID = selectServicesToRemove(currentServices);
util::Map<std::string, const Service*> currentActiveServices = filterActiveServices(currentServices);
selectedServiceID = selectServicesToRemove(currentActiveServices);
if (selectedServiceID != "")
{
m_controller.removeService(selectedServiceID);
std::cout << "Service removed sucessfully.";
std::cout << "Service removed successfully.\n\n";
}
else
{
std::cout << "Failed to remove service.";
std::cout << "Failed to remove service.\n\n";
}
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: addTechnician
Description: Adds a new technician after validating username, password, email, and phone number.
@@ -397,36 +506,37 @@ void AdminMenu::addTechnician()
{
util::clear();
std::string username, name, password, email, phoneNumber;
std::cout << std::left << std::setw(25) << "Enter Technician Username: ";
std::cout << "Add Technician\n";
std::cout << "Enter Technician Username: ";
util::read(username);
std::cout << std::left << std::setw(25) << "Enter Technician Name: ";
std::cout << "Enter Technician Name: ";
util::read(name);
std::cout << std::setw(25) << "Enter Technician Password: ";
std::cout << "Enter Technician Password: ";
util::read(password);
if(!util::isPasswordValid(password))
{
std::cout << "Error: Password is invalid!";
std::cout << "\nError: Password is invalid!\n\n";
util::pressEnter();
return;
}
std::cout << std::setw(25) << "Enter Technician Email: ";
std::cout << "Enter Technician Email: ";
util::read(email);
if(!util::isEmailValid(email))
{
std::cout << "Error: Email is invalid!";
std::cout << "\nError: Email is invalid!\n\n";
util::pressEnter();
return;
}
std::cout << std::setw(25) << "Enter Technician Phone: ";
std::cout << "Enter Technician Phone: ";
util::read(phoneNumber);
if(!util::isPhoneNumberValid(phoneNumber))
{
std::cout << "Error: Phone Number is invalid!";
std::cout << "\nError: Phone Number is invalid!\n\n";
util::pressEnter();
return;
}
m_controller.createTechnician(username, name, password, email, phoneNumber);
std::cout << "\nTechnician Added Successfully.\n";
std::cout << "\nTechnician Added Successfully.\n\n";
util::pressEnter();
}
@@ -443,18 +553,19 @@ void AdminMenu::removeUser()
auto listOfUsers = m_controller.getUsers();
auto listOfActiveUsers = filterActiveUsers(listOfUsers);
int activeUserCount = listOfActiveUsers.getSize();
std::cout << "Remove User \n";
if (activeUserCount < 1)
{
std::cout << "No Active users." << std::endl;
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;
}
@@ -468,6 +579,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.
@@ -477,7 +606,10 @@ Return type: void
void AdminMenu::createComboPackages()
{
util::clear();
std::cout << "Create Combo Packages\n";
auto serviceList = m_controller.getServices();
auto activeServices = filterActiveServices(serviceList);
int currentActiveServicesCount = activeServices.getSize();
const int NUMBER_OF_SERVICE_PER_PACKAGE = 2;
util::Vector<std::string> selectedServiceID;
for (int iterator = 0; iterator < NUMBER_OF_SERVICE_PER_PACKAGE; iterator++)
@@ -485,10 +617,10 @@ void AdminMenu::createComboPackages()
const Service* chosenService = nullptr;
while (true)
{
chosenService = selectServiceFromServices(serviceList);
if (chosenService == nullptr)
chosenService = selectServiceFromServices(activeServices);
if (!chosenService)
{
std::cout << "Failed to create combo package!";
std::cout << "Failed to create combo package!\n\n";
util::pressEnter();
return;
}
@@ -503,13 +635,21 @@ void AdminMenu::createComboPackages()
}
if (alreadyChosen)
{
if (currentActiveServicesCount < 2)
{
break;
}
std::cout << "Service already selected. Please choose a different one." << std::endl;
continue;
}
selectedServiceID.push_back(chosenService->getId());
util::clear();
break;
}
if (currentActiveServicesCount < 2)
{
std::cout << "All the available services selected\n\n";
break;
}
}
std::string packageName;
double discountPercentage;
@@ -537,16 +677,17 @@ Return type: void
void AdminMenu::removeComboPackage()
{
util::clear();
std::cout << "Remove Combo Package\n";
util::Map<std::string, const ComboPackage*> currentComboPackages = m_controller.getComboPackages();
std::string selectedComboPackageID = selectComboPackage(currentComboPackages);
if (selectedComboPackageID != "")
if (!selectedComboPackageID.empty())
{
m_controller.removeComboPackage(selectedComboPackageID);
std::cout << "Combo Package removed successfully.\n";
std::cout << "Combo Package removed successfully.\n\n";
}
else
{
std::cout << "Combo package removal failed.\n";
std::cout << "Combo package removal failed.\n\n";
}
util::pressEnter();
}
@@ -24,10 +24,13 @@ public:
void removeInventoryItem();
void checkStockAvailability();
void assignJob();
void displayServices();
void createService();
void removeService();
void displayUsers();
void addTechnician();
void removeUser();
void displayComboPackages();
void createComboPackages();
void removeComboPackage();
void viewNotifications();
@@ -144,11 +144,12 @@ void CustomerMenu::updateDetails()
{
std::string email, phone;
util::clear();
std::cout << "Update Details\n";
std::cout << "Enter new email: ";
util::read(email);
if (!util::isEmailValid(email))
{
std::cout << "Error: Email is invalid!";
std::cout << "Error: Email is invalid!\n";
util::pressEnter();
return;
}
@@ -156,12 +157,12 @@ void CustomerMenu::updateDetails()
util::read(phone);
if (!util::isPhoneNumberValid(phone))
{
std::cout << "Error: Phone number is invalid!";
std::cout << "Error: Phone number is invalid!\n";
util::pressEnter();
return;
}
m_controller.updateUserDetails(email, phone);
std::cout << "Profile details updated successfully";
std::cout << "Profile details updated successfully\n";
util::pressEnter();
}
@@ -175,9 +176,16 @@ Return type: void
void CustomerMenu::selectService()
{
std::string vehicleNumber, vehicleBrand, vehicleModel;
auto services = m_controller.getServices();
util::Vector<std::string> selectedServices;
util::clear();
std::cout << "Select a Service\n";
auto services = m_controller.getServices();
if (services.isEmpty())
{
std::cout << "No services available!";
util::pressEnter();
return;
}
util::Vector<std::string> selectedServices;
const Service* selectedService = selectServiceFromServices(services);
if (selectedService == nullptr)
{
@@ -187,6 +195,7 @@ void CustomerMenu::selectService()
}
selectedServices.push_back(selectedService->getId());
util::clear();
std::cout << "Enter Vehicle Details\n";
std::cout << "Enter vehicle number: ";
util::read(vehicleNumber);
std::cout << "Enter vehicle brand: ";
@@ -208,16 +217,27 @@ Return type: void
void CustomerMenu::selectComboPackage()
{
std::string vehicleNumber, vehicleBrand, vehicleModel;
auto comboPackages = m_controller.getComboPackages();
util::clear();
const ComboPackage* selectedComboPackage = selectComboPackageFromPackages(comboPackages);
if (selectedComboPackage == nullptr)
std::cout << "Select a Combo Package\n";
auto comboPackages = m_controller.getComboPackages();
util::Map<std::string, const ComboPackage*> activeComboPackages = filterComboPackages(comboPackages);
if (activeComboPackages.isEmpty())
{
std::cout << "Failed to book combo package!";
std::cout << "No combo packages available!\n\n";
util::pressEnter();
return;
}
const ComboPackage* selectedComboPackage = selectComboPackageFromPackages(activeComboPackages);
if (selectedComboPackage == nullptr)
{
std::cout << "Failed to book combo package!\n\n";
util::pressEnter();
return;
}
std::cout << "Combo Package selected\n";
util::pressEnter();
util::clear();
std::cout << "Enter the vehicle details\n";
std::cout << "Enter vehicle number: ";
util::read(vehicleNumber);
std::cout << "Enter vehicle brand: ";
@@ -225,7 +245,7 @@ void CustomerMenu::selectComboPackage()
std::cout << "Enter vehicle model: ";
util::read(vehicleModel);
m_controller.purchaseComboPackage(selectedComboPackage->getId(), vehicleNumber, vehicleBrand, vehicleModel);
std::cout << "Combo Package has been booked successfully";
std::cout << "Combo Package has been booked successfully\n\n";
util::pressEnter();
}
@@ -245,16 +265,17 @@ void CustomerMenu::viewServiceHistory()
const User* currentUser = m_controller.getAuthenticatedUser();
std::string currentUserID = currentUser->getId();
util::Map<std::string, const ServiceBooking*> serviceBookingsByCurrentUser = m_controller.getServiceBookingsByUser(currentUserID);
std::cout << "View Service History" << std::endl;
if (serviceBookingsByCurrentUser.getSize() != 0)
{
std::cout << std::left
<< std::setw(12) << "Booking ID"
<< std::setw(15) << "Booking ID"
<< std::setw(20) << "Technician"
<< std::setw(15) << "Vehicle Brand"
<< std::setw(15) << "Vehicle Number"
<< std::setw(15) << "Vehicle Model"
<< std::setw(10) << "Discount %"
<< std::setw(12) << "Status"
<< std::setw(20) << "Vehicle Brand"
<< std::setw(20) << "Vehicle Number"
<< std::setw(20) << "Vehicle Model"
<< std::setw(20) << "Discount %"
<< std::setw(20) << "Status"
<< std::endl;
for (int iterator = 0; iterator < serviceBookingsByCurrentUser.getSize(); iterator++)
{
@@ -263,13 +284,13 @@ void CustomerMenu::viewServiceHistory()
? "Not Assigned"
: currentBooking->getAssignedTechnician()->getName();
std::cout << std::left
<< std::setw(12) << currentBooking->getId()
<< std::setw(15) << currentBooking->getId()
<< std::setw(20) << technicianName
<< std::setw(15) << currentBooking->getVehicleBrand()
<< std::setw(15) << currentBooking->getVehicleNumber()
<< std::setw(15) << currentBooking->getVehicleModel()
<< std::setw(10) << currentBooking->getDiscountPercentage()
<< std::setw(12) << util::getServiceJobStatusString(currentBooking->getStatus())
<< std::setw(20) << currentBooking->getVehicleBrand()
<< std::setw(20) << currentBooking->getVehicleNumber()
<< std::setw(20) << currentBooking->getVehicleModel()
<< std::setw(20) << currentBooking->getDiscountPercentage()
<< std::setw(20) << util::getServiceJobStatusString(currentBooking->getStatus())
<< std::endl;
hasServiceHistory = true;
}
@@ -293,11 +314,35 @@ Returns:
void CustomerMenu::completePayments()
{
util::clear();
std::cout << "Complete Payments\n";
util::Map<std::string, const Invoice*> currentInvoices = m_controller.getInvoicesByUser();
if (currentInvoices.isEmpty())
{
std::cout << "No pending invoices available for payment.\n";
util::pressEnter();
return;
}
bool hasPending = false;
for (int index = 0; index < currentInvoices.getSize(); ++index)
{
const Invoice* invoice = currentInvoices.getValueAt(index);
if (invoice && invoice->getStatus() == util::PaymentStatus::PENDING)
{
hasPending = true;
break;
}
}
if (!hasPending)
{
std::cout << "No pending invoices available for payment.\n";
util::pressEnter();
return;
}
std::string selectedID = selectInvoiceFromUserForPayment(currentInvoices);
if (selectedID == "")
{
std::cout << "Payment failed.\n";
util::pressEnter();
return;
}
util::PaymentMode paymentMode = selectPaymentMode();
@@ -317,6 +362,7 @@ Returns:
void CustomerMenu::viewInvoices()
{
util::clear();
std::cout << "View Invoices\n";
util::Map<std::string, const Invoice*> currentUserInvoices = m_controller.getInvoicesByUser();
displayInvoices(currentUserInvoices);
util::pressEnter();
@@ -352,4 +398,4 @@ void CustomerMenu::configureNotifications()
util::clear();
std::cout << "Notification preferences updated successfully.\n";
util::pressEnter();
}
}
File diff suppressed because it is too large Load Diff
@@ -6,6 +6,7 @@ Description: Implementation file containing the method definitions of the
Author: Trenser
Date:19-May-2026
*/
#include "Enums.h"
#include "InputHelper.h"
#include "JobCard.h"
@@ -33,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))
@@ -63,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:
@@ -82,28 +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 << "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<int, const JobCard*> incompleteJobCards;
std::cout << "Jobs to be completed.\n";
std::string selectedJobID = selectJobCardToComplete(assignedJobCards, incompleteJobCards);
if (selectedJobID == "")
std::cout << "Select the type of job you want to update\n1.Started\n2.Inprogress\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())
{
std::cout << "Failed to complete the job.\n";
}
else
{
m_controller.completeJob(selectedJobID);
std::cout << "Job marked as completed.\n";
m_controller.updateJobStatus(selectedJobID);
std::cout << "\nJob status updated.\n\n";
}
util::pressEnter();
}
@@ -17,7 +17,8 @@ private:
bool handleOperation(int choice);
public:
void showMenu();
void completeJob();
void displayJobs();
void updateJobStatus();
void viewNotifications();
void logout();
void changePassword();
@@ -7,6 +7,8 @@ Author: Trenser
Date:19-May-2026
*/
#include <iostream>
#include <stdexcept>
#include "Enums.h"
#include "InputHelper.h"
#include "OutputHelper.h"
@@ -23,29 +25,44 @@ Return type: void
*/
void UserInterface::run()
{
m_controller.loadSystemData();
m_controller.runSystemChecks();
bool isMenuActive = true;
while (isMenuActive)
try
{
try
m_controller.loadSystemData();
m_controller.runSystemChecks();
bool isMenuActive = true;
while (isMenuActive)
{
int choice;
util::clear();
std::cout << "Vehicle Service System\n1. Login\n2. Register Customer\n3. Exit\nEnter your Choice: ";
util::read(choice);
if (!handleOperation(choice))
try
{
isMenuActive = false;
int choice;
util::clear();
std::cout << "Vehicle Service System\n1. Login\n2. Register Customer\n3. Exit\nEnter your Choice: ";
util::read(choice);
if (!handleOperation(choice))
{
isMenuActive = false;
}
}
catch (const std::exception& e)
{
std::cout << "Exception: " << e.what() << std::endl;
util::pressEnter();
}
}
catch (const std::exception& e)
{
std::cout << "Exception: " << e.what() << std::endl;
util::pressEnter();
}
m_controller.saveSystemData();
}
catch (const std::invalid_argument& exception)
{
std::cout << "Exception: Invalid Argument: " << exception.what() << std::endl;
}
catch (const std::exception& exception)
{
std::cout << "Exception: " << exception.what() << std::endl;
}
catch (...)
{
std::cout << "Unknown error occurred." << std::endl;
}
m_controller.saveSystemData();
}
/*
@@ -85,6 +102,7 @@ void UserInterface::login()
{
std::string username, password;
util::clear();
std::cout << "Login\n";
std::cout << "Enter username: ";
util::read(username);
std::cout << "Enter password: ";
@@ -92,7 +110,7 @@ void UserInterface::login()
if (m_controller.login(username, password))
{
const User* authenticatedUser = m_controller.getAuthenticatedUser();
if (authenticatedUser != nullptr)
if (authenticatedUser && authenticatedUser->getState() != util::State::INACTIVE)
{
switch (authenticatedUser->getUserType())
{
@@ -110,10 +128,16 @@ void UserInterface::login()
break;
}
}
else if (authenticatedUser && authenticatedUser->getState() == util::State::INACTIVE)
{
std::cout << "\nError: Your account has been disabled. Please contact your Administrator.";
util::pressEnter();
}
}
else
{
std::cout << "\nError: Invalid Username or Password";
util::pressEnter();
}
}
@@ -129,6 +153,7 @@ void UserInterface::registerCustomer()
{
std::string username, name, email, phone, password;
util::clear();
std::cout << "Register Customer\n";
std::cout << "Enter username: ";
util::read(username);
std::cout << "Enter name: ";
@@ -27,4 +27,4 @@ public:
void run();
void login();
void registerCustomer();
};
};