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
└── LICENSECore 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.