Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
4904fa6
feat(erp): Phase 1 — ERP foundations, React setup & authentication
claude May 24, 2026
7617c25
feat(erp): Phase 2 — Inventory module (backend + frontend)
claude May 29, 2026
c6eb117
chore: ignore .claude/ session directory
claude May 29, 2026
63df94d
feat(erp): Phase 3 — Finance module (backend + frontend)
claude May 29, 2026
e5a3705
feat(erp): Phase 4 — HR module, User Management, and Live Dashboard
claude May 29, 2026
aa2b2c2
feat(erp): Phase 5 — Analytics, Notifications, Audit Log & Settings
claude May 29, 2026
00ed26a
feat: Phase 6 — Global Search, CSV Export, Printable Invoice
claude May 29, 2026
a5988af
feat: Phase 7 partial — extract Invoice traits + bills migrations
claude May 30, 2026
0e65d5c
feat: Phase 7 — Accounts Payable (Bills) + Financial Statements
claude May 30, 2026
7759bc3
fix: Phase 7 review fixes — bill payment guard + test hardening
claude May 30, 2026
357e671
feat: Phase 8 — Aged Reports, Account Ledger, PO Receive UI, Stock Hi…
claude May 30, 2026
196e519
feat: Phase 9 WIP — Quotes models, policy, and migrations
claude May 31, 2026
78b8a85
feat: Phase 9 — Quotes (Estimates) module
claude May 31, 2026
cef9cc1
feat: Phase 10 WIP — Credit Notes backend (models, policy, controller…
claude May 31, 2026
ec75800
feat: Phase 10 — Credit Notes module + Customer Statement report
claude May 31, 2026
60ac948
feat: Phase 11 — Recurring Invoices with scheduled generation
claude May 31, 2026
ff3acb8
feat: Phase 12 — Sales Orders (fulfillment + invoice conversion)
claude May 31, 2026
d5e54a8
feat: Phase 13 (partial) — install dompdf and add PDF styles partial
claude May 31, 2026
99663dc
feat: Phase 13 — PDF generation and email delivery for invoices, quot…
claude May 31, 2026
f68f993
chore: add recharts dependency for dashboard charts
claude May 31, 2026
40b5027
feat: Phase 14 — Dashboard with KPI cards, revenue chart, and invento…
claude May 31, 2026
f5ee733
feat: Phase 15 — HR module (Employees, Departments, Leave Requests, P…
claude May 31, 2026
dba10ff
feat: Phase 16 — Multi-currency support with exchange rates
claude Jun 1, 2026
a4c0c62
feat: Phase 17 — Bank Reconciliation with CSV import and transaction …
claude Jun 1, 2026
d522d9b
feat: Phase 18 — Tax/VAT Return report
claude Jun 1, 2026
eb37e2c
feat: Phase 19 — User Management UI (invite, roles, activate/deactivate)
claude Jun 1, 2026
9ba6746
feat: Phase 20 — Company Settings (profile, currency, timezone, logo)
claude Jun 1, 2026
ec86ca7
feat: Phase 21 — CSV exports for all financial reports
claude Jun 1, 2026
413393f
feat: Phase 22 — Warehouse transfers with stock movement tracking
claude Jun 1, 2026
43386ad
feat: Phase 23 — Audit Log viewer with event and model filters
claude Jun 1, 2026
75749b2
feat: Phase 24 — In-app notifications for overdue invoices, low stock…
claude Jun 1, 2026
265728b
feat: Phase 25 — Employee Expense Claims (submit, approve, reject, re…
claude Jun 1, 2026
2c266dc
feat: Phase 26 — Budget Management with variance reporting
claude Jun 1, 2026
8625603
feat: Phase 27 — Fixed Assets register with straight-line depreciation
claude Jun 1, 2026
1086366
chore: ignore .env.bak files
claude Jun 1, 2026
a041bc8
feat: Phase 28 — Product Categories with colour badges and product fi…
claude Jun 1, 2026
06d55be
feat: Phase 29 — Reorder points with low-stock suggestions and one-cl…
claude Jun 1, 2026
f921e31
feat: Phase 30 — Price Lists with product overrides and global discounts
claude Jun 1, 2026
d756b71
feat: Phase 31 — Project Tracking with billable hours and invoice lin…
claude Jun 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ yarn-debug.log*
yarn-error.log*
/.changelog
.npm/
.claude/
18 changes: 18 additions & 0 deletions erp/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
root = true

[*]
charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true

[*.md]
trim_trailing_whitespace = false

[*.{yml,yaml}]
indent_size = 2

[{compose,docker-compose}.{yml,yaml}]
indent_size = 4
65 changes: 65 additions & 0 deletions erp/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
APP_NAME=Laravel
APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_URL=http://localhost

APP_LOCALE=en
APP_FALLBACK_LOCALE=en
APP_FAKER_LOCALE=en_US

APP_MAINTENANCE_DRIVER=file
# APP_MAINTENANCE_STORE=database

# PHP_CLI_SERVER_WORKERS=4

BCRYPT_ROUNDS=12

LOG_CHANNEL=stack
LOG_STACK=single
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug

DB_CONNECTION=sqlite
# DB_HOST=127.0.0.1
# DB_PORT=3306
# DB_DATABASE=laravel
# DB_USERNAME=root
# DB_PASSWORD=

SESSION_DRIVER=database
SESSION_LIFETIME=120
SESSION_ENCRYPT=false
SESSION_PATH=/
SESSION_DOMAIN=null

BROADCAST_CONNECTION=log
FILESYSTEM_DISK=local
QUEUE_CONNECTION=database

CACHE_STORE=database
# CACHE_PREFIX=

MEMCACHED_HOST=127.0.0.1

REDIS_CLIENT=phpredis
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379

MAIL_MAILER=log
MAIL_SCHEME=null
MAIL_HOST=127.0.0.1
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_FROM_ADDRESS="hello@example.com"
MAIL_FROM_NAME="${APP_NAME}"

AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=
AWS_USE_PATH_STYLE_ENDPOINT=false

VITE_APP_NAME="${APP_NAME}"
11 changes: 11 additions & 0 deletions erp/.gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
* text=auto eol=lf

*.blade.php diff=html
*.css diff=css
*.html diff=html
*.md diff=markdown
*.php diff=php

/.github export-ignore
CHANGELOG.md export-ignore
.styleci.yml export-ignore
28 changes: 28 additions & 0 deletions erp/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
*.log
.DS_Store
.env
.env.backup
.env.production
.phpactor.json
.phpunit.result.cache
/.codex
/.cursor/
/.idea
/.nova
/.phpunit.cache
/.vscode
/.zed
/auth.json
/node_modules
/public/build
/public/fonts-manifest.dev.json
/public/hot
/public/storage
/storage/*.key
/storage/pail
/vendor
_ide_helper.php
Homestead.json
Homestead.yaml
Thumbs.db
erp/.env.bak
2 changes: 2 additions & 0 deletions erp/.npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ignore-scripts=true
audit=true
58 changes: 58 additions & 0 deletions erp/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<p align="center"><a href="https://laravel.com" target="_blank"><img src="https://raw.githubusercontent.com/laravel/art/master/logo-lockup/5%20SVG/2%20CMYK/1%20Full%20Color/laravel-logolockup-cmyk-red.svg" width="400" alt="Laravel Logo"></a></p>

<p align="center">
<a href="https://github.com/laravel/framework/actions"><img src="https://github.com/laravel/framework/workflows/tests/badge.svg" alt="Build Status"></a>
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/dt/laravel/framework" alt="Total Downloads"></a>
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/v/laravel/framework" alt="Latest Stable Version"></a>
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/l/laravel/framework" alt="License"></a>
</p>

## About Laravel

Laravel is a web application framework with expressive, elegant syntax. We believe development must be an enjoyable and creative experience to be truly fulfilling. Laravel takes the pain out of development by easing common tasks used in many web projects, such as:

- [Simple, fast routing engine](https://laravel.com/docs/routing).
- [Powerful dependency injection container](https://laravel.com/docs/container).
- Multiple back-ends for [session](https://laravel.com/docs/session) and [cache](https://laravel.com/docs/cache) storage.
- Expressive, intuitive [database ORM](https://laravel.com/docs/eloquent).
- Database agnostic [schema migrations](https://laravel.com/docs/migrations).
- [Robust background job processing](https://laravel.com/docs/queues).
- [Real-time event broadcasting](https://laravel.com/docs/broadcasting).

Laravel is accessible, powerful, and provides tools required for large, robust applications.

## Learning Laravel

Laravel has the most extensive and thorough [documentation](https://laravel.com/docs) and video tutorial library of all modern web application frameworks, making it a breeze to get started with the framework.

In addition, [Laracasts](https://laracasts.com) contains thousands of video tutorials on a range of topics including Laravel, modern PHP, unit testing, and JavaScript. Boost your skills by digging into our comprehensive video library.

You can also watch bite-sized lessons with real-world projects on [Laravel Learn](https://laravel.com/learn), where you will be guided through building a Laravel application from scratch while learning PHP fundamentals.

## Agentic Development

Laravel's predictable structure and conventions make it ideal for AI coding agents like Claude Code, Cursor, and GitHub Copilot. Install [Laravel Boost](https://laravel.com/docs/ai) to supercharge your AI workflow:

```bash
composer require laravel/boost --dev

php artisan boost:install
```

Boost provides your agent 15+ tools and skills that help agents build Laravel applications while following best practices.

## Contributing

Thank you for considering contributing to the Laravel framework! The contribution guide can be found in the [Laravel documentation](https://laravel.com/docs/contributions).

## Code of Conduct

In order to ensure that the Laravel community is welcoming to all, please review and abide by the [Code of Conduct](https://laravel.com/docs/contributions#code-of-conduct).

## Security Vulnerabilities

If you discover a security vulnerability within Laravel, please send an e-mail to Taylor Otwell via [taylor@laravel.com](mailto:taylor@laravel.com). All security vulnerabilities will be promptly addressed.

## License

The Laravel framework is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT).
47 changes: 47 additions & 0 deletions erp/app/Http/Controllers/Admin/AuditLogController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use App\Models\User;
use App\Modules\Core\Models\AuditLog;
use Illuminate\Http\Request;
use Inertia\Inertia;
use Inertia\Response;

class AuditLogController extends Controller
{
public function index(Request $request): Response
{
$this->authorize('viewAny', User::class);

$logs = AuditLog::with('user')
->where('tenant_id', auth()->user()->tenant_id)
->when($request->event, fn ($q) => $q->where('event', $request->event))
->when($request->model, fn ($q) => $q->where('auditable_type', 'like', "%{$request->model}%"))
->latest()
->paginate(50)
->withQueryString()
->through(fn ($log) => [
'id' => $log->id,
'event' => $log->event,
'model' => class_basename($log->auditable_type),
'model_id' => $log->auditable_id,
'user' => $log->user?->name ?? 'System',
'old_values' => $log->old_values,
'new_values' => $log->new_values,
'ip_address' => $log->ip_address,
'created_at' => $log->created_at->diffForHumans(),
'created_at_raw' => $log->created_at->toDateTimeString(),
]);

return Inertia::render('Admin/AuditLog/Index', [
'logs' => $logs,
'filters' => $request->only(['event', 'model']),
'breadcrumbs' => [
['label' => 'Administration'],
['label' => 'Audit Log', 'href' => route('admin.audit-log.index')],
],
]);
}
}
139 changes: 139 additions & 0 deletions erp/app/Http/Controllers/Admin/UserController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\Rules\Password;
use Inertia\Inertia;
use Inertia\Response;
use Spatie\Permission\Models\Role;

class UserController extends Controller
{
public function index(Request $request): Response
{
$this->authorize('viewAny', User::class);

$users = User::with('roles')
->when($request->search, fn ($q) => $q->where('name', 'like', "%{$request->search}%")
->orWhere('email', 'like', "%{$request->search}%"))
->where('tenant_id', auth()->user()->tenant_id)
->orderBy('name')
->paginate(25)
->withQueryString();

return Inertia::render('Admin/Users/Index', [
'users' => $users->through(fn ($u) => [
'id' => $u->id,
'name' => $u->name,
'email' => $u->email,
'roles' => $u->roles->pluck('name'),
'created_at' => $u->created_at?->toDateString(),
]),
'filters' => $request->only(['search']),
'breadcrumbs' => [
['label' => 'Administration'],
['label' => 'Users', 'href' => route('admin.users.index')],
],
]);
}

public function create(): Response
{
$this->authorize('create', User::class);

return Inertia::render('Admin/Users/Create', [
'roles' => Role::orderBy('name')->pluck('name'),
'breadcrumbs' => [
['label' => 'Administration'],
['label' => 'Users', 'href' => route('admin.users.index')],
['label' => 'New User'],
],
]);
}

public function store(Request $request): RedirectResponse
{
$this->authorize('create', User::class);

$data = $request->validate([
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'email', 'max:255', 'unique:users,email'],
'password' => ['required', Password::defaults()],
'role' => ['required', 'string', 'exists:roles,name'],
]);

$user = User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => Hash::make($data['password']),
'tenant_id' => auth()->user()->tenant_id,
]);

$user->assignRole($data['role']);

return redirect()->route('admin.users.index')
->with('success', 'User created.');
}

public function edit(User $user): Response
{
$this->authorize('update', $user);

return Inertia::render('Admin/Users/Edit', [
'user' => [
'id' => $user->id,
'name' => $user->name,
'email' => $user->email,
'role' => $user->roles->first()?->name,
],
'roles' => Role::orderBy('name')->pluck('name'),
'breadcrumbs' => [
['label' => 'Administration'],
['label' => 'Users', 'href' => route('admin.users.index')],
['label' => $user->name . ' — Edit'],
],
]);
}

public function update(Request $request, User $user): RedirectResponse
{
$this->authorize('update', $user);

$data = $request->validate([
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'email', 'max:255', "unique:users,email,{$user->id}"],
'password' => ['nullable', Password::defaults()],
'role' => ['required', 'string', 'exists:roles,name'],
]);

$user->update([
'name' => $data['name'],
'email' => $data['email'],
...(($data['password'] ?? null) ? ['password' => Hash::make($data['password'])] : []),
]);

$user->syncRoles([$data['role']]);

return redirect()->route('admin.users.index')
->with('success', 'User updated.');
}

public function destroy(User $user): RedirectResponse
{
$this->authorize('delete', $user);

if ($user->id === auth()->id()) {
return back()->withErrors(['user' => 'You cannot delete your own account.']);
}

$user->delete();

return redirect()->route('admin.users.index')
->with('success', 'User deleted.');
}
}
Loading