Core Module

Authentication & Authorization

Home | SoftLixx Creates
Core Module Authentication

Core Module

The Core module provides foundational entities and authentication features required across the ERP. It includes authentication (Laravel Sanctum), authorization (Spatie Roles & Permissions), and Branch management.

ER Diagram

Module Relationship Diagram

                        erDiagram
                            USERS ||--o{ USER_BRANCHES : "can_access"
                            BRANCHES ||--o{ USER_BRANCHES : "assigned_to"
                            BRANCHES ||--o{ SHOWROOM : "has"

                            USERS {
                                bigint id PK
                                string name
                                string email
                                string password
                                string username
                            }

                            DESIGNATIONS {
                                bigint id PK
                                string designation_code "e.g., GM, SE"
                                string name "e.g., General Manager, Sales Executive"
                                text description
                                boolean is_active
                            }

                            COMPANY {
                                int id PK
                                string company_name
                                string company_logo_path
                                string address
                                string city
                                string country
                                string phone_number
                                string email
                                string website
                                boolean is_active
                            }


                            BRANCHES {
                                bigint id PK
                                string branch_name
                                string branch_code "e.g., NY01, LA02"
                                string address
                                string city
                                string phone_number
                            }

                            SHOWROOM {
                                bigint id PK
                                int branch_id FK
                                string showroom_name
                                string showroom_code "e.g., SR01, SR02"
                                string location
                                string contact_number
                                boolean is_active
                            }

                            USER_BRANCHES {
                                bigint user_id FK
                                bigint branch_id FK
                                bool is_default
                            }

                            COMPANIES {
                                int id PK
                                string company_name
                                string company_code
                                boolean is_active
                            }
                            COUNTRIES {
                                int id PK
                                string country_name
                                string iso_code
                                boolean is_active
                            }
                            REGIONS {
                                int id PK
                                string region_name
                                int country_id FK
                                boolean is_active
                            }

                            COLORS {
                                int id PK
                                string color_name
                                string hex_value
                                boolean is_active
                            }

                            TAXES {
                                int id PK
                                string tax_name
                                decimal tax_rate "e.g., 0.15 for 15%"
                                boolean is_active
                            } 


                            MODULES {
                                int id PK
                                string name "Purchase, Sales, HR, etc." 
                            }

                            APPROVAL_SETUPS {
                                int id PK
                                int module_id FK
                                string operation_name "e.g., Purchase Order, Invoice"
                                string model "Eloquent model class e.g., App\\Models\\PurchaseOrder"
                                bool is_active
                            }

                            APPROVAL_SETUP_STEPS {
                                int id PK
                                int approval_setup_id FK
                                int approver_id FK
                                int step_order
                                enum approval_type "approve, sign defines whether the step requires simple approval or digital signature"
                            }

                            APPROVAL_REQUESTS {
                                int id PK
                                int approval_setup_id FK
                                int record_id "ID of the business record (e.g., PO id)"
                                string status "pending, approved, rejected"
                                datetime created_at
                            }

                            APPROVAL_REQUEST_STEPS {
                                int id PK
                                int approval_request_id FK
                                int approver_id FK
                                int step_order
                                enum approval_type "approve, sign"
                                string status "pending, approved, rejected"
                                string signature_path "optional, for signed steps"
                                string reason "optional, for rejection reason"
                                datetime acted_at
                            }
                            MODULES ||--o{ APPROVAL_SETUPS : "has"
                            APPROVAL_SETUPS ||--o{ APPROVAL_SETUP_STEPS : "defines"
                            APPROVAL_SETUPS ||--o{ APPROVAL_REQUESTS : "triggers"
                            APPROVAL_REQUESTS ||--o{ APPROVAL_REQUEST_STEPS : "tracks"
                            USERS ||--o{ APPROVAL_SETUP_STEPS : "can be approver"
                            USERS ||--o{ APPROVAL_REQUEST_STEPS : "acts on"

                    
Implementation

Approval Engine Service

                

                        namespace App\Services;
    
                        use App\Models\{
                            Module,
                            ApprovalSetup,
                            ApprovalSetupStep,
                            ApprovalRequest,
                            ApprovalRequestStep,
                            User
                        };
    
                        use Illuminate\Support\Facades\DB;
    
                        class ApprovalEngine
                        {
                            // ==========================
                            // 1. Setup Management
                            // ==========================
                            public function createSetup(int $moduleId, string $operationName, string $modelClass, array $approvers)
                            {
                                return DB::transaction(function () use ($moduleId, $operationName, $modelClass, $approvers) {
                                    $setup = ApprovalSetup::create([
                                        'module_id'      => $moduleId,
                                        'operation_name' => $operationName,
                                        'model'          => $modelClass,
                                        'is_active'      => true,
                                    ]);
    
                                    foreach ($approvers as $index => $approverId) {
                                        ApprovalSetupStep::create([
                                            'approval_setup_id' => $setup->id,
                                            'approver_id'       => $approverId,
                                            'step_order'        => $index + 1,
                                            'approval_type'     => 'approve', // or 'sign' based on requirements
                                        ]);
                                    }
    
                                    return $setup->load('steps');
                                });
                            }
    
                            // ==========================
                            // 2. Create Approval Request
                            // ==========================
                            public function createRequest(int $approvalSetupId, int $recordId) {
                                return DB::transaction(function () use ($setupId, $recordId) {
                                    // Create approval request
                                    $request = ApprovalRequest::create([
                                        'approval_setup_id' => $setupId,
                                        'record_id' => $recordId,
                                        'status' => 'pending',
                                        'created_at' => now(),
                                    ]);

                                    // Fetch setup steps
                                    $setupSteps = \App\Models\ApprovalSetupStep::where('approval_setup_id', $setupId)
                                        ->orderBy('step_order')
                                        ->get();

                                    // Create request steps
                                    foreach ($setupSteps as $step) {
                                        ApprovalRequestStep::create([
                                            'approval_request_id' => $request->id,
                                            'approver_id' => $step->approver_id,
                                            'step_order' => $step->step_order,
                                            'approval_type' => $step->approval_type,
                                            'status' => 'pending',
                                        ]);
                                    }

                                    return $request;
                                });
                            }
    
                        /**
                        * Act on the next pending step of the approval request.
                        * Automatically moves sequentially to the next step.
                        *
                        * @param int $requestId ID of ApprovalRequest
                        * @param int $userId ID of the user acting
                        * @param string|null $signatureBase64 Signature (if required)
                        */
                        public function actOnRequest(int $requestId, int $userId, ?string $signatureBase64 = null): ApprovalRequest
                        {
                            return DB::transaction(function () use ($requestId, $userId, $signatureBase64) {
                                $request = ApprovalRequest::with('steps')->findOrFail($requestId);

                                if ($request->status !== 'pending') {
                                    throw new \Exception("Request already finalized.");
                                }

                                // Find the current pending step for this user
                                $currentStep = $request->steps()
                                    ->where('status', 'pending')
                                    ->orderBy('step_order')
                                    ->first();

                                if (!$currentStep) {
                                    throw new \Exception("No pending step found.");
                                }

                                if ($currentStep->approver_id !== $userId) {
                                    throw new \Exception("You are not authorized to approve this step.");
                                }

                                // If step requires signature
                                if ($currentStep->approval_type === 'sign') {
                                    if (!$signatureBase64) {
                                        throw new \Exception("Signature is required for this step.");
                                    }

                                    // Save signature as image
                                    $signaturePath = "uploads/signatures/step_{$currentStep->id}_" . time() . ".png";
                                    file_put_contents(public_path($signaturePath), base64_decode($signatureBase64));
                                    $currentStep->signature_path = $signaturePath;
                                }

                                // Mark step as approved
                                $currentStep->status = 'approved';
                                $currentStep->acted_at = now();
                                $currentStep->save();

                                // Check if there is a next step
                                $nextStep = $request->steps()
                                    ->where('status', 'pending')
                                    ->orderBy('step_order')
                                    ->first();

                                // If no more steps, finalize request
                                if (!$nextStep) {
                                    $request->status = 'approved';
                                    $request->save();

                                    // TODO: Optionally notify record owner or update main record status
                                }

                                return $request;
                            });
                        }

                        /**
                        * Reject the approval request at current step
                        */
                        public function rejectRequest(int $requestId, int $userId, ?string $reason = null): ApprovalRequest
                        {
                            return DB::transaction(function () use ($requestId, $userId, $reason) {
                                $request = ApprovalRequest::with('steps')->findOrFail($requestId);

                                if ($request->status !== 'pending') {
                                    throw new \Exception("Request already finalized.");
                                }

                                // Find the current pending step for this user
                                $currentStep = $request->steps()
                                    ->where('status', 'pending')
                                    ->orderBy('step_order')
                                    ->first();

                                if (!$currentStep || $currentStep->approver_id !== $userId) {
                                    throw new \Exception("You are not authorized to reject this step.");
                                }

                                $currentStep->status = 'rejected';
                                $currentStep->reason = $reason;
                                $currentStep->acted_at = now();
                                $currentStep->save();

                                $request->status = 'rejected';
                                $request->save();

                                // TODO: Optionally notify record owner
                                return $request;
                            });
                        }


    
                            // ==========================
                            // 4. Notification (placeholder)
                            // ==========================
                            protected function notifyApprover(ApprovalRequest $request)
                            {
                                $nextStep = $request->steps()
                                    ->where('status', 'pending')
                                    ->sortBy('step_order')
                                    ->first();
    
                                if ($nextStep) {
                                    $user = User::find($nextStep->approver_id);
                                    // For real app, trigger notification (broadcast, email, in-app)
                                    // Here just log
                                    info("Approval request #{$request->id} pending for user {$user->name}");
                                }
                            }
                        }
    
                
                    
Usage Example

Usage Example

                    
                        $engine = new \App\Services\ApprovalEngine();

                        // 1. Create a request for a PO
                        $request = $engine->createRequest($setupId = 1, $recordId = 123);

                        $currentStep = $request->steps()
                            ->where('status', 'pending')
                            ->orderBy('step_order')
                            ->first();

                        if ($currentStep->approval_type === 'sign') {
                            // 2. User acts on the request
                            $updatedRequest = $engine->actOnRequest(
                                $request->id,
                                $userId = 2,
                                $signatureBase64 = $signatureData // only if step type is 'sign'
                            );
                        } else {
                            // Render simple approve button
                            $updatedRequest = $engine->actOnRequest(
                                $request->id,
                                $userId = 2
                            );
                        }

                        // 2. User acts on the request

                        // 3. Reject example
                        $rejectedRequest = $engine->rejectRequest($request->id, $userId = 2, "Incorrect amount");

                        po->status = $updatedRequest->status; // 'approved' or 'rejected'
                        $po->save();
                    
                    
Related Documentation

Table Documentation Links