1032fc64bd
<User Story> Complete Payments - 1797</User Story> <Changes> 1. Added Controller::getAllInvoices - Retrieves all invoices from PaymentManagementService and returns them as a read-only map. 2. Implemented Controller::confirmPayment - Delegates payment confirmation for a given invoice ID to PaymentManagementService. 3. Introduced PaymentManagementService::getAllInvoice - Provides access to all invoices stored in the datastore. 4. Added PaymentManagementService::confirmPayment - Confirms payment for a specific invoice, updates payment date and status, and sends notification. 5. Extended util::PaymentStatus enum - Added PAID status and updated string conversion. 6. Integrated AdminMenu::confirmPayment - Validates invoice list, filters by status, allows selection, and confirms payment. 7. Updated CustomerMenu::completePayments - Uses parameterized status filtering for invoice selection. 8. Enhanced MenuHelper::selectInvoiceFromUserForPayment - Accepts requiredStatus parameter for flexible filtering. 9. Adjusted AdminMenu options - Added "Confirm Payment" before Logout. </Changes> <Test> Acceptance Criteria: 1. Admin selects "Confirm Payment" from menu. - Verify system prompts and displays invoices filtered by status. 2. Admin selects invoice with status = PAID. - Verify payment confirmation updates date, sets status, and sends notification. 3. Admin attempts confirmation with empty invoice list. - Verify error message: "No pending invoices available for confirmation." 4. Customer completes payment. - Verify selection uses util::PaymentStatus::PENDING and payment flow works correctly. 5. Invalid invoice ID entered. - Verify system throws runtime_error with "Payment failed: invalid invoice ID." Precondition: 1. Admin logged into system. 2. At least one invoice exists in datastore. 3. Notification system available. Steps: 1. Navigate to Admin menu → Confirm Payment. 2. Select invoice with PAID status. 3. Confirm payment and check notification. 4. Attempt with empty invoice list. 5. Attempt with invalid invoice ID. </Test> <Review> Sreeja Reghukumar </Review>
431 lines
14 KiB
C++
431 lines
14 KiB
C++
/*
|
||
File: PaymentManagementService.cpp
|
||
Description: Implements the PaymentManagementService class which manages payment-related operations
|
||
in the Vehicle Service Management System. Provides functionality for attaching/detaching observers,
|
||
sending notifications, and issuing payment reminders based on invoice status and thresholds.
|
||
Author: Trenser
|
||
Date: 20-May-2026
|
||
*/
|
||
|
||
#include <stdexcept>
|
||
#include "Config.h"
|
||
#include "Enums.h"
|
||
#include "Factory.h"
|
||
#include "FileManager.h"
|
||
#include "InventoryItem.h"
|
||
#include "Invoice.h"
|
||
#include "JobCard.h"
|
||
#include "PaymentManagementService.h"
|
||
#include "Service.h"
|
||
#include "ServiceBooking.h"
|
||
#include "Timestamp.h"
|
||
#include "User.h"
|
||
#include "Utility.h"
|
||
|
||
util::Map<std::string, User*> PaymentManagementService::m_observers{};
|
||
|
||
/*
|
||
Function: attach
|
||
Description: Attaches a user as an observer to the PaymentManagementService for receiving notifications.
|
||
Parameters:
|
||
- user: Pointer to the User object to be attached.
|
||
Returns:
|
||
- void
|
||
*/
|
||
void PaymentManagementService::attach(User* user)
|
||
{
|
||
if (user)
|
||
{
|
||
const std::string& userID = user->getId();
|
||
if (m_observers.find(userID) == -1)
|
||
{
|
||
m_observers[userID] = user;
|
||
}
|
||
}
|
||
}
|
||
|
||
/*
|
||
Function: detach
|
||
Description: Detaches a user from the observer list of the PaymentManagementService.
|
||
Parameters:
|
||
- user: Pointer to the User object to be detached.
|
||
Returns:
|
||
- void
|
||
*/
|
||
void PaymentManagementService::detach(User* user)
|
||
{
|
||
if (user)
|
||
{
|
||
const std::string& userID = user->getId();
|
||
if (m_observers.find(userID) != -1)
|
||
{
|
||
m_observers.remove(userID);
|
||
}
|
||
}
|
||
}
|
||
|
||
/*
|
||
Function: sendNotification
|
||
Description: Sends a notification to a user if they are registered as an observer.
|
||
Parameters:
|
||
- user: Pointer to the User object to receive the notification.
|
||
- title: Title of the notification.
|
||
- message: Message content of the notification.
|
||
Returns:
|
||
- void
|
||
Throws:
|
||
- std::runtime_error if notification creation fails.
|
||
*/
|
||
void PaymentManagementService::sendNotification(User* user, const std::string& title, const std::string& message)
|
||
{
|
||
if (user)
|
||
{
|
||
if (m_observers.find(user->getId()) != -1)
|
||
{
|
||
Notification* notification =
|
||
Factory::getObject<Notification>(
|
||
user->getId(),
|
||
user,
|
||
title,
|
||
message,
|
||
util::Timestamp()
|
||
);
|
||
if (notification)
|
||
{
|
||
user->addNotification(notification);
|
||
}
|
||
else
|
||
{
|
||
throw std::runtime_error("Failed to create notification");
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/*
|
||
Function: sendPaymentReminders
|
||
Description: Iterates through all invoices in the datastore and sends payment reminders to customers
|
||
whose invoices are pending beyond the configured threshold duration.
|
||
Parameters:
|
||
- None
|
||
Returns:
|
||
- void
|
||
*/
|
||
void PaymentManagementService::sendPaymentReminders()
|
||
{
|
||
auto& invoicesMap = m_dataStore.getInvoices();
|
||
int invoicesMapSize = invoicesMap.getSize();
|
||
for (int index = 0; index < invoicesMapSize; index++)
|
||
{
|
||
const Invoice* invoice = invoicesMap.getValueAt(index);
|
||
if (invoice && invoice->getStatus() == util::PaymentStatus::PENDING)
|
||
{
|
||
util::Timestamp invoiceCreationTimestamp = invoice->getInvoiceDate();
|
||
util::Timestamp currentTimestamp;
|
||
if (util::Timestamp::getDurationInHours(invoiceCreationTimestamp, currentTimestamp) >= config::threshold::PAYMENT_REMINDER_THRESHOLD_HOURS)
|
||
{
|
||
const ServiceBooking* serviceBooking = invoice->getBooking();
|
||
if (serviceBooking)
|
||
{
|
||
User* customer = serviceBooking->getCustomer();
|
||
if (customer)
|
||
{
|
||
std::string title = "Payment Reminder";
|
||
std::string message = "Your payment for Invoice ID " + invoice->getId() + " is still pending. Please complete the payment.";
|
||
sendNotification(customer, title, message);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/*
|
||
Function: getObserverIDs
|
||
Description: Retrieves the IDs of all observers currently attached to the
|
||
PaymentManagementService.
|
||
Parameters:
|
||
- None
|
||
Returns:
|
||
- util::Vector<std::string>: Vector of observer user IDs
|
||
*/
|
||
util::Vector<std::string> PaymentManagementService::getObserverIDs()
|
||
{
|
||
util::Vector<std::string> observerIDs;
|
||
int numberOfObservers = m_observers.getSize();
|
||
for (int index = 0; index < numberOfObservers; index++)
|
||
{
|
||
User* observer = m_observers.getValueAt(index);
|
||
if (observer)
|
||
{
|
||
observerIDs.push_back(observer->getId());
|
||
}
|
||
}
|
||
return observerIDs;
|
||
}
|
||
|
||
/*
|
||
Function: loadInvoices
|
||
Description: Loads invoices from persistent storage into the datastore.
|
||
Validates associated service bookings and inventory parts before
|
||
attaching them to each invoice. Throws exceptions if invalid IDs
|
||
are encountered.
|
||
Parameters:
|
||
- None
|
||
Returns:
|
||
- void
|
||
Throws:
|
||
- std::runtime_error if a booking ID or part ID is invalid
|
||
*/
|
||
void PaymentManagementService::loadInvoices()
|
||
{
|
||
util::FileManager<Invoice> invoiceFileManager(config::file::INVOICE_FILE);
|
||
auto& invoices = m_dataStore.getInvoices();
|
||
auto& serviceBookings = m_dataStore.getServiceBookings();
|
||
auto& inventoryItems = m_dataStore.getInventoryItems();
|
||
auto invoicesMap = invoiceFileManager.load();
|
||
for (int invoiceIndex = 0; invoiceIndex < invoicesMap.getSize(); invoiceIndex++)
|
||
{
|
||
Invoice* invoice = invoicesMap.getValueAt(invoiceIndex);
|
||
int bookingIndex = serviceBookings.find(invoice->getBookingId());
|
||
if (bookingIndex == -1)
|
||
{
|
||
throw std::runtime_error("Invalid Booking ID");
|
||
}
|
||
ServiceBooking* booking = serviceBookings.getValueAt(bookingIndex);
|
||
invoice->setBooking(booking);
|
||
util::Map<std::string, InventoryItem*> invoiceParts;
|
||
auto& partIDs = invoice->getPartIDs();
|
||
for (int partIndex = 0; partIndex < partIDs.getSize(); partIndex++)
|
||
{
|
||
const std::string& partID = partIDs[partIndex];
|
||
int inventoryIndex = inventoryItems.find(partID);
|
||
if (inventoryIndex == -1)
|
||
{
|
||
throw std::runtime_error("Invalid Part ID");
|
||
}
|
||
invoiceParts[partID] = inventoryItems.getValueAt(inventoryIndex);
|
||
}
|
||
invoice->setParts(invoiceParts);
|
||
invoices[invoice->getId()] = invoice;
|
||
}
|
||
}
|
||
|
||
/*
|
||
Function: saveInvoices
|
||
Description: Saves invoices from the datastore to persistent storage.
|
||
Uses FileManager to serialize invoices into the configured file.
|
||
Parameters:
|
||
- None
|
||
Returns:
|
||
- void
|
||
*/
|
||
void PaymentManagementService::saveInvoices()
|
||
{
|
||
util::FileManager<Invoice> invoiceFileManager(config::file::INVOICE_FILE);
|
||
auto& invoices = m_dataStore.getInvoices();
|
||
invoiceFileManager.save(invoices);
|
||
}
|
||
|
||
/*
|
||
Function: loadObservers
|
||
Description: Loads observer IDs from persistent storage and attaches corresponding
|
||
users as observers to the PaymentManagementService.
|
||
Parameters:
|
||
- None
|
||
Returns:
|
||
- void
|
||
Throws:
|
||
- std::runtime_error if an observer ID is invalid (not found in datastore)
|
||
*/
|
||
void PaymentManagementService::loadObservers()
|
||
{
|
||
util::loadObservers(config::file::PAYMENTMANAGEMENTOBSERVERS, this, m_dataStore);
|
||
}
|
||
|
||
/*
|
||
Function: saveObservers
|
||
Description: Saves the current observer IDs of the PaymentManagementService
|
||
to persistent storage for future retrieval.
|
||
Parameters:
|
||
- None
|
||
Returns:
|
||
- void
|
||
*/
|
||
void PaymentManagementService::saveObservers()
|
||
{
|
||
util::saveObservers(config::file::PAYMENTMANAGEMENTOBSERVERS, this);
|
||
}
|
||
|
||
/*
|
||
Function: createInventoryItemsMap (static helper)
|
||
Description: Builds a map of inventory items required for a given service and adds them to the booking’s inventory map.
|
||
Parameters:
|
||
- completeInventoryItemMapOfBooking: util::Map<std::string, InventoryItem*>&, map to store inventory items for the booking
|
||
- currentService: const Service*, pointer to the current service
|
||
Returns:
|
||
- void
|
||
*/
|
||
static void createInventoryItemsMap(util::Map<std::string, InventoryItem*>& completeInventoryItemMapOfBooking, const Service* currentService)
|
||
{
|
||
auto& currentRequiredInventoryItems = currentService->getRequiredInventoryItems();
|
||
for (int iterator = 0; iterator < currentRequiredInventoryItems.getSize(); iterator++)
|
||
{
|
||
auto& currentRequiredInventoryItem = currentRequiredInventoryItems.getValueAt(iterator);
|
||
completeInventoryItemMapOfBooking.insert(currentRequiredInventoryItem->getId(), currentRequiredInventoryItem);
|
||
}
|
||
}
|
||
|
||
/*
|
||
Function: generateInvoice
|
||
Description: Generates an invoice for a completed service booking.
|
||
Validates that all job cards are completed, calculates labor and parts cost, applies discount,
|
||
and stores the invoice in the datastore.
|
||
Parameters:
|
||
- booking: ServiceBooking*, pointer to the service booking
|
||
Returns:
|
||
- void
|
||
Throws:
|
||
- std::runtime_error if booking is null or job cards are incomplete
|
||
*/
|
||
void PaymentManagementService::generateInvoice(ServiceBooking* booking)
|
||
{
|
||
if (!booking)
|
||
{
|
||
throw std::runtime_error("Invoice generation failed: booking is null.");
|
||
}
|
||
double totalLaborCost = 0, totalPartsCost = 0, totalServiceCost = 0;
|
||
double discountPercentage = booking->getDiscountPercentage();
|
||
std::string bookingID = booking->getId();
|
||
util::Map<std::string, Service*> servicesInTheBookedService = booking->getServices();
|
||
util::Map<std::string, InventoryItem*> completeInventoryItemMapOfBooking;
|
||
util::Map<std::string, JobCard*> currentJobCards = m_dataStore.getJobCards();
|
||
for (int iterator = 0; iterator < currentJobCards.getSize(); iterator++)
|
||
{
|
||
JobCard* currentJobCard = currentJobCards.getValueAt(iterator);
|
||
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 + "'.");
|
||
}
|
||
}
|
||
for (int iterator = 0; iterator < servicesInTheBookedService.getSize(); iterator++)
|
||
{
|
||
Service* currentService = servicesInTheBookedService.getValueAt(iterator);
|
||
if (currentService)
|
||
{
|
||
createInventoryItemsMap(completeInventoryItemMapOfBooking, currentService);
|
||
totalLaborCost += currentService->getLaborCost();
|
||
totalPartsCost += util::calculatePartsCost(currentService);
|
||
}
|
||
}
|
||
totalServiceCost = totalLaborCost + totalPartsCost;
|
||
totalServiceCost -= (totalServiceCost * (discountPercentage / 100));
|
||
Invoice* invoice = Factory::getObject<Invoice>(bookingID, booking, util::Timestamp(), totalLaborCost, completeInventoryItemMapOfBooking, totalPartsCost, discountPercentage, totalServiceCost, util::Timestamp(), util::PaymentMode::NOTSET, util::PaymentStatus::PENDING);
|
||
util::Map<std::string, Invoice*>& currentInvoices = m_dataStore.getInvoices();
|
||
currentInvoices.insert(invoice->getId(), invoice);
|
||
}
|
||
|
||
/*
|
||
Function: getInvoices
|
||
Description: Retrieves all invoices associated with a specific customer.
|
||
Parameters:
|
||
- customerID: std::string, ID of the customer
|
||
Returns:
|
||
- util::Map<std::string, Invoice*> containing the customer’s invoices
|
||
*/
|
||
util::Map<std::string, Invoice*> PaymentManagementService::getInvoices(const std::string& customerID)
|
||
{
|
||
util::Map<std::string, Invoice*>& currentInvoices = m_dataStore.getInvoices();
|
||
util::Map<std::string, Invoice*> currentUserInvoices;
|
||
for (int iterator = 0; iterator < currentInvoices.getSize(); iterator++)
|
||
{
|
||
Invoice* currentInvoice = currentInvoices.getValueAt(iterator);
|
||
if (currentInvoice->getBooking()->getCustomerId() == customerID)
|
||
{
|
||
currentUserInvoices.insert(currentInvoice->getId(), currentInvoice);
|
||
}
|
||
}
|
||
return currentUserInvoices;
|
||
}
|
||
|
||
/*
|
||
Function: completePayment
|
||
Description: Completes payment for a specific invoice. Updates payment method, date, and status,
|
||
then sends a notification to the customer.
|
||
Parameters:
|
||
- invoiceID: std::string, ID of the invoice
|
||
- paymentMode: util::PaymentMode, mode of payment (e.g., ONLINE, OFFLINE)
|
||
Returns:
|
||
- void
|
||
Throws:
|
||
- std::runtime_error if the invoice ID is invalid
|
||
*/
|
||
void PaymentManagementService::completePayment(const std::string& invoiceID, util::PaymentMode paymentMode)
|
||
{
|
||
auto& currentInvoices = m_dataStore.getInvoices();
|
||
int invoiceIndex = currentInvoices.find(invoiceID);
|
||
if (invoiceIndex != -1)
|
||
{
|
||
Invoice* invoice = currentInvoices.getValueAt(invoiceIndex);
|
||
if (invoice && invoice->getStatus() != util::PaymentStatus::PAID)
|
||
{
|
||
User* currentUser = invoice->getBooking()->getCustomer();
|
||
invoice->setPaymentMethod(paymentMode);
|
||
invoice->setPaymentDate(util::Timestamp());
|
||
invoice->setStatus(util::PaymentStatus::PAID);
|
||
std::string title, message;
|
||
title = "Payment successful";
|
||
message = "Payment successful for Invoice ID " + invoiceID;
|
||
sendNotification(currentUser, title, message);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
throw std::runtime_error("Payment failed: invalid invoice ID.");
|
||
}
|
||
}
|
||
|
||
/*
|
||
Function: getAllInvoice
|
||
Description: Provides access to all invoices stored in the data store.
|
||
Parameters:
|
||
- none
|
||
Returns:
|
||
- util::Map<std::string, Invoice*>&: Map of invoice IDs to invoice objects
|
||
*/
|
||
util::Map<std::string, Invoice*>& PaymentManagementService::getAllInvoices()
|
||
{
|
||
return m_dataStore.getInvoices();
|
||
}
|
||
|
||
/*
|
||
Function: confirmPayment
|
||
Description: Confirms payment for a specific invoice. Updates payment date and status,
|
||
then sends a notification to the customer.
|
||
Parameters:
|
||
- invoiceID: std::string, ID of the invoice to confirm
|
||
Returns:
|
||
- void
|
||
Throws:
|
||
- std::runtime_error if the invoice ID is invalid
|
||
*/
|
||
void PaymentManagementService::confirmPayment(const std::string& invoiceID)
|
||
{
|
||
auto& currentInvoices = m_dataStore.getInvoices();
|
||
int invoiceIndex = currentInvoices.find(invoiceID);
|
||
if (invoiceIndex == -1)
|
||
{
|
||
throw std::runtime_error("Payment confirmation failed: invalid invoice ID.");
|
||
}
|
||
Invoice* invoice = currentInvoices.getValueAt(invoiceIndex);
|
||
if (!invoice || invoice->getStatus() != util::PaymentStatus::PAID)
|
||
{
|
||
throw std::runtime_error("Payment confirmation failed: invoice is not awaiting confirmation.");
|
||
}
|
||
User* currentUser = invoice->getBooking()->getCustomer();
|
||
invoice->setStatus(util::PaymentStatus::COMPLETED);
|
||
std::string title = "Payment Confirmed";
|
||
std::string message = "Payment Confirmed for Invoice ID " + invoiceID;
|
||
sendNotification(currentUser, title, message);
|
||
} |