PHP 8 · MySQL 8 · Production Ready

CoreConduit Inventory System

The production inventory system powering CoreConduit operations. Full-featured stock management with order tracking, customer CRM, and automated reporting. Evolved from years of OSWA-Inv customization.

⏱ 45 min📊 Advanced📅 Production v3.5

What Makes This Different from OSWA-Inv

The CoreConduit Inventory System is what happened when OSWA-Inv met real-world nonprofit operations. After running OSWA-Inv for several years, I identified gaps that required custom development:

Order-Centric Workflow

All sales must belong to an order. Delete an order and stock auto-restores. View order totals, print invoices, and manage by order number.

Stock Adjustment Logging

Every quantity change is logged with reason. No more "where did those 50 units go?" — full audit trail for compliance.

Customer CRM

Customer database with contact info, order history, and notes. Search by customer when adding orders.

Location Tracking

Products can have physical locations ("Warehouse A", "Shelf 3B"). Pick lists generated by order location.

Multi-Currency Support

Admin-editable currency selection covering 91+ ISO 4217 codes. Change from User Management → Settings — no config file editing required.

Role-Based Access

Admin, Supervisor, and User roles. Users can add sales but not delete. Supervisors can adjust stock with full audit trail. Role names match the codebase constants.

Multi-Tenant Organizations

Multiple organizations per installation, each with full data isolation. Topbar org switcher appears when a user belongs to two or more orgs. Full org management UI for admins — create, rename, add members, soft-delete, and restore.

Soft-Delete & Recovery

Users, customers, orders, sales, and stock records are soft-deleted rather than hard-purged. Admins can browse a trash view, restore records, or permanently purge — no data loss from accidental deletes.

★ From the Field: This system has managed inventory for community organizations with 5,000+ SKUs and $200K+ annual throughput. The order-centric design emerged after losing track of which sales belonged to which grant-funded projects — a problem the original OSWA-Inv couldn't solve.

System Requirements

  • Raspberry Pi 5 (8GB) (recommended; also works on Raspberry Pi 4 4GB+; for 1000+ products)
  • Raspberry Pi OS (64-bit) or Ubuntu Server 24.04 LTS
  • Apache 2.4+ with mod_rewrite
  • PHP 8.1+ with mysqli, gd, mbstring, json extensions
  • MySQL 8.0+ or MariaDB 10.11+
  • SSL certificate (Let's Encrypt recommended for remote access)

Installation

Step 1: LAMP Stack Setup

# Update system
sudo apt update && sudo apt full-upgrade -y

# Install Apache, PHP, MySQL
sudo apt install -y apache2 php php-mysql php-gd php-mbstring php-json \
    php-curl php-zip php-xml mariadb-server

# Enable mod_rewrite for pretty URLs
sudo a2enmod rewrite
sudo systemctl restart apache2

# Secure MySQL
sudo mysql_secure_installation

Step 2: Database Setup

# Create database and user
sudo mysql -u root

CREATE DATABASE coreconduit_inventory CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

CREATE USER 'inv_user'@'localhost' IDENTIFIED BY 'your_strong_password_here';
GRANT ALL PRIVILEGES ON coreconduit_inventory.* TO 'inv_user'@'localhost';
FLUSH PRIVILEGES;
EXIT;

Step 3: Application Deployment

# Clone repository (or extract from backup)
cd /var/www/html
sudo git clone https://github.com/coreconduit/inventory-system.git inventory
# Or: sudo tar -xzf inventory-v3.5.tar.gz

# Set permissions
sudo chown -R www-data:www-data inventory/
sudo chmod -R 755 inventory/
sudo chmod -R 775 inventory/uploads/
sudo chmod -R 775 inventory/logs/

# Import database schema (schema.sql is at the project root)
cd inventory
sudo mysql -u inv_user -p coreconduit_inventory < schema.sql

Step 4: Configuration

# Copy the example env file and edit it
cd /var/www/html/inventory
sudo cp .env.example .env
sudo nano .env

# Set these values:
DB_HOST=localhost
DB_USER=inv_user
DB_PASS=your_strong_password_here
DB_NAME=coreconduit_inventory
APP_SECRET=generate_a_long_random_string_here
APP_LANG=en
★ No PHP constants to edit: Configuration lives in .env at the project root, loaded by includes/config.php at runtime. Never commit your .env — it's in .gitignore. Currency symbol and formatting are managed through the admin panel (User Management → Settings) — no file editing required after first login.

Step 5: Apache Virtual Host

# Create Apache config
sudo nano /etc/apache2/sites-available/inventory.conf


    ServerName inventory.yourdomain.com
    DocumentRoot /var/www/html/inventory

    
        Options -Indexes +FollowSymLinks
        AllowOverride All
        Require all granted
    

    ErrorLog ${APACHE_LOG_DIR}/inventory-error.log
    CustomLog ${APACHE_LOG_DIR}/inventory-access.log combined


# Enable site and reload
sudo a2ensite inventory
sudo systemctl reload apache2
★ Security Note: The AllowOverride All directive allows the included .htaccess file to enforce additional security rules (IP restrictions, XSS protection headers). Review .htaccess before deploying to production.

First-Time Setup

After installation, access the system and complete initial setup:

  1. Change default admin password — Default credentials: admin / admin (change immediately after first login)
  2. Create user accounts — Add staff members with appropriate roles (Admin / Supervisor / User)
  3. Configure currency — User Management → Settings, select your currency from 91+ ISO 4217 codes
  4. Configure organization settings — Manage → Organizations to rename your default org, add members, or create additional orgs
  5. Set up categories — Create at least one category before adding products
  6. Add locations — Define storage locations if using location tracking
  7. Set reorder points — Configure low stock alerts per product

Organization Management

The system supports multiple organizations per installation with full row-level data isolation — each org sees only its own products, customers, orders, and sales. This is designed for consulting firms, fiscal sponsors, or any operator managing inventory across multiple departments or client organizations.

Org Switcher

When a user belongs to two or more organizations, a switcher appears in the topbar. Clicking it shows all orgs with a checkmark on the active one. Switching takes effect immediately — all subsequent queries are scoped to the selected org.

Managing Organizations (Admin only)

  1. Navigate to Manage → Organizations
  2. Create a new org — give it a unique name
  3. Add members — search for existing users and assign them
  4. Rename or soft-delete orgs as needed — deleted orgs can be restored before a permanent purge
★ Single-org deployments: If you're running one organization (the common case), you never need to touch org management. A "Default Organization" is seeded at install time and all data automatically belongs to it. The topbar switcher only appears when a user has two or more org memberships.

Daily Workflow

Receiving Stock

  1. Navigate to Inventory → Stock Adjustment
  2. Select product, enter quantity received
  3. Select "Purchase" or "Donation" as reason
  4. Add notes (PO number, donor name) for audit trail
  5. Save — quantity updates automatically

Creating an Order

  1. Sales → New Order
  2. Select existing customer or create new
  3. Add products — system prevents overselling based on available stock
  4. Set order status: Quote, Pending, Processing, Shipped, Complete
  5. Print pick list (sorted by location) for warehouse staff
  6. Print or email invoice when order ships

Stock Take / Audit

  1. Reports → Stock Status
  2. Export to CSV for physical count
  3. Use Stock Adjustment with "Inventory Count" reason to correct discrepancies
  4. All adjustments logged with user ID and timestamp

Did this guide help?

Your answers shape what we write next.

Backup Strategy

The system includes backup.php at the project root. Run it directly or schedule via cron:

# Run a manual backup (as web user to match file permissions)
sudo -u www-data php /var/www/html/inventory/backup.php

# Schedule daily at 2 AM
sudo crontab -e
# Add: 0 2 * * * sudo -u www-data php /var/www/html/inventory/backup.php
★ Backup Includes:
  • Full database dump (UTF-8 encoded)
  • Uploaded product images
  • Configuration files (sanitized)
  • 30-day rotation (configurable)
Store backups off-device — USB, NAS, or sync to remote storage.

Security Hardening

CSRF Protection

Every form in the system includes a hidden CSRF token generated per-session. On POST, the server calls verify_csrf() before processing any data — requests that fail the check are rejected immediately. This is built in and requires no additional configuration.

# Verify CSRF is active by checking any edit form response
# The token appears as a hidden input in every form:
# <input type="hidden" name="csrf_token" value="...">

Content Security Policy

The Apache virtual host enforces a Content Security Policy header that restricts script and style sources to 'self' only. No inline styles, no external CDN scripts. This blocks a significant class of XSS attacks at the browser level.

# Add to your Apache VirtualHost (or .htaccess):
Header always set Content-Security-Policy \
  "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:"

File Permissions

# Set restrictive permissions on the .env file
sudo chmod 640 /var/www/html/inventory/.env
sudo chown root:www-data /var/www/html/inventory/.env

# Disable PHP execution in uploads directory
echo "\n    Require all denied\n" | \
    sudo tee /var/www/html/inventory/uploads/.htaccess

Database Security

# Remove remote access for database user
sudo mysql -u root
REVOKE ALL PRIVILEGES ON *.* FROM 'inv_user'@'%';
FLUSH PRIVILEGES;

# Enable query logging for audit (optional)
SET GLOBAL general_log = 'ON';
SET GLOBAL general_log_file = '/var/log/mysql/inventory-queries.log';
EXIT;

HTTPS with Let's Encrypt

# Install certbot
sudo apt install certbot python3-certbot-apache

# Obtain certificate
sudo certbot --apache -d inventory.yourdomain.com

# Auto-renewal is configured automatically
sudo certbot renew --dry-run  # Test renewal

Troubleshooting

"Cannot connect to database"

  • Verify MySQL is running: sudo systemctl status mysql
  • Check credentials in .env at the project root
  • Test connection: mysql -u inv_user -p coreconduit_inventory
  • Check MySQL error log: sudo tail /var/log/mysql/error.log

"500 Internal Server Error"

  • Check Apache error log: sudo tail /var/log/apache2/inventory-error.log
  • Verify PHP modules: php -m | grep -E "mysqli|gd|mbstring"
  • Check file permissions: sudo ls -la /var/www/html/inventory/

"Session timeout too fast"

Edit php.ini:

sudo nano /etc/php/8.2/apache2/php.ini

# Set session lifetime (in seconds)
session.gc_maxlifetime = 3600  # 1 hour
session.cookie_lifetime = 3600

Migration from OSWA-Inv

If you're currently running OSWA-Inv and want to migrate:

  1. Backup existing system — Database and files (see backup guide)
  2. Export products — Use OSWA-Inv export feature or CSV from phpMyAdmin
  3. Install CoreConduit Inventory — Fresh installation alongside (different directory)
  4. Import products — Use included CSV import tool
  5. Reconcile stock — Adjust quantities to match physical count
  6. Train staff — New order-centric workflow requires mindset shift
  7. Switch over — Change Apache DocumentRoot when ready
★ Migration Warning: OSWA-Inv sales history cannot be directly imported due to the order structure difference. Export historical data as CSV for reference, but plan to start fresh orders in the new system.

Source Code & Updates

The CoreConduit Inventory System is MIT licensed and available on GitHub:

View Source on GitHub

Updates are released as features stabilize. To update your installation:

# Put system in maintenance mode (optional)
sudo touch /var/www/html/inventory/.maintenance

# Backup first (backup.php is at the project root, run as web user)
sudo -u www-data php /var/www/html/inventory/backup.php

# Pull updates (if using git)
cd /var/www/html/inventory
git pull origin main

# Or extract new version over existing (keeping config)
sudo tar -xzf inventory-v3.5.tar.gz --strip-components=1

# Run any new database migrations (numbered sequentially, apply in order)
ls migrations/
sudo mysql -u inv_user -p coreconduit_inventory < migrations/005_users_soft_delete.up.sql

# Clear cache
sudo rm -rf /var/www/html/inventory/cache/*
sudo touch /var/www/html/inventory/.updated

# Remove maintenance mode
sudo rm /var/www/html/inventory/.maintenance
← Back to Guides

Join the conversation.

Questions, experiences, or ideas — we're listening.