'int', 'restaurant_id' => 'int', 'active' => 'boolean', 'ordering' => 'int', 'structure' => 'json', 'configs' => 'json' ]; // Flow types const TYPE_RECONFIRM = 'reconfirm'; const TYPE_REMINDER = 'reminder'; const TYPE_NOTIFICATION = 'notification'; const TYPE_WHATSAPP = 'whatsapp'; const TYPE_EMAIL = 'email'; const TYPE_TRIGGER = 'trigger'; public static function booted() { static::creating(function($model) { if (empty($model->uuid)) { $model->uuid = Str::uuid('wf'); } // Only set default structure if not already set // Note: Don't use empty() as it returns true for arrays if (!isset($model->attributes['structure']) || $model->attributes['structure'] === null) { $model->structure = self::getDefaultStructure(); } // Only set default configs if not already set if (!isset($model->attributes['configs']) || $model->attributes['configs'] === null) { $model->configs = []; } }); } // Relationships public function restaurant() { return $this->belongsTo(Restaurant::class); } // Accessors /** * Count all nodes in the structure tree */ public function getNodesCountAttribute() { $structure = $this->structure; if (empty($structure)) { return 0; } return $this->countNodes($structure); } /** * Get the structure tree */ public function getStructureAttribute($value) { if (is_string($value)) { return json_decode($value, true) ?? []; } return $value ?? []; } /** * Get the configs object */ public function getConfigsAttribute($value) { if (is_string($value)) { return json_decode($value, true) ?? []; } return $value ?? []; } // Helper methods /** * Count nodes recursively in the structure tree */ protected function countNodes($node) { if (empty($node)) { return 0; } $count = 1; // Count current node if (isset($node['children']) && is_array($node['children'])) { foreach ($node['children'] as $child) { $count += $this->countNodes($child); } } return $count; } /** * Get default structure for a new flow */ public static function getDefaultStructure() { return [ 'id' => 'node-trigger', 'type' => 'trigger', 'title' => 'TRIGGER', 'description' => 'Flow trigger', 'children' => [] ]; } /** * Set the structure tree */ public function setStructure($structure) { $this->structure = $structure; return $this; } /** * Set the configs object */ public function setConfigs($configs) { $this->configs = $configs; return $this; } /** * Get a specific node by ID from the structure */ public function getNodeById($nodeId) { return $this->findNodeInTree($this->structure, $nodeId); } /** * Find a node recursively in the tree */ protected function findNodeInTree($node, $nodeId) { if (empty($node)) { return null; } if (isset($node['id']) && $node['id'] === $nodeId) { return $node; } if (isset($node['children']) && is_array($node['children'])) { foreach ($node['children'] as $child) { $found = $this->findNodeInTree($child, $nodeId); if ($found) { return $found; } } } return null; } /** * Get config for a specific node */ public function getNodeConfig($nodeId) { $configs = $this->configs; return $configs[$nodeId] ?? []; } /** * Set config for a specific node */ public function setNodeConfig($nodeId, $config) { $configs = $this->configs; $configs[$nodeId] = $config; $this->configs = $configs; return $this; } /** * Update config for a specific node (merge) */ public function updateNodeConfig($nodeId, $config) { $configs = $this->configs; $existingConfig = $configs[$nodeId] ?? []; $configs[$nodeId] = array_merge($existingConfig, $config); $this->configs = $configs; return $this; } /** * Remove config for a specific node */ public function removeNodeConfig($nodeId) { $configs = $this->configs; unset($configs[$nodeId]); $this->configs = $configs; return $this; } /** * Get all node IDs from the structure */ public function getAllNodeIds() { return $this->collectNodeIds($this->structure); } /** * Collect node IDs recursively */ protected function collectNodeIds($node, $ids = []) { if (empty($node)) { return $ids; } if (isset($node['id'])) { $ids[] = $node['id']; } if (isset($node['children']) && is_array($node['children'])) { foreach ($node['children'] as $child) { $ids = $this->collectNodeIds($child, $ids); } } return $ids; } /** * Clean orphan configs (configs for nodes that don't exist in structure) */ public function cleanOrphanConfigs() { $nodeIds = $this->getAllNodeIds(); $configs = $this->configs; $cleanedConfigs = []; foreach ($configs as $nodeId => $config) { if (in_array($nodeId, $nodeIds)) { $cleanedConfigs[$nodeId] = $config; } } $this->configs = $cleanedConfigs; return $this; } /** * Validate the structure tree */ public function validateStructure() { $structure = $this->structure; if (empty($structure)) { return false; } // Must have at least id and type if (!isset($structure['id']) || !isset($structure['type'])) { return false; } return true; } /** * Get available flow types */ public static function getTypes() { return [ self::TYPE_RECONFIRM => 'Re-confirm', self::TYPE_REMINDER => 'Reminder', self::TYPE_NOTIFICATION => 'Notification', self::TYPE_WHATSAPP => 'WhatsApp', self::TYPE_EMAIL => 'Email', self::TYPE_TRIGGER => 'Trigger' ]; } /** * Check if flow is of a specific type */ public function isType($type) { return $this->type === $type; } /** * Duplicate the flow with a new name */ public function duplicate($newName = null) { $newFlow = new self(); $newFlow->restaurant_id = $this->restaurant_id; $newFlow->name = $newName ?? $this->name . ' Copy'; $newFlow->type = $this->type; $newFlow->description = $this->description; $newFlow->structure = $this->structure; $newFlow->configs = $this->configs; $newFlow->active = false; // Duplicated flows start inactive $newFlow->ordering = $this->ordering + 1; return $newFlow; } }