================================================================================ MPUMALANGA PVC - SYSTEM OVERVIEW ================================================================================ Last Updated: 2026-03-26 ================================================================================ WHAT IS THIS SYSTEM? -------------------- This is a custom-built business management system for Mpumalanga PVC Ceilings, Flooring and Artificial Grass. It handles the full sales and operations workflow from quoting a client through to invoicing, payments, supplier orders, and on-site work scheduling. TECH STACK ---------- - Language: PHP (server-side rendering, no frontend framework) - Database: MariaDB (hosted on ewg.dedicated.co.za) - Database name: elegaysv_edesigns_pvc - PDF: FPDF library (classes/fpdf.php) - Styling: Custom CSS utility classes (width_80, background_1, border_radius etc.) - JavaScript: Vanilla JS only, no jQuery or libraries HOSTING ------- Live: mpumalangapvc.elegantwork.co.za Dev: dev.mpumalangapvc.elegantwork.co.za (dev.edesigns.elegantwork.co.za) DB host: ewg.dedicated.co.za ================================================================================ FOLDER STRUCTURE ================================================================================ / Root /index.php Redirect to login or app home /login.php Login page /logout.php Session destroy + redirect /classes/ All PHP class files (autoloaded) /app/ All application modules /app/home.php Financial Year Dashboard /app/clients/ /app/quotes/ /app/invoices/ /app/payments/ /app/c_notes/ Credit Notes /app/delivery_note/ /app/supplier_orders/ /app/suppliers/ /app/stock/ /app/work_schedule/ /default_app_data/ System settings (users, user types, table views, links) /assets/ Logo images used in PDFs /app/old/ Old/unused supplier order files - ignore these ================================================================================ CORE CLASS SYSTEM (classes/) ================================================================================ Every page starts with: include "../../classes/autoload.php"; autoload.php loads all classes and creates two global objects: $db = new db(); (database connection) $function = new app_features(); (helper/utility functions) -------------------------------------------------------------------------------- CLASS: db (db.class.php) -------------------------------------------------------------------------------- Handles all database interaction. Constructor - Connects to MariaDB. - On first run, auto-creates system tables if missing: logs, users, user_types, user_table_views, default_table_views, default_links, user_type_app_access, excluded_file_identifiers, status - Creates a default DEV user and ADMIN user type. $db->query($table_name, $sql) - Runs any SQL statement. - First argument is the table name (used for logging and validation). - Checks the table exists before running; prints an error if it doesn't. - For INSERT queries: returns mysqli_insert_id() (the new record ID). IMPORTANT: Many tables in this app have NO AUTO_INCREMENT on record_id, so mysqli_insert_id() returns 0 for those tables. Do not rely on this return value for redirect logic on those tables. - For all other queries: returns the mysqli result object. - On SQL error: prints the error and exits. - Destructor automatically logs every non-SELECT query to the logs table. $db->select_query($table, $selector, $where_clause) - Builds a SELECT query for you. Less commonly used. $db->login($username, $password) - SHA256 hashes the password and checks against the users table. - Sets $_SESSION['user_id'] and $_SESSION['user_type'] on success. $db->session_check() - Returns the current user_id from session (or 0 if not logged in). $db->check_table_exists($table) - Returns true/false. Used internally before every query. KNOWN ISSUE WITH db->query() AND INSERTS: Tables without AUTO_INCREMENT always return 0 from query() on INSERT. Affected tables: clients, invoices, quotes, orders, credit_notes, delivery_note, supplier_orders, etc. Workaround: After INSERT, immediately SELECT the record back by a known unique value to get the real record_id. -------------------------------------------------------------------------------- CLASS: inner_app (app.class.php) -------------------------------------------------------------------------------- Controls the page shell (sidebar quick bar and content area wrapper). $app->quick_bar($folder_path) - Renders the left-side navigation bar. - Scans the current folder and creates a button for each visible PHP file. - Files are hidden from the bar if their name contains words in the excluded_file_identifiers table (default: pdf, edit, save, update, delete). - Also shows section buttons for all subfolders in /default_app_data/. - Pass the folder path string e.g. "/app/clients/" to scope it correctly. $app->app_start() - Opens the main content div wrapper and search bar overlay. $app->__destruct() - Closes the wrapper divs automatically when the page finishes rendering. quick_bar_button($text, $link) - Renders a nav button linking to a page. quick_bar_section_button($text, $link) - Renders a section heading button. search_button() - Injects the page-level search overlay JS. -------------------------------------------------------------------------------- CLASS: app_features (app.class.php) -- accessed via $function -------------------------------------------------------------------------------- Reusable helper functions used across all modules. get_clients_datalist($list_id) - Echoes a of all clients. - Format: "Client Name : record_id" - Used in quotes, invoices, etc. for client selection. get_suppliers_datalist($list_id) - Echoes a of all suppliers. - Format: "Supplier Name : record_id : CODE" get_stock_datalist($list_id) - Echoes a of all active stock items (status != 0). - Format: "CODE~Name~UOM~retail_price" - Tilde (~) separated so JS can split and populate multiple fields at once. get_stock_datalist_cost($list_id) - Same as get_stock_datalist but uses cost price instead of retail. get_invoices_list_as_record_id($list_id) - Echoes a of all invoices. - Each option has value=invoice_number and data-id=record_id attribute. - Used anywhere an invoice needs to be selected. get_unpaid_invoices_list() - Echoes plain