NamaIn is a multi-tenant inventory and invoicing platform that runs your whole back office — products, customers, suppliers, stock across multiple storages, invoices, payments, quotes, POS sessions, and account statements — with full Arabic/RTL support and dark mode throughout. Role-based access, CSV/Excel import-export, clean browser-based printing, and a calm, consistent design come built in, so your team spends time running the business instead of fighting the software.
- Multi-tenancy — every tenant lives on its own subdomain (
{tenant}.namain.test) with strict data isolation enforced through a global tenant scope on every model. - Catalog — products, categories, multiple units per product with conversion factors, expiry tracking, low-stock alerts.
- Inventory — multiple storages per tenant, stock additions from purchase invoices, deductions on sales, transfers between storages, manual adjustments with full audit trail.
- Sales & Purchases — invoices, transactions, partial delivery tracking, returns (sale and purchase), price quotes that convert to invoices.
- POS — checkout sessions, fast item lookup, thermal receipt printing (80mm).
- Payments & Treasury — payments against invoices, customer advances, cheques (payee tracking + status), treasury accounts and transfers, expense management with approvals.
- Contacts — customers and suppliers with running balances, category tagging, account statements over any date range.
- Roles & Permissions — tenant-scoped roles (owner, admin, etc.) with fine-grained permissions backed by a default-roles service.
- Import / Export — CSV and Excel pipelines with QuickBooks-compatible templates, queued background processing, broadcast progress updates, and validation failure reporting.
- Real-time — broadcasting via Reverb for import progress and operation feeds.
- Printing — invoices, POS receipts, and account statements all render as Vue pages that trigger the browser's native print dialog; no headless Chrome, no PDF generation.
- Internationalization — Arabic and English UI with full RTL support across every page.
app/
├── Actions/ Single-responsibility business operations (e.g. SettleCustomerAdvanceAction)
├── Enums/ PaymentStatus, PaymentMethod, InvoiceStatus, ...
├── Events/ Broadcast events (ImportStatusUpdated, OperationFeed, ...)
├── Exports/ Excel/CSV export classes
├── Http/Controllers/ Grouped by domain: Catalog, Inventory, Invoicing, Sales, Contacts, ...
├── Http/Requests/ FormRequest classes for validation
├── Imports/ Maatwebsite/Excel import classes + Concerns
├── Jobs/ Queued work (ProcessImportJob, GenerateExportJob)
├── Models/ Eloquent models, all extend BaseModel
├── Policies/ Authorization policies
├── Queries/ Query objects (StatementQuery, PartyAccountQuery, ...)
├── Scopes/ Global scopes (TenantScope)
├── Services/ Cross-cutting services (CsvSampleGenerator, OperationFeed, ...)
├── Traits/ Reusable model/controller traits (BelongsToTenant, HandlesPartyAccount)
└── ValueObjects/ Domain values
resources/
├── js/Pages/ Inertia Vue pages mirroring controller domains (Invoices, Quotes, Pos, ...)
└── lang/ Translation files (en, ar)
routes/
├── web.php Root domain routes (landing, tenant selection)
└── tenant.php Tenant subdomain routes (the application surface)
- Tenants are subdomains. Routes in
routes/tenant.phpare bound to{tenant}.namain.testand gated by a tenant-resolution middleware. - The
BelongsToTenanttrait onBaseModeladds:- A
tenant_idforeign column (added by migrationadd_tenant_id_to_all_tables). - A
TenantScopethat constrains every query to the current tenant — and explicitly returns no rows if no tenant context is bound, to fail safe. - A
creatinghook that auto-fillstenant_idfrom the authenticated user'scurrent_tenant_idor the boundcurrentTenantinstance.
- A
- Users can belong to multiple tenants via the
tenant_userpivot, and switch viatenant.switch.
- Inertia.js connects the Laravel backend to Vue pages — no separate API layer, no manual routing.
- Tailwind utility classes only, no component library. A strict design system lives in
CLAUDE.mdcovering colors, spacing, dark mode pairings, and RTL handling. - Inline SVG icons from Heroicons; no icon font.
- All printable documents (invoices, receipts, statements, quotes) are Vue pages that call
window.print()on mount — no PDF backend.
- Models are unguarded by default (
Model::unguard()inBaseModel::booted). - Form requests live in
app/Http/Requests. Controllers stay thin. - Validation rules that need domain knowledge live in the request, not the controller (e.g.
StockRequestrejects already-delivered invoices). - Actions encapsulate non-trivial workflows; controllers delegate to them.
- PHP 8.4 (the project's
composer.jsonlock targets 8.4; 8.3 will hit unrelated symfony version conflicts) - Composer 2
- Node.js 18+
- A relational database (MySQL/PostgreSQL/SQLite)
- Redis (for queues, broadcasting, cache)
- A local host that resolves wildcard subdomains (e.g. Laravel Herd, dnsmasq, or
/etc/hostsentries for each tenant)
# 1. Clone and install dependencies
git clone <repo-url> namain
cd namain
composer install
npm install
# 2. Environment
cp .env.example .env
php artisan key:generate
# Edit .env — at minimum set:
# APP_DOMAIN=namain.test (or whatever your local TLD is)
# APP_URL=http://namain.test
# DB_* credentials
# QUEUE_CONNECTION=redis (recommended)
# BROADCAST_DRIVER=reverb
# 3. Database
php artisan migrate --seed
# 4. Frontend
npm run build # production assets, or
npm run dev # Vite dev server with HMR
# 5. Serve
# With Laravel Herd: the project is already served at https://namain.test
# Otherwise: php artisan serve (note: tenant subdomains require Herd/Valet/nginx)# Queue worker — required for imports, exports, broadcasting
php artisan horizon # (Horizon is configured for this project)
# Reverb broadcaster
php artisan reverb:startThe test suite uses Pest. SQLite in-memory is configured in phpunit.xml for fast, isolated runs.
# Run everything
php artisan test --compact
# Filter by name or path
php artisan test --compact --filter=QuotesTest
php artisan test --compact tests/Feature/InvoicesControllerTest.php
# Architecture tests (Pest arch())
php artisan test --compact --testsuite=ArchitectureThe project also ships Cypress E2E tests under tests/cypress/:
npx cypress open# PHP (Laravel Pint)
vendor/bin/pint # format everything
vendor/bin/pint --dirty --format agent # format only changed files
# JavaScript / Vue
npm run lint # ESLint with --fix
npm run format # PrettierCLAUDE.md contains the working agreement for AI assistants on the project, but it doubles as living documentation for:
- The UI design system (colors, spacing, typography, dark mode, RTL).
- Laravel and Eloquent conventions used here.
- Skill guidance for common tasks (authentication, testing, broadcasting, Horizon, Tailwind).
Read it before making non-trivial changes.
- Branch from
master. - Write or update tests for any behavioral change — see
tests/Feature/for examples. - Run
vendor/bin/pint --dirty --format agentandnpm run lintbefore opening a PR. - Make sure
php artisan test --compactpasses locally. - Open a pull request; CI runs the full suite on every push.