Digital - Development Guide

Generated: 2025-10-16 Framework: Laravel 10.x PHP Version: 8.2+ Environment: Local Development


Table of Contents

  1. System Requirements
  2. Initial Setup
  3. Environment Configuration
  4. Database Setup
  5. Common Commands
  6. Development Workflow
  7. Development Standards & Best Practices
  8. Testing
  9. Debugging
  10. Common Development Tasks
  11. Troubleshooting

System Requirements

Required Software

Software Version Purpose
PHP 8.2+ Backend runtime
Composer 2.x PHP dependency management
MySQL 5.7+ or 8.0+ Primary database
Redis Latest Cache & queue backend
Node.js 16+ Frontend asset compilation
npm 8+ JavaScript dependency management

Optional Software

Software Purpose
Meilisearch Full-text search engine
Docker Containerized development environment
Postman API testing
MySQL Workbench Database management GUI

PHP Extensions (Required)

Ensure the following PHP extensions are installed:

php -m | grep -E '(bcmath|ctype|fileinfo|json|mbstring|openssl|pdo|tokenizer|xml|gd|curl|zip)'

Required extensions: - bcmath - Arbitrary precision mathematics - ctype - Character type checking - fileinfo - File information - json - JSON support - mbstring - Multi-byte string handling - openssl - Cryptographic functions - pdo - Database abstraction - pdo_mysql - MySQL driver for PDO - tokenizer - Token parsing - xml - XML processing - gd or imagick - Image manipulation - curl - HTTP client - zip - ZIP archive handling


Initial Setup

1. Clone Repository

git clone <repository-url> digital2
cd digital2

2. Install PHP Dependencies

composer install

Note: If you encounter memory issues: bash COMPOSER_MEMORY_LIMIT=-1 composer install

3. Install JavaScript Dependencies

npm install

4. Copy Environment File

cp .env.example .env

5. Generate Application Key

php artisan key:generate

This sets the APP_KEY in your .env file, which is used for encryption.

6. Configure Environment Variables

Edit .env file with your local configuration (see Environment Configuration section).

7. Run Migrations

php artisan migrate

With seeders (optional): bash php artisan migrate --seed

8. Create Storage Symlink

php artisan storage:link

This creates a symbolic link from public/storage to storage/app/public.

9. Start Development Server

php artisan serve

API will be available at: http://localhost:8000


Environment Configuration

Core Application Settings

APP_NAME=Digital
APP_ENV=local
APP_KEY=                          # Auto-generated by key:generate
APP_DEBUG=true                    # Enable detailed error messages
APP_URL=http://localhost:8000     # Base URL for the application

Production Settings: - Set APP_ENV=production - Set APP_DEBUG=false - Use HTTPS in APP_URL


Database Configuration

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=digital_db            # Your database name
DB_USERNAME=root                   # Your database user
DB_PASSWORD=                       # Your database password

For Docker/Local MySQL: env DB_HOST=mysql DB_PORT=3306


Redis Configuration

REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379

Cache & Queue with Redis: env CACHE_DRIVER=redis QUEUE_CONNECTION=redis SESSION_DRIVER=redis

Without Redis (development): env CACHE_DRIVER=file QUEUE_CONNECTION=database SESSION_DRIVER=file


AWS S3 Configuration

For cloud file storage:

AWS_ACCESS_KEY_ID=your_access_key
AWS_SECRET_ACCESS_KEY=your_secret_key
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=your_bucket_name

Local Development (skip S3): env FILESYSTEM_DRIVER=local


Email Configuration

Development (Mailtrap): env MAIL_MAILER=smtp MAIL_HOST=sandbox.smtp.mailtrap.io MAIL_PORT=2525 MAIL_USERNAME=your_mailtrap_username MAIL_PASSWORD=your_mailtrap_password MAIL_ENCRYPTION=null MAIL_FROM_ADDRESS="digital@tbf.ro" MAIL_FROM_NAME="${APP_NAME}"

Production (AWS SES): env MAIL_MAILER=ses MAIL_HOST=email-smtp.eu-central-1.amazonaws.com MAIL_PORT=587 MAIL_USERNAME=your_ses_username MAIL_PASSWORD=your_ses_password SES_CONFIGURATION_SET=my-first-configuration-set


Meilisearch Configuration

For full-text search:

SCOUT_DRIVER=meilisearch
MEILISEARCH_HOST=http://127.0.0.1:7700
MEILISEARCH_KEY=your_master_key

Install Meilisearch: ```bash

macOS

brew install meilisearch

Linux

curl -L https://install.meilisearch.com | sh

Docker

docker run -d -p 7700:7700 getmeili/meilisearch:latest ```

Import existing data to Meilisearch: bash php artisan scout:import "App\Models\User" php artisan scout:import "App\Models\Deal" php artisan scout:import "App\Models\Task"


OpenAI Configuration

For AI features:

OPENAI_API_KEY=sk-...
OPENAI_ORGANIZATION=org-...

WebSocket Configuration

For real-time notifications:

WS_HOST=tcp://127.0.0.1
WS_PORT_NOTIFICATION=9080
WS_PORT_PROJECT=9090
WS_PORT_MEETING=9070
WS_SECRET=your_random_secret_key

Start WebSocket Server: bash php artisan websockets:serve


Firebase Configuration

For push notifications:

FIREBASE_SERVER_KEY=your_firebase_server_key

Stripe Configuration

For payment processing:

STRIPE_API_KEY=sk_test_...
STRIPE_API_PUBLISHEABLE_KEY=pk_test_...

Frontend URL

Set the frontend application URL:

FE_URL="http://localhost:3000"    # Local VueJS dev server

Database Setup

Create Database

MySQL Command Line: sql CREATE DATABASE digital_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

MySQL Workbench: 1. Right-click “Schemas” → “Create Schema” 2. Name: digital_db 3. Charset: utf8mb4 4. Collation: utf8mb4_unicode_ci

Run Migrations

Fresh Migration (drops all tables): bash php artisan migrate:fresh

Fresh with Seeders: bash php artisan migrate:fresh --seed

Rollback Last Migration: bash php artisan migrate:rollback

Rollback All Migrations: bash php artisan migrate:reset

Check Migration Status: bash php artisan migrate:status

Seeders

Run All Seeders: bash php artisan db:seed

Run Specific Seeder: bash php artisan db:seed --class=UserSeeder

Database Backup

Export Database: bash mysqldump -u root -p digital_db > backup_$(date +%Y%m%d).sql

Import Database: bash mysql -u root -p digital_db < backup_20251016.sql


Common Commands

Artisan Commands

List All Commands: bash php artisan list

Get Help for a Command: bash php artisan help migrate


Development Server

Start Server: bash php artisan serve

Custom Host/Port: bash php artisan serve --host=0.0.0.0 --port=8080


Queue Management

Run Queue Worker: bash php artisan queue:work

Run Queue Worker (auto-reload on code changes): bash php artisan queue:listen

Process Single Job: bash php artisan queue:work --once

Clear Failed Jobs: bash php artisan queue:flush

Retry Failed Jobs: bash php artisan queue:retry all

Monitor Queue (Laravel Horizon - if installed): bash php artisan horizon


Cache Management

Clear All Caches: bash php artisan cache:clear php artisan config:clear php artisan route:clear php artisan view:clear

Cache Configuration: bash php artisan config:cache

Cache Routes: bash php artisan route:cache

Optimize Application: bash php artisan optimize

Clear Optimization: bash php artisan optimize:clear


Scheduled Tasks (Cron)

Run Scheduler Manually: bash php artisan schedule:run

List Scheduled Tasks: bash php artisan schedule:list

Production Cron Entry: cron * * * * * cd /path-to-project && php artisan schedule:run >> /dev/null 2>&1


Asset Compilation (Laravel Mix)

Development Build: bash npm run dev

Watch for Changes: bash npm run watch

Watch with Polling: bash npm run watch-poll

Hot Module Replacement: bash npm run hot

Production Build: bash npm run production


Model & Migration Generation

Create Model: bash php artisan make:model ModelName

Create Model with Migration: bash php artisan make:model ModelName -m

Create Model with Migration, Factory, and Seeder: bash php artisan make:model ModelName -mfs

Create Migration: bash php artisan make:migration create_table_name_table

Create Controller: bash php artisan make:controller Api/ControllerName

Create API Resource Controller: bash php artisan make:controller Api/ControllerName --api

Create Form Request: bash php artisan make:request RequestName


Testing Commands

Run All Tests: bash php artisan test

Run Specific Test File: bash php artisan test tests/Feature/UserTest.php

Run Tests with Coverage: bash php artisan test --coverage


Development Workflow

Typical Development Day

  1. Pull Latest Changes bash git pull origin main composer install npm install php artisan migrate

  2. Start Services ```bash

    Terminal 1: Development server

    php artisan serve

    Terminal 2: Queue worker

    php artisan queue:work

    Terminal 3: Asset watcher

    npm run watch

    Terminal 4 (optional): WebSocket server

    php artisan websockets:serve ```

  3. Develop Features

  4. Before Pushing ```bash

    Run tests

    php artisan test

    Clear caches

    php artisan cache:clear

    Check code style

    ./vendor/bin/phpcs

    Push changes

    git push origin feature/new-feature ```


Creating a New API Endpoint

Example: Create “Report” endpoint

  1. Create Model & Migration bash php artisan make:model Report -m

  2. Define Migration (database/migrations/xxxx_create_reports_table.php) php Schema::create('reports', function (Blueprint $table) { $table->id(); $table->foreignId('instance_id')->constrained(); $table->string('title'); $table->text('description'); $table->foreignId('user_id')->constrained(); $table->timestamps(); $table->softDeletes(); });

  3. Run Migration bash php artisan migrate

  4. Define Model (app/Models/Report.php) ```php <?php

    namespace App\Models;

    use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletes; use Auth;

    class Report extends Model { use SoftDeletes;

    protected $fillable = ['instance_id', 'title', 'description', 'user_id'];
    
    /**
     * Boot method to auto-set instance_id
     */
    public static function boot()
    {
        parent::boot();
    
        self::creating(function(Report $report){
            $authUser = Auth::user();
            if ($authUser && !$report->instance_id) {
                $report->instance_id = $authUser->instance_id;
            }
        });
    }
    
    /**
     * Relationship: Report belongs to User
     */
    public function user()
    {
        return $this->belongsTo(User::class);
    }
    

    } ```

  5. Create Controller bash php artisan make:controller Api/ReportController --api

  6. Define Controller (app/Http/Controllers/Api/ReportController.php) ```php <?php

    namespace App\Http\Controllers\Api;

    use App\Http\Controllers\Controller; use App\Models\Report; use Illuminate\Http\Request;

    class ReportController extends Controller { /**

     * Display a listing of the resource.
     */
    public function index()
    {
        $reports = Report::where('instance_id', auth()->user()->instance_id)
            ->with('user')
            ->paginate(15);
    
        return response()->json([
            'success' => true,
            'data' => $reports
        ]);
    }
    
    /**
     * Store a newly created resource in storage.
     */
    public function store(Request $request)
    {
        $validated = $request->validate([
            'title' => 'required|string|max:255',
            'description' => 'required|string',
        ]);
    
        $report = Report::create($validated);
    
        return response()->json([
            'success' => true,
            'data' => $report,
            'message' => 'Report created successfully'
        ], 201);
    }
    
    /**
     * Display the specified resource.
     */
    public function show($id)
    {
        $report = Report::where('instance_id', auth()->user()->instance_id)
            ->findOrFail($id);
    
        return response()->json([
            'success' => true,
            'data' => $report
        ]);
    }
    
    /**
     * Update the specified resource in storage.
     */
    public function update(Request $request, $id)
    {
        $report = Report::where('instance_id', auth()->user()->instance_id)
            ->findOrFail($id);
    
        $validated = $request->validate([
            'title' => 'sometimes|required|string|max:255',
            'description' => 'sometimes|required|string',
        ]);
    
        $report->update($validated);
    
        return response()->json([
            'success' => true,
            'data' => $report,
            'message' => 'Report updated successfully'
        ]);
    }
    
    /**
     * Remove the specified resource from storage.
     */
    public function destroy($id)
    {
        $report = Report::where('instance_id', auth()->user()->instance_id)
            ->findOrFail($id);
    
        $report->delete();
    
        return response()->json([
            'success' => true,
            'message' => 'Report deleted successfully'
        ]);
    }
    

    } ```

  7. Add Routes (routes/api.php) php Route::middleware(['auth:sanctum'])->group(function () { Route::apiResource('reports', ReportController::class); });

  8. Test Endpoint ```bash

    GET /api/reports

    curl -H “Authorization: Bearer {token}” http://localhost:8000/api/reports

    POST /api/reports

    curl -X POST -H “Authorization: Bearer {token}” \ -H “Content-Type: application/json” \ -d ‘{“title”:“Test Report”,“description”:“Description”}’ \ http://localhost:8000/api/reports ```


Development Standards & Best Practices

🎯 Purpose: These standards ensure consistency, maintainability, and best practices across the Digital codebase.

📌 Mandatory: All developers must follow these guidelines when contributing to the project.


1. Controllers

Rule 1.1: Use Route Model Binding

✓ Use Model Binding when ID is passed via URL

// ✅ CORRECT - Use Model Binding
public function show(User $user)
{
    return new UserResource($user);
}

// ❌ INCORRECT - Manual ID lookup
public function show($id)
{
    $user = User::findOrFail($id);
    return new UserResource($user);
}

Benefits: - Automatic 404 handling - Cleaner code - Type safety


Rule 1.2: Instance-Scoped Models Must Use Instance Binding

For models with instance_id field, the index() method must: - Accept Instance $instance parameter - Fetch data through the instance relationship

// ✅ CORRECT - Instance-scoped query
public function index(Instance $instance)
{
    $users = $instance->users()
        ->with('department', 'role')
        ->paginate(15);

    return UserResourceCollection::make($users);
}

// ❌ INCORRECT - Direct query without instance binding
public function index()
{
    $users = User::where('instance_id', auth()->user()->instance_id)
        ->paginate(15);

    return UserResourceCollection::make($users);
}

Rule 1.3: Always Use Resources for API Responses

If endpoint returns database data (more than just an ID), use Resource or ResourceCollection

// ✅ CORRECT - Using Resource
public function show(User $user)
{
    return new UserResource($user);
}

public function index(Instance $instance)
{
    $users = $instance->users()->paginate(15);
    return UserResourceCollection::make($users);
}

// ❌ INCORRECT - Returning raw model data
public function show(User $user)
{
    return response()->json(['data' => $user]);
}

See: API Contracts - Resources


Rule 1.4: Filter Data for Forms

Dropdown/select data must be returned by: 1. InstanceController::filters() method (preferred) 2. Or a dedicated filters() method in the specific controller

// ✅ CORRECT - Filters method
public function filters(Instance $instance)
{
    return response()->json([
        'success' => true,
        'data' => [
            'departments' => $instance->departments()
                ->select('id', 'name')
                ->get(),
            'roles' => $instance->roles()
                ->select('id', 'name')
                ->get(),
            'statuses' => UserStatusEnum::options(),
        ]
    ]);
}

Usage in frontend: javascript // Fetch filters before showing form const filters = await axios.get('/api/instances/{id}/users/filters'); // Use filters.data.departments for dropdown


Rule 1.5: Paginated Responses Must Include total_results

Requests with pagination or view_more must include total_results field

// ✅ CORRECT - Include total_results
public function index(Instance $instance, Request $request)
{
    $perPage = $request->input('per_page', 15);
    $users = $instance->users()->paginate($perPage);

    return response()->json([
        'success' => true,
        'data' => UserResource::collection($users->items()),
        'total_results' => $users->total(),  // ← Required
    ]);
}

2. Requests & Validation

Rule 2.1: Use FormRequest for 3+ Validation Rules

If more than 2-3 validation rules exist, create a FormRequest class

// ✅ CORRECT - FormRequest for complex validation
php artisan make:request UserStoreRequest

// app/Http/Requests/UserStoreRequest.php
class UserStoreRequest extends FormRequest
{
    public function authorize()
    {
        return true;
    }

    public function rules()
    {
        return [
            'first_name' => 'required|string|max:255',
            'last_name' => 'required|string|max:255',
            'email' => 'required|email|unique:users,email',
            'department_id' => 'required|exists:departments,id',
            'role_id' => 'required|exists:roles,id',
        ];
    }
}

// Controller
public function store(UserStoreRequest $request)
{
    $user = User::create($request->validated());
    return new UserResource($user);
}

Naming convention: {Model}{Action}Request - UserStoreRequest - ProductUpdateRequest - DealDeleteRequest


Rule 2.2: NEVER Use $request->all()

Always use $request->validated() to get validated data only

// ✅ CORRECT - Use validated data
public function store(UserStoreRequest $request)
{
    $validated = $request->validated();
    $user = User::create($validated);
    return new UserResource($user);
}

// ❌ INCORRECT - Using all() exposes security risk
public function store(Request $request)
{
    $user = User::create($request->all());  // ← Mass assignment vulnerability!
    return new UserResource($user);
}

Why? $request->all() includes ALL request data, including potentially malicious fields like is_admin, instance_id, etc.


Rule 2.3: Custom Validation Messages Only When Needed

Add custom messages only if default Laravel messages are insufficient

// ✅ CORRECT - Use defaults when possible
public function rules()
{
    return [
        'email' => 'required|email',  // Default message is fine
    ];
}

// ✅ CORRECT - Custom message when needed
public function rules()
{
    return [
        'vat_number' => 'required|regex:/^[A-Z]{2}[0-9]{8,10}$/',
    ];
}

public function messages()
{
    return [
        'vat_number.regex' => 'VAT number must follow EU format (e.g., RO12345678)',
    ];
}

3. Models

Rule 3.1: All Models Must Use SoftDeletes

Every model must implement SoftDeletes trait

// ✅ CORRECT - All models use SoftDeletes
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;

class User extends Model
{
    use SoftDeletes;  // ← Required

    protected $fillable = ['first_name', 'last_name', 'email'];
}

Migration: php Schema::create('users', function (Blueprint $table) { $table->id(); // ... other fields $table->timestamps(); $table->softDeletes(); // ← Adds deleted_at column });


Rule 3.2: Auto-Set instance_id in boot() Method

All models must auto-set instance_id in the boot() method

// ✅ CORRECT - Auto-set instance_id
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Auth;

class Task extends Model
{
    protected $fillable = ['title', 'description', 'instance_id'];

    /**
     * Boot method to auto-set instance_id on creation
     *
     * @return void
     */
    public static function boot()
    {
        parent::boot();

        self::creating(function ($model) {
            $authUser = Auth::user();
            if ($authUser && !$model->instance_id) {
                $model->instance_id = $authUser->instance_id;
            }
        });
    }
}

Why? This ensures multi-tenancy is enforced automatically.


Rule 3.3: Relationship Names in snake_case

All Eloquent relationships must use snake_case naming

// ✅ CORRECT - snake_case relationships
public function partner_address()
{
    return $this->hasOne(PartnerAddress::class);
}

public function product_type()
{
    return $this->belongsTo(ProductType::class);
}

public function user_files()
{
    return $this->hasMany(UserFile::class);
}

// ❌ INCORRECT - camelCase
public function partnerAddress()  // ← Wrong
{
    return $this->hasOne(PartnerAddress::class);
}

Usage: php $user->partner_address; // ✅ Correct $user->partnerAddress; // ❌ Wrong


Rule 3.4: Nomenclature Values in English

All values stored in database (nomenclatures, enums, statuses) must be in English

// ✅ CORRECT - English values in DB
DB::table('vacation_types')->insert([
    'name' => 'Annual Leave',       // ← English
    'code' => 'ANNUAL',
]);

// ❌ INCORRECT - Romanian in database
DB::table('vacation_types')->insert([
    'name' => 'Concediu de odihnă',  // ← Wrong
]);

See: Rule 6: Translations


4. Resources & API Responses

Rule 4.1: Use ResourceCollection for Lists

Collections/lists must always use ResourceCollection

// ✅ CORRECT - ResourceCollection for lists
public function index(Instance $instance)
{
    $users = $instance->users()->paginate(15);
    return UserResourceCollection::make($users);
}

// Or with collection helper
public function index(Instance $instance)
{
    $users = $instance->users()->get();
    return UserResource::collection($users);
}

// ❌ INCORRECT - Returning raw array
public function index()
{
    $users = User::all();
    return response()->json(['data' => $users]);  // ← Missing transformation
}

Rule 4.2: Use Resource for Individual Objects

Single objects must use Resource classes

// ✅ CORRECT - Resource for single object
public function show(User $user)
{
    return new UserResource($user);
}

public function store(UserStoreRequest $request)
{
    $user = User::create($request->validated());
    return new UserResource($user);
}

Example Resource: php // app/Http/Resources/UserResource.php class UserResource extends JsonResource { public function toArray($request) { return [ 'id' => $this->id, 'first_name' => $this->first_name, 'last_name' => $this->last_name, 'full_name' => $this->first_name . ' ' . $this->last_name, 'email' => $this->email, 'department' => new DepartmentResource($this->whenLoaded('department')), 'role' => new RoleResource($this->whenLoaded('role')), 'created_at' => $this->created_at ]; } }


Rule 4.3: No Unnecessary Logs in Production

Do not add unnecessary log statements (error, warning, info) in production code

// ❌ INCORRECT - Unnecessary logging
public function index(Instance $instance)
{
    Log::info('Fetching users');  // ← Remove
    $users = $instance->users()->get();
    Log::info('Users fetched: ' . $users->count());  // ← Remove
    return UserResource::collection($users);
}

// ✅ CORRECT - Only log exceptions or critical events
public function store(UserStoreRequest $request)
{
    try {
        $user = User::create($request->validated());
        return new UserResource($user);
    } catch (\Exception $e) {
        Log::error('Failed to create user', [
            'error' => $e->getMessage(),
            'request' => $request->validated(),
        ]);
        throw $e;
    }
}

When to log: - ✅ Exceptions and errors - ✅ Critical business events (payment processed, etc.) - ✅ Security events (unauthorized access) - ❌ Routine operations (fetching data, saving records)


5. Authorization & Middleware

Rule 5.1: Use Middleware for Authorization

Authorization should be done at middleware level, not manually in methods

// ✅ CORRECT - Middleware authorization
Route::middleware(['auth:sanctum', 'check.right:ADMIN_HR'])
    ->group(function () {
        Route::apiResource('users', UserController::class);
    });

// ❌ INCORRECT - Manual checks in every method
public function index()
{
    if (!auth()->user()->hasRight(RightEnum::ADMIN_HR)) {
        return response()->json(['error' => 'Unauthorized'], 403);
    }
    // ... rest of code
}

Rule 5.2: Rights Managed via OrganigramModulePolicy

List of rights that can be managed through middleware is defined in: - app/Policies/OrganigramModulePolicy.php

Example middleware usage: php // routes/api.php Route::middleware(['auth:sanctum', 'check.right:ADMIN_CRM']) ->prefix('deals') ->group(function () { Route::get('/', [DealController::class, 'index']); Route::post('/', [DealController::class, 'store']); });

Available rights (examples): - ADMIN_OBJECTIVE, - MANAGER_OBJECTIVE, - EMPLOYEE_OBJECTIVE, - ADMIN_PROCEDURE, - AUDITOR_PROCEDURE, - EMPLOYEE_PROCEDURE, - ADMIN_ORGANIGRAM, - EMPLOYEE_ORGANIGRAM, - ADMIN_CRM, - EMPLOYEE_CRM,

See: app/Enums/RightEnum.php for complete list


6. Translations & Texts

Rule 6.1: All Texts Must Have Translations

All user-facing text must have translations in all supported languages

Supported languages: - 🇬🇧 English (en) - 🇷🇴 Romanian (ro) - 🇩🇪 German (de) - 🇪🇸 Spanish (es) - 🇮🇹 Italian (it)

// ✅ CORRECT - Translation keys
return response()->json([
    'message' => __('messages.user_created_successfully'),
]);

// resources/lang/en/messages.php
return [
    'user_created_successfully' => 'User created successfully',
];

// resources/lang/ro/messages.php
return [
    'user_created_successfully' => 'Utilizator creat cu succes',
];

Rule 6.2: Database Values in English Only

Messages and nomenclature stored in database must be in English

// ✅ CORRECT - English in database, translations separate
DB::table('vacation_types')->insert([
    'name' => 'Sick Leave',  // ← English in DB
    'code' => 'SICK',
]);

// Frontend displays translated version:
__('vacation_types.sick_leave')  // Returns "Concediu medical" in Romanian

// ❌ INCORRECT - Non-English in database
DB::table('vacation_types')->insert([
    'name' => 'Concediu medical',  // ← Wrong
]);

Rule 6.3: Error Messages Must Be Translated

Validation errors and exceptions must have translations

// ✅ CORRECT - Translated error messages
public function messages()
{
    return [
        'email.required' => __('validation.email_required'),
        'email.unique' => __('validation.email_already_exists'),
    ];
}

// ❌ INCORRECT - Hardcoded English messages
public function messages()
{
    return [
        'email.required' => 'Email is required',  // ← Not translated
    ];
}

Summary Checklist

Before submitting a pull request, verify:


Quick Reference

Category Rule Example
Controllers Model Binding show(User $user) not show($id)
Controllers Instance Binding index(Instance $instance)
Controllers Use Resources return new UserResource($user)
Requests Use validated() $request->validated() not $request->all()
Requests FormRequest Create for 3+ validation rules
Models SoftDeletes use SoftDeletes; in all models
Models Auto instance_id Set in boot() method
Models Relationships partner_address() not partnerAddress()
Resources Collections Use ResourceCollection::make()
Resources Single Objects Use new UserResource()
Authorization Middleware check.right:ADMIN_HR in routes
Translations Database English only in database
Translations UI Text Translate all 5 languages

📚 Related Documentation: - Architecture - Multi-Tenancy - Architecture - Authorization - API Contracts - Response Formats - Data Models - Model Patterns


Testing

Running Tests

All Tests: bash php artisan test

Specific Test Suite: bash php artisan test --testsuite=Feature php artisan test --testsuite=Unit

Specific Test File: bash php artisan test tests/Feature/UserTest.php

With Coverage: bash php artisan test --coverage


Writing Tests

Create Test: bash php artisan make:test UserTest php artisan make:test UserTest --unit

Example Feature Test (tests/Feature/UserTest.php): ```php <?php

namespace Tests\Feature;

use App\Models\User; use App\Models\Instance; use Illuminate\Foundation\Testing\RefreshDatabase; use Tests\TestCase;

class UserTest extends TestCase { use RefreshDatabase;

/**
 * Test user can be created.
 */
public function test_user_can_be_created()
{
    $instance = Instance::factory()->create();

    $user = User::factory()->create([
        'instance_id' => $instance->id,
        'first_name' => 'John',
        'last_name' => 'Doe',
        'email' => 'john@example.com',
    ]);

    $this->assertDatabaseHas('users', [
        'email' => 'john@example.com',
    ]);
}

/**
 * Test API endpoint returns users.
 */
public function test_api_returns_users_list()
{
    $instance = Instance::factory()->create();
    $user = User::factory()->create(['instance_id' => $instance->id]);

    $response = $this->actingAs($user, 'sanctum')
        ->getJson('/api/users');

    $response->assertStatus(200)
        ->assertJsonStructure([
            'success',
            'data' => [
                '*' => ['id', 'first_name', 'last_name', 'email']
            ]
        ]);
}

} ```


Debugging

Laravel Telescope (Recommended)

Install Telescope: bash composer require laravel/telescope php artisan telescope:install php artisan migrate

Access Dashboard: http://localhost:8000/telescope


Debug Bar

Install Debug Bar: bash composer require barryvdh/laravel-debugbar --dev

Disable in Production: env DEBUGBAR_ENABLED=false


Query Logging

Log All Queries: ```php // In AppServiceProvider boot() use Illuminate\Support\Facades\DB;

DB::listen(function($query) { \Log::info( $query->sql, $query->bindings, $query->time ); }); ```


API Request Debugging

Using dd() in Controllers: php public function index() { $data = User::all(); dd($data); // Dump and die }

Using ray() (Recommended): bash composer require spatie/laravel-ray

ray($user);  // Outputs to Ray app
ray()->showQueries();  // Log all queries

Common Development Tasks

Adding a New Permission

  1. Create Migration: bash php artisan make:migration add_new_right_to_rights_table

  2. Define Migration: php public function up() { DB::table('rights')->insert([ 'name' => 'ADMIN_REPORTS', 'description' => 'Manage reports module', 'module' => 'reports', ]); }

  3. Add to RightEnum: php // app/Enums/RightEnum.php class RightEnum { const ADMIN_REPORTS = 'ADMIN_REPORTS'; // ... other rights }

  4. Check Permission in Controller: php if (!auth()->user()->hasRight(RightEnum::ADMIN_REPORTS)) { return response()->json(['error' => 'Unauthorized'], 403); }


Adding Custom Fields to an Entity

Example: Add custom fields to Task model

  1. Add Trait to Model: ```php use App\Traits\HasCustomFields;

    class Task extends Model { use HasCustomFields; } ```

  2. Create Custom Field Definition (via API or seeder): php CustomField::create([ 'instance_id' => 1, 'name' => 'priority_score', 'field_type' => 'number', 'entity_type' => 'task', 'is_required' => false, ]);

  3. Set/Get Custom Field Values: php $task->setCustomFieldValue('priority_score', 95); $score = $task->getCustomFieldValue('priority_score');


Enabling Search for a Model

  1. Add Searchable Trait: ```php use Laravel\Scout\Searchable;

    class Task extends Model { use Searchable;

    /**
     * Get the indexable data array for the model.
     */
    public function toSearchableArray()
    {
        return [
            'model_id'  => $this->id,
            'model' => class_basename(static::class),
            'name'  => $this->title,
            'description' => $this->description,
            'access_ids' => [$this->user_id],  // Permission filtering
        ];
    }
    
    /**
     * Override search index name
     */
    public function searchableAs()
    {
        return 'global_index';
    }
    

    } ```

  2. Import Existing Data: bash php artisan scout:import "App\Models\Task"


Troubleshooting

Common Issues

Issue: “No application encryption key has been specified”

Solution: bash php artisan key:generate


Issue: “Class not found” errors

Solution: bash composer dump-autoload php artisan clear-compiled php artisan config:clear


Issue: “SQLSTATE[HY000] [2002] Connection refused”

Solution: - Check MySQL is running: mysql -u root -p - Verify .env database credentials - Check DB_HOST (use 127.0.0.1 instead of localhost)


Issue: “419 Page Expired” on API requests

Solution: - CSRF protection is disabled for API routes - Ensure using Authorization: Bearer {token} header - Verify token is valid: php artisan sanctum:purge-expired


Issue: Queue jobs not processing

Solution: ```bash

Restart queue worker

php artisan queue:restart

Run queue worker with verbose output

php artisan queue:work –verbose

Check failed jobs

php artisan queue:failed ```


Issue: Storage link broken after deployment

Solution: bash php artisan storage:link


Issue: Permission denied on storage directories

Solution: bash chmod -R 775 storage bootstrap/cache chown -R www-data:www-data storage bootstrap/cache


Issue: Meilisearch connection refused

Solution: ```bash

Start Meilisearch

brew services start meilisearch

Or Docker

docker start meilisearch

Verify running

curl http://localhost:7700/health ```


Related Documentation


External Resources


Document Generated: 2025-10-16 Last Updated: 2025-10-28 (Added Development Standards & Best Practices) Maintained By: Development Team