MyERP is a backend-only ERP system built with Laravel 12, focused on enterprise-grade business workflows, strict type safety, and a reusable approval workflow engine.
This project demonstrates real-world ERP architecture patterns used in enterprise systems, HR platforms, and financial applications.
This repository showcases:
- Enterprise Laravel architecture with strict coding standards
- Database-first design with comprehensive ERD modeling
- Reusable approval workflow engine used across all business modules
- PHPStan Level 8 compliance for maximum type safety
- Clean, maintainable codebase following strict conventions
Note: This is not a simple CRUD demo. It reflects production-grade patterns used in real enterprise systems.
All models follow strict conventions for consistency and maintainability:
Every model uses standardized traits for audit tracking:
CreatedByTrait- Tracks who created the record (created_by)UpdatedByTrait- Tracks who last updated the record (updated_by)DeletedByTrait- Tracks who soft-deleted the record (deleted_by)HasUlids- Uses ULIDs instead of auto-incrementing IDsSoftDeletes- Enables soft deletion for data retention
Models are automatically observed for audit field population:
#[ObservedBy([CreatedByObserver::class, UpdatedByObserver::class, DeletedByObserver::class])]
class YourModel extends Model
{
use CreatedByTrait, UpdatedByTrait, DeletedByTrait, HasUlids, SoftDeletes;
}All relationships use explicit generic type hints for PHPStan Level 8:
/**
* @return HasMany<PurchaseRequestComponent, $this>
*/
public function components(): HasMany
{
return $this->hasMany(PurchaseRequestComponent::class);
}
/**
* @return BelongsTo<User, $this>
*/
public function createdBy(): BelongsTo
{
return $this->belongsTo(User::class, 'created_by')->withTrashed();
}Models requiring approval workflows extend ApprovalAbstract:
use App\Abstracts\ApprovalAbstract;
class PurchaseRequest extends ApprovalAbstract
{
// Automatically includes approval event relationship
// Implements: approve(), reject(), cancel(), rollback(), force()
protected function onApprove(ApprovalEvent $approvalEvent): void
{
// Business logic executed when approval completes
}
}Controllers follow a strict, standardized pattern with NO FormRequest classes and inline validation.
Every controller implements these 7 base actions:
index()- List resources (paginated or collection)show()- Display single resourcestore()- Create new resourceupdate()- Update existing resource (approval models may not have this)delete()- Soft delete resourcerestore()- Restore soft-deleted resourcedestroy()- Permanently delete resource
Controllers for approval-enabled models add these actions:
approve()- Approve the workflow stepreject()- Reject the workflowcancel()- Cancel pending workflowrollback()- Rollback completed workflowforce()- Force execute specific step (admin override)
All validation is done inline using $request->validate():
public function store(Request $request): array
{
$request->validate([
'code' => ['required', 'string', 'max:255', new ValidationWithoutTrashed(Model::class, 'code')],
'name' => ['required', 'string', 'max:255'],
]);
// Business logic...
}Controllers return arrays or Models directly (Laravel auto-converts to JSON):
/**
* @return array{message: string, purchase_request: JsonResource}
*/
public function store(): array
{
return [
'message' => trans('messages.success.store'),
'purchase_request' => $purchaseRequest->toResource(),
];
}The approval system is a reusable, event-driven workflow engine that can be attached to any model.
- Model extends
ApprovalAbstract- Gains approval capabilities - User creates a request - Controller calls
$model->initEvent($user) - Approval flow is initiated - Based on configured approval dictionary
- Contributors approve/reject - Each step requires specific approvers
- Business logic executes - On final approval,
onApprove()is triggered
// User creates purchase request
$purchaseRequest->initEvent($user);
// Approvers sequentially approve
$purchaseRequest->approve($approver1);
$purchaseRequest->approve($approver2);
// On final approval, onApprove() executes
protected function onApprove(ApprovalEvent $approvalEvent): void
{
// Automatically create Purchase Procurement
$procurement = new PurchaseProcurement();
$procurement->purchase_request_id = $this->id;
$procurement->save();
// Transfer components to procurement
foreach ($this->components as $component) {
// Create procurement components...
}
}Models automatically filter by approval status using ApprovalAbstractScope:
withContributors()- Eager load approval contributorswithUsers()- Eager load audit users (created_by, updated_by, deleted_by)
Routes follow explicit definition pattern (NO Route::resource() or Route::apiResource()).
Route::prefix('purchase')->name('purchase.')->middleware(['auth:sanctum'])->group(function () {
Route::prefix('request')->name('request.')->middleware('can:purchase.request.index')->group(function () {
Route::get('index', [PurchaseRequestController::class, 'index'])->name('index');
Route::get('show/{id}', [PurchaseRequestController::class, 'show'])->name('show');
Route::post('store', [PurchaseRequestController::class, 'store'])->name('store');
Route::delete('delete/{id}', [PurchaseRequestController::class, 'delete'])->name('delete');
Route::post('restore/{id}', [PurchaseRequestController::class, 'restore'])->name('restore');
Route::delete('destroy/{id}', [PurchaseRequestController::class, 'destroy'])->name('destroy');
// Approval actions
Route::post('approve/{id}', [PurchaseRequestController::class, 'approve'])->name('approve');
Route::post('reject/{id}', [PurchaseRequestController::class, 'reject'])->name('reject');
Route::post('cancel/{id}', [PurchaseRequestController::class, 'cancel'])->name('cancel');
Route::post('rollback/{id}', [PurchaseRequestController::class, 'rollback'])->name('rollback');
Route::post('force/{id}', [PurchaseRequestController::class, 'force'])->name('force');
});
});Routes use dot notation: module.entity.action
- Example:
purchase.request.store - Example:
vendor.invoice.approve
-
Purchase Management
- Purchase Requests β Procurement β Orders β Invoices β Returns
- Full approval workflow integration
- Vendor management and payment tracking
-
Inventory Management
- Item master data with batch tracking
- Stock management (Good Receipts, Good Issues)
- Real-time stock history and adjustments
-
Vendor Management
- Vendor profiles and categorization
- Account payable tracking
- Invoice and payment processing
-
Transaction Management
- Payment request workflows
- Ledger management
- Financial transaction tracking
-
Sales Management
- Sales Orders β Invoices β Returns
- Customer relationship tracking
-
Approval Workflows
- Configurable multi-step approval flows
- Role/contributor-based approvals
- Event-driven state transitions
- Full audit trail per entity
- PHP: 8.4 or higher
- MySQL: 8.0 or higher
- Composer: 2.0 or higher
- Node.js: 18.0 or higher (for tooling)
git clone git@github.com:menma977/MyERP.git
cd MyERPcomposer installcp .env.example .env
php artisan key:generateUpdate your .env file:
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=myerp
DB_USERNAME=your_username
DB_PASSWORD=your_passwordphp artisan migrate --seedphp artisan ide-helper:generate
php artisan ide-helper:models --nowritephp artisan servecomposer run devThis runs both the server and queue worker concurrently.
composer test
# or
php artisan testphp artisan test --filter PurchaseRequestTestphp artisan test --coverage./vendor/bin/phpstan analyseAll code must pass PHPStan Level 8 for maximum type safety.
./vendor/bin/pintAutomatically formats code to PSR-12 standards.
graph LR
%% Purchasing & Inbound Flow
PurchaseRequest -->|Approved| PurchaseProcurement
PurchaseProcurement -->|Selected Vendor| PurchaseOrder
PurchaseOrder --> GoodReceipt
GoodReceipt --> Stock
GoodReceipt --> PurchaseInvoice
PurchaseInvoice --> PaymentRequest
PaymentRequest -->|Approved| Ledger
PurchaseOrder -->|Returns| PurchaseReturn
PurchaseReturn --> GoodIssue1[Good Issue]
%% Sales & Outbound Flow
SalesOrder --> SalesInvoice
SalesInvoice --> GoodIssue2[Good Issue]
GoodIssue2 --> Stock
SalesInvoice -->|Returns| SalesReturn
%% Inventory Management
Item --> ItemBatch
ItemBatch --> Stock
Stock --> StockHistory
%% Vendor Management
Vendor --> VendorInvoice
VendorInvoice --> VendorPayment
VendorPayment --> VendorAccountPayable
%% User & Permissions
User -->|created_by| PurchaseRequest
User -->|updated_by| PurchaseRequest
User -->|deleted_by| PurchaseRequest
Role --> User
Permission --> Role
- Table Names: snake_case, plural (e.g.,
purchase_requests) - Model Names: PascalCase, singular (e.g.,
PurchaseRequest) - Foreign Keys:
{model}_id(e.g.,purchase_request_id) - Audit Fields:
created_by,updated_by,deleted_by(all nullable integers) - Soft Deletes:
deleted_attimestamp on all tables - Primary Keys: ULIDs (string-based, sortable, globally unique)
- Framework: Laravel 12.0
- PHP: 8.4+
- Database: MySQL 8.0
- Authentication: Laravel Sanctum
- Permissions: Spatie Laravel Permission
- Image Processing: Intervention Image
- Static Analysis: PHPStan Level 8 (Larastan)
- Code Style: Laravel Pint (PSR-12)
- Testing: PHPUnit 11
- Debugging: Laradumps
- IDE Support: Laravel IDE Helper
- API Docs: Scramble (auto-generated OpenAPI docs)
- β Core ERP modules (Purchase, Inventory, Sales, Vendors)
- β Approval workflow engine
- β PHPStan Level 8 compliance
- β Comprehensive test coverage
- Multi-Tenant Support (future goal, not yet implemented)
- Advanced reporting and analytics
- Real-time notifications
- Mobile API optimization
- Advanced caching strategies
- Queue system for heavy operations
- Performance monitoring and logging
This project is open-sourced software licensed under the MIT license.
- Fork the repository
- Create a feature branch
- Make your changes following project conventions
- Run PHPStan and Pint
- Write/update tests
- Submit a pull request
- Follow project conventions (see user rules/guidelines)
- No
Route::resource()orRoute::apiResource()- use explicit routes - No FormRequest classes - use inline validation
- PHPStan Level 8 must pass - strict type safety required
- All relationships need generic type hints
- All models must use standard traits and observers
Built with discipline and precision. MyERP - Enterprise-grade Laravel architecture.