Contents

Supermarket Management System: C++ Application with CMake and PostgreSQL

Supermarket Management System: C++ Application with CMake and PostgreSQL

In this post, I’ll share insights from my Supermarket Management System project, which demonstrates advanced C++ application development, database design, inventory management, and point-of-sale system implementation using PostgreSQL and modern C++ practices.

Project Overview

The Supermarket Management System is a comprehensive retail management application that handles inventory tracking, customer transactions, employee management, and business reporting. Built with C++ and PostgreSQL, it provides a robust foundation for retail operations with real-time data processing and comprehensive reporting capabilities.

Technical Architecture

Project Structure

SupermarketManagement/
├── src/
│   ├── core/
│   │   ├── inventory/
│   │   │   ├── product_manager.cpp
│   │   │   ├── product_manager.h
│   │   │   ├── category_manager.cpp
│   │   │   ├── category_manager.h
│   │   │   ├── supplier_manager.cpp
│   │   │   └── supplier_manager.h
│   │   ├── sales/
│   │   │   ├── transaction_manager.cpp
│   │   │   ├── transaction_manager.h
│   │   │   ├── cart_manager.cpp
│   │   │   ├── cart_manager.h
│   │   │   ├── payment_processor.cpp
│   │   │   └── payment_processor.h
│   │   ├── customer/
│   │   │   ├── customer_manager.cpp
│   │   │   ├── customer_manager.h
│   │   │   ├── loyalty_program.cpp
│   │   │   └── loyalty_program.h
│   │   ├── employee/
│   │   │   ├── employee_manager.cpp
│   │   │   ├── employee_manager.h
│   │   │   ├── shift_manager.cpp
│   │   │   └── shift_manager.h
│   │   └── reporting/
│   │       ├── report_generator.cpp
│   │       ├── report_generator.h
│   │       ├── analytics.cpp
│   │       └── analytics.h
│   ├── database/
│   │   ├── database_manager.cpp
│   │   ├── database_manager.h
│   │   ├── connection_pool.cpp
│   │   ├── connection_pool.h
│   │   ├── migrations/
│   │   └── queries/
│   ├── ui/
│   │   ├── console/
│   │   │   ├── main_menu.cpp
│   │   │   ├── inventory_menu.cpp
│   │   │   ├── sales_menu.cpp
│   │   │   ├── customer_menu.cpp
│   │   │   ├── employee_menu.cpp
│   │   │   └── reports_menu.cpp
│   │   └── gui/
│   │       ├── main_window.cpp
│   │       ├── inventory_window.cpp
│   │       ├── pos_window.cpp
│   │       └── reports_window.cpp
│   ├── utils/
│   │   ├── logger.cpp
│   │   ├── logger.h
│   │   ├── config_manager.cpp
│   │   ├── config_manager.h
│   │   ├── date_utils.cpp
│   │   ├── date_utils.h
│   │   ├── currency_utils.cpp
│   │   └── currency_utils.h
│   └── models/
│       ├── product.cpp
│       ├── product.h
│       ├── transaction.cpp
│       ├── transaction.h
│       ├── customer.cpp
│       ├── customer.h
│       ├── employee.cpp
│       ├── employee.h
│       └── common_types.h
├── include/
│   └── supermarket/
│       ├── core/
│       ├── database/
│       ├── ui/
│       └── utils/
├── tests/
│   ├── unit/
│   ├── integration/
│   └── fixtures/
├── scripts/
│   ├── database/
│   │   ├── schema.sql
│   │   ├── sample_data.sql
│   │   └── migrations/
│   └── deployment/
├── docs/
│   ├── api/
│   ├── user_guide/
│   ├── admin_guide/
│   └── developer_guide/
├── CMakeLists.txt
├── README.md
└── LICENSE

Core Implementation

Product Management System

// src/core/inventory/product_manager.h
#ifndef PRODUCT_MANAGER_H
#define PRODUCT_MANAGER_H

#include <string>
#include <vector>
#include <memory>
#include <map>
#include <chrono>
#include "product.h"
#include "database_manager.h"

namespace SupermarketApp {

class ProductManager {
public:
    struct ProductSearchCriteria {
        std::string name_pattern;
        std::string category;
        std::string supplier;
        double min_price = 0.0;
        double max_price = std::numeric_limits<double>::max();
        int min_stock = 0;
        bool in_stock_only = false;
        bool active_only = true;
    };
    
    struct InventoryAlert {
        enum Type {
            LOW_STOCK,
            OUT_OF_STOCK,
            EXPIRING_SOON,
            EXPIRED
        };
        
        Type type;
        std::string product_id;
        std::string message;
        std::chrono::system_clock::time_point created_at;
    };
    
    explicit ProductManager(std::shared_ptr<DatabaseManager> db_manager);
    ~ProductManager() = default;
    
    // Product CRUD operations
    bool add_product(const Product& product);
    bool update_product(const Product& product);
    bool delete_product(const std::string& product_id);
    std::optional<Product> get_product(const std::string& product_id);
    std::vector<Product> get_products(const ProductSearchCriteria& criteria = {});
    
    // Inventory management
    bool update_stock(const std::string& product_id, int quantity_change, 
                     const std::string& reason = "Manual adjustment");
    bool set_stock(const std::string& product_id, int new_quantity);
    int get_current_stock(const std::string& product_id);
    
    // Price management
    bool update_price(const std::string& product_id, double new_price);
    bool apply_discount(const std::string& product_id, double discount_percentage);
    bool remove_discount(const std::string& product_id);
    
    // Product search and filtering
    std::vector<Product> search_products(const std::string& search_term);
    std::vector<Product> get_products_by_category(const std::string& category);
    std::vector<Product> get_products_by_supplier(const std::string& supplier);
    std::vector<Product> get_low_stock_products(int threshold = 10);
    std::vector<Product> get_expiring_products(int days_ahead = 7);
    
    // Barcode operations
    bool add_barcode(const std::string& product_id, const std::string& barcode);
    bool remove_barcode(const std::string& barcode);
    std::optional<Product> get_product_by_barcode(const std::string& barcode);
    std::vector<std::string> get_product_barcodes(const std::string& product_id);
    
    // Category management
    bool add_category(const std::string& name, const std::string& description = "");
    bool update_category(const std::string& name, const std::string& new_name, 
                        const std::string& description = "");
    bool delete_category(const std::string& name);
    std::vector<std::string> get_categories();
    
    // Supplier management
    bool add_supplier(const std::string& name, const std::string& contact_info);
    bool update_supplier(const std::string& name, const std::string& new_name, 
                        const std::string& contact_info);
    bool delete_supplier(const std::string& name);
    std::vector<std::string> get_suppliers();
    
    // Inventory alerts
    std::vector<InventoryAlert> get_active_alerts();
    bool acknowledge_alert(const std::string& alert_id);
    void generate_alerts();
    
    // Statistics and reporting
    struct InventoryStats {
        size_t total_products;
        size_t active_products;
        size_t low_stock_products;
        size_t out_of_stock_products;
        double total_inventory_value;
        double average_product_price;
        std::string most_expensive_product;
        std::string cheapest_product;
    };
    
    InventoryStats get_inventory_statistics();
    
    // Bulk operations
    bool import_products(const std::string& csv_file);
    bool export_products(const std::string& csv_file, const ProductSearchCriteria& criteria = {});
    bool bulk_update_prices(const std::map<std::string, double>& price_updates);
    bool bulk_update_stock(const std::map<std::string, int>& stock_updates);
    
private:
    std::shared_ptr<DatabaseManager> db_manager_;
    
    // Helper methods
    Product product_from_row(const pqxx::row& row);
    std::string generate_product_id();
    bool validate_product(const Product& product);
    void log_inventory_change(const std::string& product_id, int old_quantity, 
                             int new_quantity, const std::string& reason);
    std::vector<InventoryAlert> check_stock_levels();
    std::vector<InventoryAlert> check_expiration_dates();
};

} // namespace SupermarketApp

#endif // PRODUCT_MANAGER_H
// src/core/inventory/product_manager.cpp
#include "product_manager.h"
#include "logger.h"
#include <algorithm>
#include <random>
#include <sstream>
#include <iomanip>

namespace SupermarketApp {

ProductManager::ProductManager(std::shared_ptr<DatabaseManager> db_manager)
    : db_manager_(db_manager) {
    Logger::get_instance().info("ProductManager initialized");
}

bool ProductManager::add_product(const Product& product) {
    if (!validate_product(product)) {
        Logger::get_instance().error("Invalid product data");
        return false;
    }
    
    if (!db_manager_->is_connected()) {
        Logger::get_instance().error("Database not connected");
        return false;
    }
    
    try {
        pqxx::work transaction(*db_manager_->get_connection());
        
        std::string product_id = generate_product_id();
        
        std::string query = R"(
            INSERT INTO products (id, name, description, category, supplier, 
                                price, cost, stock_quantity, min_stock_level, 
                                max_stock_level, barcode, sku, unit_of_measure,
                                expiration_date, is_active, created_at)
            VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, NOW())
        )";
        
        transaction.exec_params(query,
                               product_id,
                               product.name,
                               product.description,
                               product.category,
                               product.supplier,
                               product.price,
                               product.cost,
                               product.stock_quantity,
                               product.min_stock_level,
                               product.max_stock_level,
                               product.barcode,
                               product.sku,
                               product.unit_of_measure,
                               product.expiration_date,
                               product.is_active);
        
        transaction.commit();
        
        Logger::get_instance().info("Product added: " + product_id);
        return true;
        
    } catch (const std::exception& e) {
        Logger::get_instance().error("Failed to add product: " + std::string(e.what()));
        return false;
    }
}

bool ProductManager::update_stock(const std::string& product_id, int quantity_change, 
                                 const std::string& reason) {
    if (!db_manager_->is_connected()) {
        Logger::get_instance().error("Database not connected");
        return false;
    }
    
    try {
        pqxx::work transaction(*db_manager_->get_connection());
        
        // Get current stock
        std::string get_stock_query = "SELECT stock_quantity FROM products WHERE id = $1";
        pqxx::result result = transaction.exec_params(get_stock_query, product_id);
        
        if (result.empty()) {
            Logger::get_instance().error("Product not found: " + product_id);
            return false;
        }
        
        int old_quantity = result[0][0].as<int>();
        int new_quantity = old_quantity + quantity_change;
        
        if (new_quantity < 0) {
            Logger::get_instance().warning("Insufficient stock for product: " + product_id);
            return false;
        }
        
        // Update stock
        std::string update_query = "UPDATE products SET stock_quantity = $1 WHERE id = $2";
        transaction.exec_params(update_query, new_quantity, product_id);
        
        // Log inventory change
        std::string log_query = R"(
            INSERT INTO inventory_log (product_id, old_quantity, new_quantity, 
                                      change_reason, changed_by, changed_at)
            VALUES ($1, $2, $3, $4, $5, NOW())
        )";
        
        transaction.exec_params(log_query, product_id, old_quantity, new_quantity, 
                               reason, "system");
        
        transaction.commit();
        
        log_inventory_change(product_id, old_quantity, new_quantity, reason);
        
        Logger::get_instance().info("Stock updated for product " + product_id + 
                                  ": " + std::to_string(old_quantity) + " -> " + 
                                  std::to_string(new_quantity));
        
        return true;
        
    } catch (const std::exception& e) {
        Logger::get_instance().error("Failed to update stock: " + std::string(e.what()));
        return false;
    }
}

std::vector<Product> ProductManager::search_products(const std::string& search_term) {
    std::vector<Product> products;
    
    if (!db_manager_->is_connected()) {
        Logger::get_instance().error("Database not connected");
        return products;
    }
    
    try {
        pqxx::work transaction(*db_manager_->get_connection());
        
        std::string query = R"(
            SELECT * FROM products 
            WHERE (name ILIKE $1 OR description ILIKE $1 OR barcode ILIKE $1 OR sku ILIKE $1)
            AND is_active = true
            ORDER BY name
        )";
        
        std::string search_pattern = "%" + search_term + "%";
        pqxx::result result = transaction.exec_params(query, search_pattern);
        
        for (const auto& row : result) {
            products.push_back(product_from_row(row));
        }
        
        Logger::get_instance().info("Found " + std::to_string(products.size()) + 
                                   " products matching: " + search_term);
        
    } catch (const std::exception& e) {
        Logger::get_instance().error("Failed to search products: " + std::string(e.what()));
    }
    
    return products;
}

std::vector<Product> ProductManager::get_low_stock_products(int threshold) {
    std::vector<Product> products;
    
    if (!db_manager_->is_connected()) {
        Logger::get_instance().error("Database not connected");
        return products;
    }
    
    try {
        pqxx::work transaction(*db_manager_->get_connection());
        
        std::string query = R"(
            SELECT * FROM products 
            WHERE stock_quantity <= $1 AND is_active = true
            ORDER BY stock_quantity ASC
        )";
        
        pqxx::result result = transaction.exec_params(query, threshold);
        
        for (const auto& row : result) {
            products.push_back(product_from_row(row));
        }
        
        Logger::get_instance().info("Found " + std::to_string(products.size()) + 
                                   " low stock products");
        
    } catch (const std::exception& e) {
        Logger::get_instance().error("Failed to get low stock products: " + std::string(e.what()));
    }
    
    return products;
}

ProductManager::InventoryStats ProductManager::get_inventory_statistics() {
    InventoryStats stats{};
    
    if (!db_manager_->is_connected()) {
        Logger::get_instance().error("Database not connected");
        return stats;
    }
    
    try {
        pqxx::work transaction(*db_manager_->get_connection());
        
        // Total products
        pqxx::result result = transaction.exec("SELECT COUNT(*) FROM products");
        stats.total_products = result[0][0].as<size_t>();
        
        // Active products
        result = transaction.exec("SELECT COUNT(*) FROM products WHERE is_active = true");
        stats.active_products = result[0][0].as<size_t>();
        
        // Low stock products
        result = transaction.exec(R"(
            SELECT COUNT(*) FROM products 
            WHERE stock_quantity <= min_stock_level AND is_active = true
        )");
        stats.low_stock_products = result[0][0].as<size_t>();
        
        // Out of stock products
        result = transaction.exec(R"(
            SELECT COUNT(*) FROM products 
            WHERE stock_quantity = 0 AND is_active = true
        )");
        stats.out_of_stock_products = result[0][0].as<size_t>();
        
        // Total inventory value
        result = transaction.exec(R"(
            SELECT SUM(stock_quantity * cost) FROM products WHERE is_active = true
        )");
        if (!result[0][0].is_null()) {
            stats.total_inventory_value = result[0][0].as<double>();
        }
        
        // Average product price
        result = transaction.exec(R"(
            SELECT AVG(price) FROM products WHERE is_active = true
        )");
        if (!result[0][0].is_null()) {
            stats.average_product_price = result[0][0].as<double>();
        }
        
        // Most expensive product
        result = transaction.exec(R"(
            SELECT name FROM products 
            WHERE is_active = true 
            ORDER BY price DESC LIMIT 1
        )");
        if (!result.empty()) {
            stats.most_expensive_product = result[0][0].as<std::string>();
        }
        
        // Cheapest product
        result = transaction.exec(R"(
            SELECT name FROM products 
            WHERE is_active = true 
            ORDER BY price ASC LIMIT 1
        )");
        if (!result.empty()) {
            stats.cheapest_product = result[0][0].as<std::string>();
        }
        
    } catch (const std::exception& e) {
        Logger::get_instance().error("Failed to get inventory statistics: " + std::string(e.what()));
    }
    
    return stats;
}

Product ProductManager::product_from_row(const pqxx::row& row) {
    Product product;
    
    product.id = row[0].as<std::string>();
    product.name = row[1].as<std::string>();
    product.description = row[2].as<std::string>();
    product.category = row[3].as<std::string>();
    product.supplier = row[4].as<std::string>();
    product.price = row[5].as<double>();
    product.cost = row[6].as<double>();
    product.stock_quantity = row[7].as<int>();
    product.min_stock_level = row[8].as<int>();
    product.max_stock_level = row[9].as<int>();
    product.barcode = row[10].as<std::string>();
    product.sku = row[11].as<std::string>();
    product.unit_of_measure = row[12].as<std::string>();
    
    if (!row[13].is_null()) {
        product.expiration_date = row[13].as<std::string>();
    }
    
    product.is_active = row[14].as<bool>();
    
    return product;
}

std::string ProductManager::generate_product_id() {
    std::random_device rd;
    std::mt19937 gen(rd());
    std::uniform_int_distribution<> dis(100000, 999999);
    
    std::stringstream ss;
    ss << "PRD" << dis(gen);
    
    return ss.str();
}

bool ProductManager::validate_product(const Product& product) {
    if (product.name.empty()) {
        Logger::get_instance().error("Product name cannot be empty");
        return false;
    }
    
    if (product.price <= 0) {
        Logger::get_instance().error("Product price must be positive");
        return false;
    }
    
    if (product.cost < 0) {
        Logger::get_instance().error("Product cost cannot be negative");
        return false;
    }
    
    if (product.stock_quantity < 0) {
        Logger::get_instance().error("Stock quantity cannot be negative");
        return false;
    }
    
    return true;
}

void ProductManager::log_inventory_change(const std::string& product_id, int old_quantity, 
                                         int new_quantity, const std::string& reason) {
    Logger::get_instance().info("Inventory change - Product: " + product_id + 
                               ", Old: " + std::to_string(old_quantity) + 
                               ", New: " + std::to_string(new_quantity) + 
                               ", Reason: " + reason);
}

} // namespace SupermarketApp

Transaction Management System

// src/core/sales/transaction_manager.h
#ifndef TRANSACTION_MANAGER_H
#define TRANSACTION_MANAGER_H

#include <string>
#include <vector>
#include <memory>
#include <map>
#include <chrono>
#include "transaction.h"
#include "database_manager.h"

namespace SupermarketApp {

class TransactionManager {
public:
    struct TransactionItem {
        std::string product_id;
        std::string product_name;
        int quantity;
        double unit_price;
        double total_price;
        double discount_amount;
        std::string discount_reason;
    };
    
    struct PaymentInfo {
        enum Type {
            CASH,
            CREDIT_CARD,
            DEBIT_CARD,
            MOBILE_PAYMENT,
            CHECK
        };
        
        Type type;
        double amount;
        std::string reference;
        std::string card_last_four;
        std::string authorization_code;
    };
    
    struct TransactionSummary {
        std::string transaction_id;
        std::chrono::system_clock::time_point timestamp;
        std::string cashier_id;
        std::string customer_id;
        std::vector<TransactionItem> items;
        double subtotal;
        double tax_amount;
        double discount_amount;
        double total_amount;
        std::vector<PaymentInfo> payments;
        bool is_completed;
        std::string notes;
    };
    
    explicit TransactionManager(std::shared_ptr<DatabaseManager> db_manager);
    ~TransactionManager() = default;
    
    // Transaction lifecycle
    std::string create_transaction(const std::string& cashier_id, 
                                  const std::string& customer_id = "");
    bool add_item_to_transaction(const std::string& transaction_id, 
                                const std::string& product_id, int quantity);
    bool remove_item_from_transaction(const std::string& transaction_id, 
                                     const std::string& product_id);
    bool update_item_quantity(const std::string& transaction_id, 
                              const std::string& product_id, int new_quantity);
    
    // Pricing and discounts
    bool apply_discount_to_item(const std::string& transaction_id, 
                               const std::string& product_id, 
                               double discount_amount, const std::string& reason);
    bool apply_discount_to_transaction(const std::string& transaction_id, 
                                     double discount_amount, const std::string& reason);
    bool remove_discount(const std::string& transaction_id, const std::string& product_id = "");
    
    // Payment processing
    bool add_payment(const std::string& transaction_id, const PaymentInfo& payment);
    bool process_payment(const std::string& transaction_id, 
                        const std::vector<PaymentInfo>& payments);
    bool complete_transaction(const std::string& transaction_id);
    bool cancel_transaction(const std::string& transaction_id);
    
    // Transaction queries
    std::optional<TransactionSummary> get_transaction(const std::string& transaction_id);
    std::vector<TransactionSummary> get_transactions_by_date(
        const std::chrono::system_clock::time_point& start_date,
        const std::chrono::system_clock::time_point& end_date);
    std::vector<TransactionSummary> get_transactions_by_cashier(
        const std::string& cashier_id,
        const std::chrono::system_clock::time_point& start_date,
        const std::chrono::system_clock::time_point& end_date);
    std::vector<TransactionSummary> get_transactions_by_customer(
        const std::string& customer_id);
    
    // Refunds and returns
    std::string create_refund_transaction(const std::string& original_transaction_id,
                                         const std::vector<TransactionItem>& refund_items,
                                         const std::string& reason);
    bool process_refund(const std::string& refund_transaction_id,
                       const std::vector<PaymentInfo>& refund_payments);
    
    // Statistics and reporting
    struct SalesStats {
        double total_sales;
        double total_tax;
        double total_discounts;
        size_t total_transactions;
        size_t total_items_sold;
        double average_transaction_value;
        std::string best_selling_product;
        std::string top_cashier;
    };
    
    SalesStats get_sales_statistics(const std::chrono::system_clock::time_point& start_date,
                                   const std::chrono::system_clock::time_point& end_date);
    
    // Inventory integration
    bool reserve_inventory(const std::string& transaction_id);
    bool release_inventory(const std::string& transaction_id);
    bool commit_inventory_changes(const std::string& transaction_id);
    
private:
    std::shared_ptr<DatabaseManager> db_manager_;
    
    // Helper methods
    std::string generate_transaction_id();
    double calculate_tax(const std::vector<TransactionItem>& items);
    double calculate_total(const std::vector<TransactionItem>& items, double tax_amount, 
                          double discount_amount);
    bool validate_transaction(const TransactionSummary& transaction);
    void log_transaction_event(const std::string& transaction_id, 
                              const std::string& event, const std::string& details);
};

} // namespace SupermarketApp

#endif // TRANSACTION_MANAGER_H

Database Schema

-- scripts/database/schema.sql

-- Products table
CREATE TABLE IF NOT EXISTS products (
    id VARCHAR(50) PRIMARY KEY,
    name VARCHAR(255) NOT NULL,
    description TEXT,
    category VARCHAR(100) NOT NULL,
    supplier VARCHAR(100) NOT NULL,
    price DECIMAL(10,2) NOT NULL CHECK (price > 0),
    cost DECIMAL(10,2) NOT NULL CHECK (cost >= 0),
    stock_quantity INTEGER NOT NULL DEFAULT 0 CHECK (stock_quantity >= 0),
    min_stock_level INTEGER NOT NULL DEFAULT 0 CHECK (min_stock_level >= 0),
    max_stock_level INTEGER NOT NULL DEFAULT 0 CHECK (max_stock_level >= 0),
    barcode VARCHAR(50) UNIQUE,
    sku VARCHAR(50) UNIQUE,
    unit_of_measure VARCHAR(20) DEFAULT 'piece',
    expiration_date DATE,
    is_active BOOLEAN DEFAULT true,
    created_at TIMESTAMP DEFAULT NOW(),
    updated_at TIMESTAMP DEFAULT NOW()
);

-- Categories table
CREATE TABLE IF NOT EXISTS categories (
    name VARCHAR(100) PRIMARY KEY,
    description TEXT,
    parent_category VARCHAR(100),
    created_at TIMESTAMP DEFAULT NOW(),
    FOREIGN KEY (parent_category) REFERENCES categories(name)
);

-- Suppliers table
CREATE TABLE IF NOT EXISTS suppliers (
    name VARCHAR(100) PRIMARY KEY,
    contact_person VARCHAR(100),
    email VARCHAR(255),
    phone VARCHAR(20),
    address TEXT,
    created_at TIMESTAMP DEFAULT NOW()
);

-- Customers table
CREATE TABLE IF NOT EXISTS customers (
    id VARCHAR(50) PRIMARY KEY,
    first_name VARCHAR(100) NOT NULL,
    last_name VARCHAR(100) NOT NULL,
    email VARCHAR(255),
    phone VARCHAR(20),
    address TEXT,
    date_of_birth DATE,
    loyalty_points INTEGER DEFAULT 0,
    is_active BOOLEAN DEFAULT true,
    created_at TIMESTAMP DEFAULT NOW(),
    updated_at TIMESTAMP DEFAULT NOW()
);

-- Employees table
CREATE TABLE IF NOT EXISTS employees (
    id VARCHAR(50) PRIMARY KEY,
    first_name VARCHAR(100) NOT NULL,
    last_name VARCHAR(100) NOT NULL,
    email VARCHAR(255) UNIQUE NOT NULL,
    phone VARCHAR(20),
    address TEXT,
    position VARCHAR(100) NOT NULL,
    hire_date DATE NOT NULL,
    salary DECIMAL(10,2),
    is_active BOOLEAN DEFAULT true,
    created_at TIMESTAMP DEFAULT NOW(),
    updated_at TIMESTAMP DEFAULT NOW()
);

-- Transactions table
CREATE TABLE IF NOT EXISTS transactions (
    id VARCHAR(50) PRIMARY KEY,
    cashier_id VARCHAR(50) NOT NULL,
    customer_id VARCHAR(50),
    subtotal DECIMAL(10,2) NOT NULL CHECK (subtotal >= 0),
    tax_amount DECIMAL(10,2) NOT NULL DEFAULT 0 CHECK (tax_amount >= 0),
    discount_amount DECIMAL(10,2) NOT NULL DEFAULT 0 CHECK (discount_amount >= 0),
    total_amount DECIMAL(10,2) NOT NULL CHECK (total_amount >= 0),
    status VARCHAR(20) DEFAULT 'pending' CHECK (status IN ('pending', 'completed', 'cancelled', 'refunded')),
    notes TEXT,
    created_at TIMESTAMP DEFAULT NOW(),
    completed_at TIMESTAMP,
    FOREIGN KEY (cashier_id) REFERENCES employees(id),
    FOREIGN KEY (customer_id) REFERENCES customers(id)
);

-- Transaction items table
CREATE TABLE IF NOT EXISTS transaction_items (
    id SERIAL PRIMARY KEY,
    transaction_id VARCHAR(50) NOT NULL,
    product_id VARCHAR(50) NOT NULL,
    quantity INTEGER NOT NULL CHECK (quantity > 0),
    unit_price DECIMAL(10,2) NOT NULL CHECK (unit_price >= 0),
    total_price DECIMAL(10,2) NOT NULL CHECK (total_price >= 0),
    discount_amount DECIMAL(10,2) DEFAULT 0 CHECK (discount_amount >= 0),
    discount_reason VARCHAR(255),
    FOREIGN KEY (transaction_id) REFERENCES transactions(id) ON DELETE CASCADE,
    FOREIGN KEY (product_id) REFERENCES products(id)
);

-- Payments table
CREATE TABLE IF NOT EXISTS payments (
    id SERIAL PRIMARY KEY,
    transaction_id VARCHAR(50) NOT NULL,
    payment_type VARCHAR(20) NOT NULL CHECK (payment_type IN ('cash', 'credit_card', 'debit_card', 'mobile_payment', 'check')),
    amount DECIMAL(10,2) NOT NULL CHECK (amount > 0),
    reference VARCHAR(100),
    card_last_four VARCHAR(4),
    authorization_code VARCHAR(50),
    processed_at TIMESTAMP DEFAULT NOW(),
    FOREIGN KEY (transaction_id) REFERENCES transactions(id) ON DELETE CASCADE
);

-- Inventory log table
CREATE TABLE IF NOT EXISTS inventory_log (
    id SERIAL PRIMARY KEY,
    product_id VARCHAR(50) NOT NULL,
    old_quantity INTEGER NOT NULL,
    new_quantity INTEGER NOT NULL,
    change_reason VARCHAR(255) NOT NULL,
    changed_by VARCHAR(50) NOT NULL,
    changed_at TIMESTAMP DEFAULT NOW(),
    FOREIGN KEY (product_id) REFERENCES products(id)
);

-- Shifts table
CREATE TABLE IF NOT EXISTS shifts (
    id SERIAL PRIMARY KEY,
    employee_id VARCHAR(50) NOT NULL,
    shift_date DATE NOT NULL,
    start_time TIME NOT NULL,
    end_time TIME,
    total_hours DECIMAL(4,2),
    created_at TIMESTAMP DEFAULT NOW(),
    FOREIGN KEY (employee_id) REFERENCES employees(id)
);

-- Create indexes for better performance
CREATE INDEX IF NOT EXISTS idx_products_category ON products(category);
CREATE INDEX IF NOT EXISTS idx_products_supplier ON products(supplier);
CREATE INDEX IF NOT EXISTS idx_products_barcode ON products(barcode);
CREATE INDEX IF NOT EXISTS idx_products_sku ON products(sku);
CREATE INDEX IF NOT EXISTS idx_products_active ON products(is_active);

CREATE INDEX IF NOT EXISTS idx_transactions_cashier ON transactions(cashier_id);
CREATE INDEX IF NOT EXISTS idx_transactions_customer ON transactions(customer_id);
CREATE INDEX IF NOT EXISTS idx_transactions_date ON transactions(created_at);
CREATE INDEX IF NOT EXISTS idx_transactions_status ON transactions(status);

CREATE INDEX IF NOT EXISTS idx_transaction_items_transaction ON transaction_items(transaction_id);
CREATE INDEX IF NOT EXISTS idx_transaction_items_product ON transaction_items(product_id);

CREATE INDEX IF NOT EXISTS idx_payments_transaction ON payments(transaction_id);

CREATE INDEX IF NOT EXISTS idx_inventory_log_product ON inventory_log(product_id);
CREATE INDEX IF NOT EXISTS idx_inventory_log_date ON inventory_log(changed_at);

CREATE INDEX IF NOT EXISTS idx_shifts_employee ON shifts(employee_id);
CREATE INDEX IF NOT EXISTS idx_shifts_date ON shifts(shift_date);

-- Create triggers for updated_at timestamps
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
    NEW.updated_at = NOW();
    RETURN NEW;
END;
$$ language 'plpgsql';

CREATE TRIGGER update_products_updated_at BEFORE UPDATE ON products
    FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();

CREATE TRIGGER update_customers_updated_at BEFORE UPDATE ON customers
    FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();

CREATE TRIGGER update_employees_updated_at BEFORE UPDATE ON employees
    FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();

CMake Build System

# CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(SupermarketManagement VERSION 1.0.0 LANGUAGES CXX)

# Set C++ standard
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# Compiler flags
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wpedantic")
    set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g -O0")
    set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3 -DNDEBUG")
endif()

# Find required packages
find_package(PkgConfig REQUIRED)
find_package(Threads REQUIRED)

# Find PostgreSQL
find_package(PostgreSQL REQUIRED)
if(NOT PostgreSQL_FOUND)
    message(FATAL_ERROR "PostgreSQL not found. Please install libpq-dev or postgresql-devel")
endif()

# Find libpqxx
pkg_check_modules(LIBPQXX REQUIRED libpqxx)
if(NOT LIBPQXX_FOUND)
    message(FATAL_ERROR "libpqxx not found. Please install libpqxx-dev")
endif()

# Include directories
include_directories(${CMAKE_SOURCE_DIR}/include)
include_directories(${LIBPQXX_INCLUDE_DIRS})

# Source files
set(CORE_SOURCES
    src/core/inventory/product_manager.cpp
    src/core/inventory/category_manager.cpp
    src/core/inventory/supplier_manager.cpp
    src/core/sales/transaction_manager.cpp
    src/core/sales/cart_manager.cpp
    src/core/sales/payment_processor.cpp
    src/core/customer/customer_manager.cpp
    src/core/customer/loyalty_program.cpp
    src/core/employee/employee_manager.cpp
    src/core/employee/shift_manager.cpp
    src/core/reporting/report_generator.cpp
    src/core/reporting/analytics.cpp
)

set(DATABASE_SOURCES
    src/database/database_manager.cpp
    src/database/connection_pool.cpp
)

set(UI_SOURCES
    src/ui/console/main_menu.cpp
    src/ui/console/inventory_menu.cpp
    src/ui/console/sales_menu.cpp
    src/ui/console/customer_menu.cpp
    src/ui/console/employee_menu.cpp
    src/ui/console/reports_menu.cpp
)

set(UTILS_SOURCES
    src/utils/logger.cpp
    src/utils/config_manager.cpp
    src/utils/date_utils.cpp
    src/utils/currency_utils.cpp
)

set(MODEL_SOURCES
    src/models/product.cpp
    src/models/transaction.cpp
    src/models/customer.cpp
    src/models/employee.cpp
)

set(ALL_SOURCES
    ${CORE_SOURCES}
    ${DATABASE_SOURCES}
    ${UI_SOURCES}
    ${UTILS_SOURCES}
    ${MODEL_SOURCES}
)

# Create library
add_library(SupermarketLib STATIC ${ALL_SOURCES})

# Link libraries
target_link_libraries(SupermarketLib 
    ${LIBPQXX_LIBRARIES}
    Threads::Threads
)

# Link PostgreSQL
target_link_libraries(SupermarketLib ${PostgreSQL_LIBRARIES})

# Include directories for the library
target_include_directories(SupermarketLib PUBLIC
    $<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/include>
    $<INSTALL_INTERFACE:include>
)

# Create console executable
add_executable(supermarket_console src/main.cpp)
target_link_libraries(supermarket_console SupermarketLib)

# Optional GUI executable
option(BUILD_GUI "Build GUI version" OFF)
if(BUILD_GUI)
    find_package(Qt5 REQUIRED COMPONENTS Core Widgets)
    add_executable(supermarket_gui src/gui_main.cpp)
    target_link_libraries(supermarket_gui SupermarketLib Qt5::Core Qt5::Widgets)
endif()

# Testing
enable_testing()
add_subdirectory(tests)

# Installation
install(TARGETS SupermarketLib supermarket_console
        RUNTIME DESTINATION bin
        LIBRARY DESTINATION lib
        ARCHIVE DESTINATION lib)

install(DIRECTORY include/ DESTINATION include)
install(DIRECTORY scripts/ DESTINATION scripts)

# CPack configuration
set(CPACK_PACKAGE_NAME "Supermarket Management System")
set(CPACK_PACKAGE_VERSION "1.0.0")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Comprehensive supermarket management system")
set(CPACK_PACKAGE_VENDOR "Safa Bayar")
set(CPACK_PACKAGE_CONTACT "bayarsafa@gmail.com")

include(CPack)

# Print configuration summary
message(STATUS "Supermarket Management System Configuration Summary:")
message(STATUS "  Version: ${PROJECT_VERSION}")
message(STATUS "  C++ Standard: ${CMAKE_CXX_STANDARD}")
message(STATUS "  Build Type: ${CMAKE_BUILD_TYPE}")
message(STATUS "  PostgreSQL: ${PostgreSQL_VERSION}")
message(STATUS "  libpqxx: ${LIBPQXX_VERSION}")
message(STATUS "  GUI Build: ${BUILD_GUI}")

Testing Framework

Unit Tests

// tests/unit/test_product_manager.cpp
#include <gtest/gtest.h>
#include "product_manager.h"
#include "database_manager.h"
#include <memory>

class ProductManagerTest : public ::testing::Test {
protected:
    void SetUp() override {
        // Setup test database connection
        DatabaseManager::ConnectionConfig config;
        config.database_name = "test_supermarket";
        db_manager = std::make_shared<DatabaseManager>(config);
        
        if (db_manager->connect()) {
            product_manager = std::make_unique<ProductManager>(db_manager);
        }
    }
    
    void TearDown() override {
        if (db_manager && db_manager->is_connected()) {
            db_manager->disconnect();
        }
    }
    
    std::shared_ptr<DatabaseManager> db_manager;
    std::unique_ptr<ProductManager> product_manager;
};

TEST_F(ProductManagerTest, AddProduct) {
    if (!product_manager) {
        GTEST_SKIP() << "Database connection failed";
    }
    
    Product product;
    product.name = "Test Product";
    product.description = "A test product";
    product.category = "Test Category";
    product.supplier = "Test Supplier";
    product.price = 10.99;
    product.cost = 5.50;
    product.stock_quantity = 100;
    product.min_stock_level = 10;
    product.max_stock_level = 1000;
    product.barcode = "123456789";
    product.sku = "TEST-001";
    product.unit_of_measure = "piece";
    product.is_active = true;
    
    bool result = product_manager->add_product(product);
    EXPECT_TRUE(result);
}

TEST_F(ProductManagerTest, UpdateStock) {
    if (!product_manager) {
        GTEST_SKIP() << "Database connection failed";
    }
    
    // First add a product
    Product product;
    product.name = "Stock Test Product";
    product.category = "Test Category";
    product.supplier = "Test Supplier";
    product.price = 10.99;
    product.cost = 5.50;
    product.stock_quantity = 50;
    product.is_active = true;
    
    bool add_result = product_manager->add_product(product);
    ASSERT_TRUE(add_result);
    
    // Get the product ID (this would be generated)
    auto products = product_manager->get_products();
    ASSERT_FALSE(products.empty());
    
    std::string product_id = products[0].id;
    
    // Update stock
    bool update_result = product_manager->update_stock(product_id, 25, "Test stock update");
    EXPECT_TRUE(update_result);
    
    // Verify stock was updated
    int current_stock = product_manager->get_current_stock(product_id);
    EXPECT_EQ(current_stock, 75); // 50 + 25
}

TEST_F(ProductManagerTest, SearchProducts) {
    if (!product_manager) {
        GTEST_SKIP() << "Database connection failed";
    }
    
    // Add test products
    Product product1;
    product1.name = "Apple iPhone";
    product1.category = "Electronics";
    product1.supplier = "Apple";
    product1.price = 999.99;
    product1.cost = 500.00;
    product1.stock_quantity = 10;
    product1.is_active = true;
    
    Product product2;
    product2.name = "Samsung Galaxy";
    product2.category = "Electronics";
    product2.supplier = "Samsung";
    product2.price = 899.99;
    product2.cost = 450.00;
    product2.stock_quantity = 15;
    product2.is_active = true;
    
    product_manager->add_product(product1);
    product_manager->add_product(product2);
    
    // Search for products
    auto results = product_manager->search_products("iPhone");
    EXPECT_FALSE(results.empty());
    
    auto electronics = product_manager->get_products_by_category("Electronics");
    EXPECT_EQ(electronics.size(), 2);
}

TEST_F(ProductManagerTest, LowStockAlert) {
    if (!product_manager) {
        GTEST_SKIP() << "Database connection failed";
    }
    
    // Add a product with low stock
    Product product;
    product.name = "Low Stock Product";
    product.category = "Test Category";
    product.supplier = "Test Supplier";
    product.price = 10.99;
    product.cost = 5.50;
    product.stock_quantity = 5; // Low stock
    product.min_stock_level = 10;
    product.is_active = true;
    
    product_manager->add_product(product);
    
    // Check for low stock products
    auto low_stock = product_manager->get_low_stock_products(10);
    EXPECT_FALSE(low_stock.empty());
    
    // Check inventory statistics
    auto stats = product_manager->get_inventory_statistics();
    EXPECT_GT(stats.low_stock_products, 0);
}

Lessons Learned

Database Design

  • Normalization: Proper database normalization for data integrity
  • Constraints: Comprehensive constraints for data validation
  • Indexing: Strategic indexing for query performance
  • Transactions: ACID compliance for data consistency

C++ Application Architecture

  • Modular Design: Clean separation of concerns
  • Memory Management: Smart pointers and RAII principles
  • Error Handling: Comprehensive exception handling
  • Performance: Optimized algorithms and data structures

Business Logic

  • Inventory Management: Real-time stock tracking and alerts
  • Transaction Processing: Secure payment processing
  • Reporting: Comprehensive business analytics
  • User Management: Role-based access control

System Integration

  • Database Integration: Advanced PostgreSQL features
  • Build System: Modern CMake practices
  • Testing: Comprehensive unit and integration testing
  • Documentation: Clear API documentation

Future Enhancements

Advanced Features

  • Multi-location Support: Support for multiple store locations
  • Advanced Analytics: Machine learning for demand forecasting
  • Mobile Integration: Mobile app for inventory management
  • Cloud Integration: Cloud-based backup and synchronization

Technical Improvements

  • Performance: Query optimization and caching
  • Scalability: Support for high-volume transactions
  • Security: Enhanced security and audit logging
  • Integration: Third-party system integrations

Conclusion

The Supermarket Management System demonstrates comprehensive C++ application development and database integration expertise. Key achievements include:

  • System Architecture: Well-designed modular architecture
  • Database Integration: Advanced PostgreSQL integration
  • Business Logic: Comprehensive retail management features
  • User Interface: Console-based management interface
  • Testing: Comprehensive testing framework
  • Documentation: Clear documentation and user guides

The project is available on GitHub and serves as a comprehensive example of C++ application development for business systems.


This project represents my deep dive into C++ application development and demonstrates how modern C++ can be used to build robust, efficient business applications with comprehensive database integration. The lessons learned here continue to influence my approach to system-level programming and business application development.