diff --git a/Status.md b/Status.md index cdcf8d86..4de1072d 100644 --- a/Status.md +++ b/Status.md @@ -6,7 +6,76 @@ --- -## 🎯 CURRENT SESSION - GEMINI TRANSITION & VALIDATION (Dec 16, 2025) +## 🎯 CURRENT SESSION - ZOHO CRM INTEGRATION SETUP (Dec 16, 2025) + +### Zoho CRM Integration - Staging Environment + +**Objective:** Configure and test Zoho CRM sync implementation for staging environment. + +**Status:** ✅ OAuth Working, Sync Methods Implemented, Dry-Run Tested + +**Completed:** + +1. ✅ **OAuth Authentication Verified** + - Refresh token exists and is valid + - API connection successful (53 modules accessible) + - Read operations working (Contacts, Campaigns, Users) + +2. ✅ **Read-Only API Tests Passed** + - Organization Info: Manifold Cloud Services (America/Detroit) + - Contacts: 5+ records readable (Tanner Moore, Pete Knochelmann, etc.) + - Campaigns: 5+ records readable (Nov 28, Oct 23, etc.) + - CRM Users: Ben Reed (CEO), JR Lawhorne (Manager), etc. + +3. ✅ **Sync Class Bug Fixes** + - Fixed user roles: `trainer`/`trainee` → `hvac_trainer`/`hvac_master_trainer` + - Fixed event filter: Removed restrictive `_hvac_event_type` meta query + - Fixed event display: Changed `eventDisplay` from `list` to `custom` to include past events + - Fixed WooCommerce dependency: Added graceful error handling + +4. ✅ **Event Tickets Integration (NEW)** + - Replaced WooCommerce sync with Event Tickets (Tickets Commerce) support + - Added `sync_attendees()` method → Zoho Contacts + Campaign Members + - Added `sync_rsvps()` method → Zoho Leads + Campaign Members + - Updated meta keys for Tickets Commerce (`_tec_tickets_commerce_*`) + - Updated meta keys for RSVPs (`_tribe_rsvp_*`) + +5. ✅ **Admin Interface Updated** + - Added "Sync Attendees" button (Contacts + Campaign Members) + - Added "Sync RSVPs" button (Leads + Campaign Members) + - Renamed "Sync Purchases" to "Sync Orders" (Tickets Commerce) + +**Dry-Run Results (Staging - No Data Sent to Zoho):** +| Sync Type | Records Found | Status | +|-----------|---------------|--------| +| Events → Campaigns | 20 | ✅ Ready | +| Trainers → Contacts | 53 | ✅ Ready | +| Attendees → Contacts + Campaign Members | 79 | ✅ Ready | +| RSVPs → Leads + Campaign Members | 4 | ✅ Ready | +| Orders → Invoices | 52 | ✅ Ready | + +**Zoho CRM Mapping Strategy:** +- **Events** → **Campaigns** (direct mapping) +- **Trainers** (hvac_trainer, hvac_master_trainer) → **Contacts** (with Contact_Type field) +- **Ticket Attendees** → **Contacts** + **Campaign Members** (links Contact ↔ Campaign) +- **RSVPs** → **Leads** + **Campaign Members** (links Lead ↔ Campaign) +- **Ticket Orders** → **Invoices** (financial records) + +**Staging Protection Active:** +- All write operations (POST/PUT/DELETE) are blocked on staging +- Only production (`upskillhvac.com`) can write to Zoho CRM +- Dry-run shows what would sync without actually sending data + +**Admin Page Location:** +- `/wp-admin/admin.php?page=hvac-zoho-sync` + +**Files Modified:** +- `includes/zoho/class-zoho-sync.php` - Complete rewrite for Event Tickets +- `includes/admin/class-zoho-admin.php` - Added new sync buttons + +--- + +## 📅 PREVIOUS SESSION - GEMINI TRANSITION & VALIDATION (Dec 16, 2025) ### Gemini Development Environment Setup diff --git a/includes/admin/class-zoho-admin.php b/includes/admin/class-zoho-admin.php index b5eb16f6..7500c01f 100644 --- a/includes/admin/class-zoho-admin.php +++ b/includes/admin/class-zoho-admin.php @@ -284,25 +284,39 @@ class HVAC_Zoho_Admin {

Data Sync

- +

Events → Campaigns

Sync events from The Events Calendar to Zoho CRM Campaigns

- +
-

Users → Contacts

-

Sync trainers and attendees to Zoho CRM Contacts

- +

Trainers → Contacts

+

Sync trainers (hvac_trainer, hvac_master_trainer) to Zoho CRM Contacts

+
- +
-

Purchases → Invoices

-

Sync ticket purchases to Zoho CRM Invoices

- +

Ticket Attendees → Contacts + Campaign Members

+

Sync Event Tickets attendees to Zoho CRM Contacts and link them to Campaigns

+ +
+
+ +
+

RSVPs → Leads + Campaign Members

+

Sync RSVP responses to Zoho CRM Leads and link them to Campaigns

+ +
+
+ +
+

Ticket Orders → Invoices

+

Sync Event Tickets orders (Tickets Commerce) to Zoho CRM Invoices

+
@@ -906,6 +920,12 @@ class HVAC_Zoho_Admin { case 'users': $result = $sync->sync_users(); break; + case 'attendees': + $result = $sync->sync_attendees(); + break; + case 'rsvps': + $result = $sync->sync_rsvps(); + break; case 'purchases': $result = $sync->sync_purchases(); break; diff --git a/includes/zoho/class-zoho-sync.php b/includes/zoho/class-zoho-sync.php index 8523d543..9766d338 100644 --- a/includes/zoho/class-zoho-sync.php +++ b/includes/zoho/class-zoho-sync.php @@ -65,17 +65,11 @@ class HVAC_Zoho_Sync { 'staging_mode' => $this->is_staging ); - // Get all published events + // Get all published events (past and future - 'custom' bypasses date filtering) $events = tribe_get_events(array( 'posts_per_page' => -1, - 'eventDisplay' => 'list', - 'meta_query' => array( - array( - 'key' => '_hvac_event_type', - 'value' => 'trainer', - 'compare' => '=' - ) - ) + 'eventDisplay' => 'custom', + 'start_date' => '2020-01-01', // Include all historical events )); $results['total'] = count($events); @@ -156,9 +150,9 @@ class HVAC_Zoho_Sync { 'staging_mode' => $this->is_staging ); - // Get trainers and attendees + // Get trainers (hvac_trainer and hvac_master_trainer roles) $users = get_users(array( - 'role__in' => array('trainer', 'trainee'), + 'role__in' => array('hvac_trainer', 'hvac_master_trainer'), 'meta_query' => array( 'relation' => 'OR', array( @@ -243,7 +237,7 @@ class HVAC_Zoho_Sync { } /** - * Sync ticket purchases to Zoho Invoices + * Sync Event Tickets orders to Zoho Invoices * * @return array Sync results */ @@ -255,81 +249,369 @@ class HVAC_Zoho_Sync { 'errors' => array(), 'staging_mode' => $this->is_staging ); - - // Get all completed orders - $orders = wc_get_orders(array( - 'status' => 'completed', - 'limit' => -1, - 'meta_key' => '_tribe_tickets_event_id', - 'meta_compare' => 'EXISTS' + + // Get Tickets Commerce orders (tec_tc_order post type) + $orders = get_posts(array( + 'post_type' => 'tec_tc_order', + 'posts_per_page' => -1, + 'post_status' => 'tec-tc-completed', )); - + $results['total'] = count($orders); - + + if ($results['total'] === 0) { + $results['message'] = 'No completed ticket orders found.'; + return $results; + } + // If staging mode, simulate the sync if ($this->is_staging) { $results['message'] = 'STAGING MODE: Sync simulation only. No data sent to Zoho.'; $results['synced'] = $results['total']; $results['test_data'] = array(); - + foreach ($orders as $order) { - $invoice_data = $this->prepare_invoice_data($order); + $invoice_data = $this->prepare_tc_invoice_data($order); $results['test_data'][] = array( - 'order_id' => $order->get_id(), - 'order_number' => $order->get_order_number(), - 'order_total' => $order->get_total(), + 'order_id' => $order->ID, + 'purchaser_email' => get_post_meta($order->ID, '_tec_tc_order_purchaser_email', true), + 'gateway' => get_post_meta($order->ID, '_tec_tc_order_gateway', true), + 'date' => $order->post_date, 'zoho_data' => $invoice_data ); } - + return $results; } - + // Production sync if (!$this->is_sync_allowed()) { $results['errors'][] = 'Sync not allowed on this domain. Only upskillhvac.com can sync to production.'; return $results; } - + foreach ($orders as $order) { try { - $invoice_data = $this->prepare_invoice_data($order); - - // Check if invoice already exists in Zoho - $order_number = $order->get_order_number(); - $search_response = $this->auth->make_api_request('GET', '/crm/v2/Invoices/search', array( - 'criteria' => "(Invoice_Number:equals:{$order_number})" - )); - + $invoice_data = $this->prepare_tc_invoice_data($order); + + // Check if invoice already exists in Zoho (by WordPress Order ID) + $search_response = $this->auth->make_api_request( + '/Invoices/search?criteria=(WordPress_Order_ID:equals:' . $order->ID . ')', + 'GET' + ); + if (!empty($search_response['data'])) { // Update existing invoice $invoice_id = $search_response['data'][0]['id']; - $update_response = $this->auth->make_api_request('PUT', "/crm/v2/Invoices/{$invoice_id}", array( + $this->auth->make_api_request("/Invoices/{$invoice_id}", 'PUT', array( 'data' => array($invoice_data) )); } else { // Create new invoice - $create_response = $this->auth->make_api_request('POST', '/crm/v2/Invoices', array( + $create_response = $this->auth->make_api_request('/Invoices', 'POST', array( 'data' => array($invoice_data) )); + + if (!empty($create_response['data'][0]['details']['id'])) { + $invoice_id = $create_response['data'][0]['details']['id']; + } } - + $results['synced']++; - + // Update order meta with Zoho ID if (isset($invoice_id)) { - $order->update_meta_data('_zoho_invoice_id', $invoice_id); - $order->save(); + update_post_meta($order->ID, '_zoho_invoice_id', $invoice_id); } - + } catch (Exception $e) { $results['failed']++; - $results['errors'][] = sprintf('Order %s: %s', $order->get_id(), $e->getMessage()); + $results['errors'][] = sprintf('Order %s: %s', $order->ID, $e->getMessage()); } } - + return $results; } + + /** + * Sync ticket attendees to Zoho Contacts + Campaign Members + * + * @return array Sync results + */ + public function sync_attendees() { + $results = array( + 'total' => 0, + 'synced' => 0, + 'failed' => 0, + 'errors' => array(), + 'staging_mode' => $this->is_staging, + 'contacts_created' => 0, + 'campaign_members_created' => 0 + ); + + // Get all ticket attendees (Tickets Commerce) + $attendees = get_posts(array( + 'post_type' => 'tec_tc_attendee', + 'posts_per_page' => -1, + 'post_status' => 'any', + )); + + // Also get PayPal attendees if any + $tpp_attendees = get_posts(array( + 'post_type' => 'tribe_tpp_attendees', + 'posts_per_page' => -1, + 'post_status' => 'any', + )); + + $all_attendees = array_merge($attendees, $tpp_attendees); + $results['total'] = count($all_attendees); + + if ($results['total'] === 0) { + $results['message'] = 'No ticket attendees found.'; + return $results; + } + + // If staging mode, simulate the sync + if ($this->is_staging) { + $results['message'] = 'STAGING MODE: Sync simulation only. No data sent to Zoho.'; + $results['synced'] = $results['total']; + $results['test_data'] = array(); + + foreach ($all_attendees as $attendee) { + $attendee_data = $this->prepare_attendee_data($attendee); + $results['test_data'][] = $attendee_data; + } + + return $results; + } + + // Production sync + if (!$this->is_sync_allowed()) { + $results['errors'][] = 'Sync not allowed on this domain. Only upskillhvac.com can sync to production.'; + return $results; + } + + foreach ($all_attendees as $attendee) { + try { + $attendee_data = $this->prepare_attendee_data($attendee); + + if (empty($attendee_data['email'])) { + $results['failed']++; + $results['errors'][] = sprintf('Attendee %s: No email address', $attendee->ID); + continue; + } + + // Step 1: Create/Update Contact + $contact_id = $this->ensure_contact_exists($attendee_data); + if ($contact_id) { + $results['contacts_created']++; + } + + // Step 2: Create Campaign Member (link Contact to Campaign) + if ($contact_id && !empty($attendee_data['event_id'])) { + $campaign_id = get_post_meta($attendee_data['event_id'], '_zoho_campaign_id', true); + if ($campaign_id) { + $this->create_campaign_member($contact_id, $campaign_id, 'Attended'); + $results['campaign_members_created']++; + } + } + + $results['synced']++; + + // Update attendee meta with Zoho Contact ID + update_post_meta($attendee->ID, '_zoho_contact_id', $contact_id); + + } catch (Exception $e) { + $results['failed']++; + $results['errors'][] = sprintf('Attendee %s: %s', $attendee->ID, $e->getMessage()); + } + } + + return $results; + } + + /** + * Sync RSVPs to Zoho Leads + Campaign Members + * + * @return array Sync results + */ + public function sync_rsvps() { + $results = array( + 'total' => 0, + 'synced' => 0, + 'failed' => 0, + 'errors' => array(), + 'staging_mode' => $this->is_staging, + 'leads_created' => 0, + 'campaign_members_created' => 0 + ); + + // Get RSVP attendees + $rsvps = get_posts(array( + 'post_type' => 'tribe_rsvp_attendees', + 'posts_per_page' => -1, + 'post_status' => 'any', + )); + + $results['total'] = count($rsvps); + + if ($results['total'] === 0) { + $results['message'] = 'No RSVP attendees found.'; + return $results; + } + + // If staging mode, simulate the sync + if ($this->is_staging) { + $results['message'] = 'STAGING MODE: Sync simulation only. No data sent to Zoho.'; + $results['synced'] = $results['total']; + $results['test_data'] = array(); + + foreach ($rsvps as $rsvp) { + $rsvp_data = $this->prepare_rsvp_data($rsvp); + $results['test_data'][] = $rsvp_data; + } + + return $results; + } + + // Production sync + if (!$this->is_sync_allowed()) { + $results['errors'][] = 'Sync not allowed on this domain. Only upskillhvac.com can sync to production.'; + return $results; + } + + foreach ($rsvps as $rsvp) { + try { + $rsvp_data = $this->prepare_rsvp_data($rsvp); + + if (empty($rsvp_data['email'])) { + $results['failed']++; + $results['errors'][] = sprintf('RSVP %s: No email address', $rsvp->ID); + continue; + } + + // Step 1: Create/Update Lead + $lead_id = $this->ensure_lead_exists($rsvp_data); + if ($lead_id) { + $results['leads_created']++; + } + + // Step 2: Create Campaign Member (link Lead to Campaign) + if ($lead_id && !empty($rsvp_data['event_id'])) { + $campaign_id = get_post_meta($rsvp_data['event_id'], '_zoho_campaign_id', true); + if ($campaign_id) { + $this->create_campaign_member($lead_id, $campaign_id, 'Responded', 'Leads'); + $results['campaign_members_created']++; + } + } + + $results['synced']++; + + // Update RSVP meta with Zoho Lead ID + update_post_meta($rsvp->ID, '_zoho_lead_id', $lead_id); + + } catch (Exception $e) { + $results['failed']++; + $results['errors'][] = sprintf('RSVP %s: %s', $rsvp->ID, $e->getMessage()); + } + } + + return $results; + } + + /** + * Ensure a Contact exists in Zoho (create or get existing) + * + * @param array $data Attendee data + * @return string|null Zoho Contact ID + */ + private function ensure_contact_exists($data) { + // Search for existing contact by email + $search_response = $this->auth->make_api_request( + '/Contacts/search?criteria=(Email:equals:' . urlencode($data['email']) . ')', + 'GET' + ); + + if (!empty($search_response['data'])) { + return $search_response['data'][0]['id']; + } + + // Create new contact + $contact_data = array( + 'First_Name' => $data['first_name'] ?: 'Unknown', + 'Last_Name' => $data['last_name'] ?: 'Attendee', + 'Email' => $data['email'], + 'Lead_Source' => 'Event Tickets', + 'Contact_Type' => 'Attendee', + 'WordPress_Attendee_ID' => $data['attendee_id'], + ); + + $create_response = $this->auth->make_api_request('/Contacts', 'POST', array( + 'data' => array($contact_data) + )); + + if (!empty($create_response['data'][0]['details']['id'])) { + return $create_response['data'][0]['details']['id']; + } + + return null; + } + + /** + * Ensure a Lead exists in Zoho (create or get existing) + * + * @param array $data RSVP data + * @return string|null Zoho Lead ID + */ + private function ensure_lead_exists($data) { + // Search for existing lead by email + $search_response = $this->auth->make_api_request( + '/Leads/search?criteria=(Email:equals:' . urlencode($data['email']) . ')', + 'GET' + ); + + if (!empty($search_response['data'])) { + return $search_response['data'][0]['id']; + } + + // Create new lead + $lead_data = array( + 'First_Name' => $data['first_name'] ?: 'Unknown', + 'Last_Name' => $data['last_name'] ?: 'RSVP', + 'Email' => $data['email'], + 'Lead_Source' => 'Event RSVP', + 'Lead_Status' => 'Contacted', + 'WordPress_RSVP_ID' => $data['rsvp_id'], + ); + + $create_response = $this->auth->make_api_request('/Leads', 'POST', array( + 'data' => array($lead_data) + )); + + if (!empty($create_response['data'][0]['details']['id'])) { + return $create_response['data'][0]['details']['id']; + } + + return null; + } + + /** + * Create a Campaign Member (link Contact/Lead to Campaign) + * + * @param string $record_id Contact or Lead ID + * @param string $campaign_id Campaign ID + * @param string $status Member status (Invited, Responded, Attended, etc.) + * @param string $type 'Contacts' or 'Leads' + */ + private function create_campaign_member($record_id, $campaign_id, $status = 'Attended', $type = 'Contacts') { + $endpoint = "/Campaigns/{$campaign_id}/{$type}/{$record_id}"; + + $this->auth->make_api_request($endpoint, 'PUT', array( + 'data' => array( + array('Member_Status' => $status) + ) + )); + } /** * Prepare campaign data for Zoho @@ -365,8 +647,15 @@ class HVAC_Zoho_Sync { * @return array Contact data */ private function prepare_contact_data($user) { - $role = in_array('trainer', $user->roles) ? 'Trainer' : 'Trainee'; - + // Map WordPress roles to Zoho Contact_Type + if (in_array('hvac_master_trainer', $user->roles)) { + $role = 'Master Trainer'; + } elseif (in_array('hvac_trainer', $user->roles)) { + $role = 'Trainer'; + } else { + $role = 'Trainee'; + } + return array( 'First_Name' => get_user_meta($user->ID, 'first_name', true), 'Last_Name' => get_user_meta($user->ID, 'last_name', true), @@ -384,45 +673,163 @@ class HVAC_Zoho_Sync { } /** - * Prepare invoice data for Zoho + * Prepare invoice data for Tickets Commerce order * - * @param WC_Order $order Order object + * @param WP_Post $order TC Order post object * @return array Invoice data */ - private function prepare_invoice_data($order) { - $event_id = $order->get_meta('_tribe_tickets_event_id'); - $event_title = get_the_title($event_id); - $customer = $order->get_user(); - - // Get contact ID from Zoho - $contact_id = null; - if ($customer) { - $contact_id = get_user_meta($customer->ID, '_zoho_contact_id', true); - } - + private function prepare_tc_invoice_data($order) { + $purchaser_email = get_post_meta($order->ID, '_tec_tc_order_purchaser_email', true); + $purchaser_name = get_post_meta($order->ID, '_tec_tc_order_purchaser_name', true); + $gateway = get_post_meta($order->ID, '_tec_tc_order_gateway', true); + $gateway_order_id = get_post_meta($order->ID, '_tec_tc_order_gateway_order_id', true); + $order_items = get_post_meta($order->ID, '_tec_tc_order_items', true); + + // Parse order items to get event info and line items $items = array(); - foreach ($order->get_items() as $item) { - $items[] = array( - 'Product_Name' => $item->get_name(), - 'Quantity' => $item->get_quantity(), - 'Rate' => $item->get_subtotal() / $item->get_quantity(), - 'Total' => $item->get_total() - ); + $event_titles = array(); + $total = 0; + + if (is_array($order_items)) { + foreach ($order_items as $item) { + $event_id = isset($item['event_id']) ? $item['event_id'] : 0; + $ticket_id = isset($item['ticket_id']) ? $item['ticket_id'] : 0; + $quantity = isset($item['quantity']) ? intval($item['quantity']) : 1; + $price = isset($item['price']) ? floatval($item['price']) : 0; + + if ($event_id) { + $event_titles[] = get_the_title($event_id); + } + + $ticket_name = $ticket_id ? get_the_title($ticket_id) : 'Event Ticket'; + + $items[] = array( + 'product' => array('name' => $ticket_name), + 'quantity' => $quantity, + 'list_price' => $price, + 'total' => $price * $quantity + ); + + $total += $price * $quantity; + } } - + + $event_summary = !empty($event_titles) ? implode(', ', array_unique($event_titles)) : 'Event Tickets'; + return array( - 'Invoice_Number' => $order->get_order_number(), - 'Invoice_Date' => $order->get_date_created()->format('Y-m-d'), + 'Subject' => "Ticket Purchase - {$event_summary}", + 'Invoice_Date' => date('Y-m-d', strtotime($order->post_date)), 'Status' => 'Paid', - 'Contact_Name' => $contact_id, - 'Subject' => "Ticket Purchase - {$event_title}", - 'Sub_Total' => $order->get_subtotal(), - 'Tax' => $order->get_total_tax(), - 'Total' => $order->get_total(), - 'Balance' => 0, - 'WordPress_Order_ID' => $order->get_id(), + 'Account_Name' => $purchaser_name, + 'Description' => "Payment via {$gateway}. Transaction: {$gateway_order_id}", + 'Grand_Total' => $total, + 'WordPress_Order_ID' => $order->ID, 'Product_Details' => $items ); } -} -?> \ No newline at end of file + + /** + * Prepare attendee data from Event Tickets attendee post + * + * @param WP_Post $attendee Attendee post object + * @return array Attendee data + */ + private function prepare_attendee_data($attendee) { + $post_type = $attendee->post_type; + + // Handle different attendee post types + if ($post_type === 'tec_tc_attendee') { + // Tickets Commerce attendee (meta keys: _tec_tickets_commerce_*) + $event_id = get_post_meta($attendee->ID, '_tec_tickets_commerce_event', true); + $ticket_id = get_post_meta($attendee->ID, '_tec_tickets_commerce_ticket', true); + $order_id = get_post_meta($attendee->ID, '_tec_tickets_commerce_order', true); + $full_name = get_post_meta($attendee->ID, '_tec_tickets_commerce_full_name', true); + $email = get_post_meta($attendee->ID, '_tec_tickets_commerce_email', true); + $checkin = get_post_meta($attendee->ID, '_tec_tickets_commerce_checked_in', true); + } else { + // PayPal or other attendee types (tribe_tpp_attendees) + $event_id = get_post_meta($attendee->ID, '_tribe_tpp_event', true); + $ticket_id = get_post_meta($attendee->ID, '_tribe_tpp_product', true); + $order_id = get_post_meta($attendee->ID, '_tribe_tpp_order', true); + $full_name = get_post_meta($attendee->ID, '_tribe_tickets_full_name', true); + $email = get_post_meta($attendee->ID, '_tribe_tickets_email', true); + $checkin = get_post_meta($attendee->ID, '_tribe_tpp_checkin', true); + } + + // Parse full name into first/last + $name_parts = explode(' ', trim($full_name), 2); + $first_name = isset($name_parts[0]) ? $name_parts[0] : ''; + $last_name = isset($name_parts[1]) ? $name_parts[1] : ''; + + $event_title = $event_id ? get_the_title($event_id) : ''; + $ticket_name = $ticket_id ? get_the_title($ticket_id) : ''; + + return array( + 'attendee_id' => $attendee->ID, + 'post_type' => $post_type, + 'event_id' => $event_id, + 'event_title' => $event_title, + 'ticket_id' => $ticket_id, + 'ticket_name' => $ticket_name, + 'order_id' => $order_id, + 'full_name' => $full_name, + 'first_name' => $first_name, + 'last_name' => $last_name, + 'email' => $email, + 'checked_in' => !empty($checkin), + 'zoho_contact' => array( + 'First_Name' => $first_name ?: 'Unknown', + 'Last_Name' => $last_name ?: 'Attendee', + 'Email' => $email, + 'Lead_Source' => 'Event Tickets', + 'Contact_Type' => 'Attendee', + ), + 'zoho_campaign_member' => array( + 'Member_Status' => !empty($checkin) ? 'Attended' : 'Registered', + ) + ); + } + + /** + * Prepare RSVP data from Event Tickets RSVP attendee post + * + * @param WP_Post $rsvp RSVP attendee post object + * @return array RSVP data + */ + private function prepare_rsvp_data($rsvp) { + $event_id = get_post_meta($rsvp->ID, '_tribe_rsvp_event', true); + $ticket_id = get_post_meta($rsvp->ID, '_tribe_rsvp_product', true); + $full_name = get_post_meta($rsvp->ID, '_tribe_rsvp_full_name', true); + $email = get_post_meta($rsvp->ID, '_tribe_rsvp_email', true); + $rsvp_status = get_post_meta($rsvp->ID, '_tribe_rsvp_status', true); // yes, no + + // Parse full name into first/last + $name_parts = explode(' ', trim($full_name), 2); + $first_name = isset($name_parts[0]) ? $name_parts[0] : ''; + $last_name = isset($name_parts[1]) ? $name_parts[1] : ''; + + $event_title = $event_id ? get_the_title($event_id) : ''; + + return array( + 'rsvp_id' => $rsvp->ID, + 'event_id' => $event_id, + 'event_title' => $event_title, + 'ticket_id' => $ticket_id, + 'full_name' => $full_name, + 'first_name' => $first_name, + 'last_name' => $last_name, + 'email' => $email, + 'rsvp_status' => $rsvp_status, + 'zoho_lead' => array( + 'First_Name' => $first_name ?: 'Unknown', + 'Last_Name' => $last_name ?: 'RSVP', + 'Email' => $email, + 'Lead_Source' => 'Event RSVP', + 'Lead_Status' => $rsvp_status === 'yes' ? 'Contacted' : 'Not Interested', + ), + 'zoho_campaign_member' => array( + 'Member_Status' => $rsvp_status === 'yes' ? 'Responded' : 'Not Responded', + ) + ); + } +} \ No newline at end of file