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
This commit is contained in:
Jissin Mathew
2026-05-29 13:18:11 +05:30
committed by Joel Thomas
parent 972e353832
commit 2ea77bf9b6
9 changed files with 120 additions and 41 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);
}
/*
@@ -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);
@@ -1080,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::STARTED || currentJob->getStatus() == util::ServiceJobStatus::INPROGRESS)
{
return false;
}
@@ -1101,7 +1101,7 @@ Returns:
Throws:
- std::runtime_error if technician is not authenticated, job card not found, or job already completed
*/
void ServiceManagementService::completeJob(const std::string& jobID)
void ServiceManagementService::updateJobStatus(const std::string& jobID)
{
AuthenticationManagementService authenticationManagementService;
PaymentManagementService paymentManagementService;
@@ -1126,26 +1126,32 @@ void ServiceManagementService::completeJob(const std::string& jobID)
}
if (currentJob->getStatus() == util::ServiceJobStatus::STARTED)
{
currentJob->setStatus(util::ServiceJobStatus::COMPLETED);
currentJob->setStatus(util::ServiceJobStatus::INPROGRESS);
jobStatusUpdated = true;
}
else if (currentJob->getStatus() == util::ServiceJobStatus::INPROGRESS)
{
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 the job.");
}
if (!jobStatusUpdated)
{
throw std::runtime_error("Failed to complete the job, some error occurred or job already completed.");
throw std::runtime_error("Failed to update the job");
}
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);
}
}
@@ -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);
@@ -37,6 +37,7 @@ namespace util
PENDING,
STARTED,
COMPLETED,
INPROGRESS,
CANCELLED
};
@@ -209,6 +210,8 @@ namespace util
return "COMPLETED";
case ServiceJobStatus::CANCELLED:
return "CANCELLED";
case ServiceJobStatus::INPROGRESS:
return "INPROGRESS";
}
throw std::invalid_argument("Invalid ServiceJobStatus");
}
@@ -241,6 +244,10 @@ namespace util
{
return ServiceJobStatus::CANCELLED;
}
if (value == "INPROGRESS")
{
return ServiceJobStatus::INPROGRESS;
}
throw std::invalid_argument("Invalid ServiceJobStatus string");
}
@@ -646,7 +646,37 @@ 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::INPROGRESS))
{
startedJobCards.insert(currentJobCard->getId(), currentJobCard);
}
}
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);
}
@@ -675,16 +705,18 @@ inline void displayAllJobs(util::Map<std::string, const JobCard*>& assignedJobCa
<< 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))
if (currentJobCard && (currentJobCard->getStatus() == util::ServiceJobStatus::STARTED || currentJobCard->getStatus() == util::ServiceJobStatus::INPROGRESS))
{
std::cout << std::left << 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;
}
}
@@ -698,16 +730,31 @@ Parameters:
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 << "No jobs available.\n\n";
return "";
}
int currentIndex = 1;
int choice;
if (selectedJobStatusType == util::ServiceJobStatus::STARTED)
{
util::clear();
std::cout << "Select a job to update to Inprogress\n";
}
else if (selectedJobStatusType == util::ServiceJobStatus::INPROGRESS)
{
util::clear();
std::cout << "Select a job to update to 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"
@@ -715,22 +762,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 << "Select the Job Card to Update (Index): ";
util::read(choice);
int selectedJobCardIndex = incompleteJobCards.find(choice);
if (selectedJobCardIndex != -1)
@@ -741,7 +790,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 "";
}
}
@@ -35,7 +35,7 @@ void TechnicianMenu::showMenu()
util::clear();
std::cout << "Technician Menu"
<< "\n1. Display My Jobs"
<< "\n2. Mark Job as Completed"
<< "\n2. Update Job Status"
<< "\n3. View Notifications"
<< "\n4. Change Password"
<< "\n5. Logout"
@@ -68,7 +68,7 @@ bool TechnicianMenu::handleOperation(int choice)
displayJobs();
break;
case 2:
completeJob();
updateJobStatus();
break;
case 3:
viewNotifications();
@@ -99,31 +99,48 @@ 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*> startedJobCards = filterStartedJobCards(assignedJobCards);
displayAllJobs(startedJobCards);
util::Map<std::string, const JobCard*> jobCards = filterStartedJobCards(assignedJobCards);
displayAllJobs(jobCards);
util::pressEnter();
}
/*
Function: completeJob
Description: Allows the technician to mark a selected job card as completed.
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::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.Inprogress\nChoice: ";
util::read(choice);
if (choice == 1)
{
selectedJobStatus = util::ServiceJobStatus::STARTED;
}
else if (choice == 2)
{
selectedJobStatus = util::ServiceJobStatus::INPROGRESS;
}
else
{
throw std::runtime_error("Invalid Index");
}
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();
}
@@ -18,7 +18,7 @@ private:
public:
void showMenu();
void displayJobs();
void completeJob();
void updateJobStatus();
void viewNotifications();
void logout();
void changePassword();