6.8 KiB
Agent Instructions
This document is the single source of truth for all AI agents working on the Open Source Point of Sale (OSPOS) codebase. Read it fully before making any changes.
Project Overview
OpenSourcePOS is a web-based Point of Sale system built on CodeIgniter 4 (PHP 8.2+) with MySQL/MariaDB. Frontend uses Bootstrap 3 (Bootstrap 5 migration in progress) and jQuery, with assets built via Gulp.
Common Commands
# PHP dependencies
composer install
# Frontend dependencies and asset build
npm install
npm run build # Runs Gulp: compiles and copies all CSS/JS to public/resources/
# Run full test suite
composer test
# Run a single test file
vendor/bin/phpunit tests/unit/AppTest.php
# Lint / code style check
vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.no-header.php --dry-run
# Apply code style fixes
vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.no-header.php
Tests require a MariaDB/MySQL database (see CI config in .github/workflows/phpunit.yml).
Architecture
Framework & Entry Point
- Framework: CodeIgniter 4 — MVC with QueryBuilder ORM, no Eloquent
- Web root:
public/—public/index.phpis the only entry point - Routes:
app/Config/Routes.php - App config:
app/Config/App.php(version, session, security settings) - Environment:
.envfile (copy from.env.example);CI_ENVIRONMENTcontrols dev/prod/test mode
Directory Layout
app/
├── Config/ # CI4 config classes
├── Controllers/ # ~27 controllers (Sales, Items, Reports, Customers, etc.)
├── Models/ # ~28 models (Sale, Item, Customer, Supplier, etc.)
├── Views/ # PHP view templates
├── Libraries/ # Business logic (Sale_lib, Tax_lib, Receiving_lib, etc.)
├── Plugins/ # Plugin system — each plugin is a subdirectory here
├── Database/ # Migrations (ospos_ prefix) and seeds
├── Language/ # i18n files (IETF BCP 47 locale names)
├── Filters/ # Request/response filters (auth, HTTPS, etc.)
└── Events/ # CI4 event subscribers
public/
└── resources/ # Built CSS/JS (do not edit directly — generated by npm run build)
tests/ # PHPUnit test suite
Key Libraries
app/Libraries/ holds core business logic:
Sale_lib.php— sale cart state, pricing, discounts, tax calculationTax_lib.php— multi-tier tax engineReceiving_lib.php— purchase orders / receivingsBarcode_lib.php— barcode generationEmail_lib.php— email deliveryToken_lib.php— CSRF/session token management
Database
- Table prefix:
ospos_(defined inapp/Config/Database.php) - Migrations live in
app/Database/Migrations/and run automatically on first access - CodeIgniter QueryBuilder throughout — no raw SQL unless necessary
Plugin System
MANDATORY: Read app/Plugins/README.md before doing any work related to plugins. It is the authoritative reference for plugin architecture, event contracts, view hook points, the $pluginData pass-through mechanism, language file conventions, migrations, and LICENSE requirements.
Plugins live in app/Plugins/<PluginName>/ and are auto-discovered by PluginManager. Each plugin:
- Extends
BasePluginor implementsPluginInterface - Registers event hooks in
registerEvents()usingEvents::on() - Can include its own
Views/,Models/,Controllers/,Language/, andMigrations/subdirectories - Configuration stored in
ospos_plugin_configtable - Plugin-specific routes go in
Config/Routes.phpwithin the plugin directory
View Hook Points
Core views inject plugin UI using pluginContent('hook_name', $data). All currently defined hook points are listed in app/Plugins/README.md. When adding a new hook point to a core view:
- Call
pluginContent('your_hook_name', $data)at the injection point - If the hook is inside or adjacent to a form whose controller fires
Events::trigger(), adddata-plugin-form="true"to that form tag soplugin_data_helper.jsserializes any[data-plugin-field]inputs into the POST
Plugin Data Pass-through ($pluginData)
All Events::trigger() calls in core controllers pass a final array $pluginData argument decoded from the POST field plugin_data. Plugin listeners declare array $pluginData = [] as the last parameter. Plugin-injected inputs use data-plugin-field="pluginid_varname" (namespaced to the plugin ID) — the core JS collects and serializes them automatically. Never add plugin-specific logic to core controllers to read individual keys; the entire opaque array is forwarded as-is.
Frontend Build
gulpfile.js (Gulp 5) copies vendor CSS/JS from node_modules/ into public/resources/. Run npm run build after installing npm packages or changing gulp tasks. Do not manually edit files under public/resources/.
Code Style
- PSR-12 enforced via PHP-CS-Fixer (config:
.php-cs-fixer.no-header.php) camelCasefor variables and methods;PascalCasefor classes;UPPER_CASEfor constants- PHP 8.2+ features acceptable (named arguments, enums, readonly properties)
- Views in
app/Views/errors/html/are excluded from the fixer - Run fixer before committing:
vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.no-header.php
Development Workflow
- Create a new git worktree for each issue, based on the latest state of
origin/master - Commit fixes to the worktree and push to the remote
- Tests must pass before submitting changes (
composer test) - Minimum PHPUnit version: 10.5.16+. Default config:
phpunit.xml.dist
Conventions
- Controllers →
app/Controllers/ - Models →
app/Models/ - Views →
app/Views/ - Migrations →
app/Database/Migrations/ - Plugins →
app/Plugins/(seeapp/Plugins/README.mdfor plugin structure, event hooks, and LICENSE requirements) - Use CodeIgniter 4 framework patterns and helpers
- Sanitize user input; escape output using
esc()helper
Localization
- When adding new keys to language files, add the key to all
app/Language/*/variants - Non-English files must use an empty string (
'') as the value when no translation is provided — CodeIgniter automatically falls back to the default (en) language - Only
app/Language/en/andapp/Language/en-GB/should contain English strings - Use
'to encapsulate key and string values. If the value contains'then it should be escaped as\'
Security
app.allowedHostnamesmust be set in production (host header injection protection)- HTMLPurifier for HTML sanitization; Laminas Escaper for output escaping
- CSRF tokens managed via
Token_lib— do not bypass CI4's CSRF filter - Session storage is database-backed (
ospos_sessionstable) for multi-instance support - Never commit secrets, credentials, or
.envfiles - Use parameterized queries to prevent SQL injection
- Validate and sanitize all user input