# FlowBuilder Module - Key Aspects Analyzed ## WhatsApp Integration Architecture ### Entry Point: AutoReplyService **File:** `app/Services/AutoReplyService.php` When a WhatsApp message is received: 1. **Webhook receives message** → Controller processes 2. **AutoReplyService::handleResponse()** is called 3. **Check for active flow** via `FlowExecutionService::hasActiveFlow($chat)` 4. **If no active flow:** Check for keyword triggers in flows 5. **If flow found:** Execute `FlowExecutionService::executeFlow()` ```php // Flow trigger logic in executeFlow(): // 1. Exact match: message exactly equals keyword // 2. Keywords: FIND_IN_SET(message, keywords) or word matching ``` --- ## Flow Execution Pipeline ### FlowExecutionService ``` ┌─────────────────────────────────────────────────────────────────┐ │ FLOW EXECUTION PIPELINE │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ 1. executeFlow($chat, $isNewContact, $message) │ │ │ │ │ ├─► Check if FlowBuilder module enabled │ │ │ │ │ ├─► Check for active flow (FlowUserData) │ │ │ └─► If active: continue from current_step │ │ │ │ │ ├─► If no active flow: │ │ │ ├─► Find flow by keyword trigger │ │ │ │ ├─► exact_match: message == keyword │ │ │ │ └─► keywords: FIND_IN_SET(message, keywords) │ │ │ │ │ │ │ └─► Create FlowUserData with current_step = start_node │ │ │ ⚠️ BUG: Code sets current_step = 1 (wrong!) │ │ │ │ │ 2. processFlow($chat, $isNewContact, $flowData, $contactId) │ │ │ │ │ ├─► Get flow metadata (nodes + edges) │ │ │ │ │ ├─► Loop through nodes (max 50 iterations) │ │ │ │ │ │ │ ├─► findEdgesBySource(edges, nodes, current_step) │ │ │ │ ⚠️ Uses string comparison: (string)$edge['source'] │ │ │ │ ⚠️ But current_step may be wrong type! │ │ │ │ │ │ │ ├─► Get next node metadata │ │ │ │ │ │ │ └─► Switch on node type: │ │ │ ├─► 'action' → processActionNode() │ │ │ │ └─► ActionExecutionService::executeAction() │ │ │ │ │ │ │ └─► 'text'/'buttons' → processMessageNode() │ │ │ └─► WhatsAppService::sendMessage() │ │ │ │ │ └─► proceedToNextStep() → Update FlowUserData │ │ │ │ 3. ActionExecutionService::executeAction($actionType, ...) │ │ │ │ │ ├─► 'add_to_group' → Contact added to ContactGroup │ │ ├─► 'remove_from_group' → Contact removed from group │ │ ├─► 'update_contact' → Contact fields updated │ │ ├─► 'send_email' → Email via configured SMTP │ │ ├─► 'webhook' → HTTP POST/GET to external URL │ │ ├─► 'delay' → Queue job: ProcessDelayedFlowJob │ │ ├─► 'ai_response' → OpenAI API integration │ │ ├─► 'conditional' → Branch based on conditions │ │ └─► 'assign_to_agent' → Create ChatTicket, send notif │ │ │ └─────────────────────────────────────────────────────────────────┘ ``` --- ## Node Types | Type | Description | Data Structure | |------|-------------|----------------| | `start` | Entry trigger | `keywords: "test,debug"` or `new_contact: true` | | `text` | Text message | `body: "Hello!"` | | `buttons` | Interactive buttons | `buttonType: "buttons", buttons: [{id, text}]` | | `action` | Execute action | `actionType: "add_to_group", config: {...}` | --- ## Available Actions | Action | Config Fields | What It Does | |--------|---------------|--------------| | `add_to_group` | `group_id` | Adds contact to ContactGroup | | `remove_from_group` | `group_id` | Removes from group | | `update_contact` | `target_field`, `value` | Updates contact field | | `send_email` | `to`, `subject`, `body`, SMTP config | Sends email | | `webhook` | `url`, `method`, `payload` | HTTP call to external API | | `delay` | `duration` (minutes) | Pauses flow, queues job | | `ai_response` | `prompt`, `confidence_threshold` | OpenAI response | | `conditional` | `condition_type`, `conditions` | Branches flow | | `assign_to_agent` | `agent_id` | Creates ticket, assigns agent | --- ## Flow Metadata Structure ```json { "nodes": [ { "id": "start", // String ID! Not integer! "type": "start", "data": { "metadata": { "fields": { "type": "keywords", "keywords": "test,debug,flow" } } } }, { "id": "welcome", "type": "text", "data": { "metadata": { "fields": { "type": "text", "body": "Welcome message here" } } } }, { "id": "action1", "type": "action", "data": { "actionType": "add_to_group", "is_active": true, "config": { "group_id": 1 } } } ], "edges": [ {"source": "start", "target": "welcome"}, {"source": "welcome", "target": "action1"} ] } ``` --- ## Key Files to Debug | File | Purpose | Key Methods | |------|---------|--------------| | `FlowExecutionService.php` | Core execution | `executeFlow()`, `processFlow()`, `processActionNode()`, `processMessageNode()`, `proceedToNextStep()` | | `ActionExecutionService.php` | Action handlers | `executeAction()`, `executeAddToGroup()`, `executeWebhook()`, etc. | | `FlowService.php` | CRUD | `createFlow()`, `updateFlow()`, `debugFlow()` | | `FlowController.php` | HTTP | `store()`, `update()`, `debug()` | | `ProcessDelayedFlowJob.php` | Queue | `handle()` → `continueDelayedFlow()` | | `AutoReplyService.php` | Entry | `handleAutomatedFlows()` → `FlowExecutionService` | --- ## Database Tables | Table | Purpose | Key Columns | |-------|---------|-------------| | `flows` | Flow definitions | `id`, `uuid`, `organization_id`, `metadata` (JSON), `trigger`, `keywords`, `status` | | `flow_user_data` | State tracking | `contact_id`, `flow_id`, `current_step` ⚠️ (wrong type!) | | `flow_logs` | Execution logs | `flow_id`, `chat_id` | | `flow_media` | Media attachments | `flow_id`, `step_id`, `path` | | `contacts` | Contact data | `id`, `phone`, `created_by` ⚠️ (NOT NULL!) | | `chats` | Chat records | `id`, `contact_id`, `metadata` ⚠️ (NOT NULL!) | --- ## Debugging Commands ```bash # SSH to server ssh hostinger-vps # Test flow trigger cd /var/www/chatberry php artisan tinker --execute=" \$flow = Modules\FlowBuilder\Models\Flow::first(); \$meta = json_decode(\$flow->metadata, true); echo 'Start node: ' . \$meta['nodes'][0]['id']; " # Test action execution php artisan tinker --execute=" \$service = new Modules\FlowBuilder\Services\ActionExecutionService(1); \$result = \$service->executeAction('add_to_group', ['group_id' => 1], \$contact); echo \$result ? 'SUCCESS' : 'FAILED'; " # Watch logs during execution tail -f storage/logs/laravel.log # Check flow user data php artisan tinker --execute=" Modules\FlowBuilder\Models\FlowUserData::latest()->take(5)->get()->each(function(\$f) { echo 'Contact: ' . \$f->contact_id . ', Step: ' . \$f->current_step . PHP_EOL; }); " ``` --- ## Critical Bug Summary for Reference 1. **`current_step` initialized wrong** - Set to `1` instead of `'start'` 2. **`current_step` column wrong type** - BIGINT instead of VARCHAR 3. **`created_by` NOT NULL** - Breaks contact creation 4. **`metadata` NOT NULL** - Breaks chat creation All four must be fixed for flows to work.