PluginProbe ʕ •ᴥ•ʔ
CommerceBird – AI Command Center, ERP Integrations & B2B for WooCommerce (Zoho, Exact Online). / 2.4.2
CommerceBird – AI Command Center, ERP Integrations & B2B for WooCommerce (Zoho, Exact Online). v2.4.2
3.0.3 3.0.2 3.0.1 trunk 2.2.14 2.2.15 2.2.16 2.2.17 2.2.18 2.2.19 2.3.0 2.3.1 2.3.10 2.3.11 2.3.12 2.3.13 2.3.14 2.3.2 2.3.3 2.3.4 2.3.5 2.3.6 2.3.7 2.3.8 2.3.9 2.4.0 2.4.1 2.4.2 2.4.3 2.4.4 2.4.5 2.4.6 2.5.0 2.5.1 2.5.2 2.6.0 2.6.1 2.6.2 2.6.3 2.6.4 2.6.5 2.7.0 2.7.1 2.7.2 2.7.3 2.7.4 2.7.5 2.7.6 2.7.7 2.7.8 2.7.9 2.7.91 2.7.92 2.7.93 2.8.0 2.8.1 2.8.2 2.8.3 2.8.4 2.8.5 2.9.0 2.9.1 2.9.2 2.9.3 3.0.0
commercebird / includes / classes / zoho-crm / class-zcrm-salesorder.php
commercebird / includes / classes / zoho-crm Last commit date
class-zcrm-contact.php 1 year ago class-zcrm-modules.php 1 year ago class-zcrm-product.php 11 months ago class-zcrm-salesorder.php 1 year ago index.php 1 year ago
class-zcrm-salesorder.php
431 lines
1 <?php
2
3 if ( ! defined( 'ABSPATH' ) ) {
4 exit;
5 }
6
7 class CMBIRD_ZCRM_SalesOrder {
8
9 protected $zcrm_modules;
10 protected $zoho_crm_url;
11 protected $zcrm_custom_fields;
12
13 public function __construct() {
14 $this->zcrm_modules = new CMBIRD_ZCRM_Modules();
15 $this->zoho_crm_url = rtrim( get_option( 'cmbird_zoho_crm_url' ), '/' ) . '/';
16 // get the option for custom fields
17 $this->zcrm_custom_fields = get_option( 'zcrm_sales_orders_fields', array() );
18 }
19
20 public function cmbird_zcrm_order_sync( $order_id ) {
21 // $fd = fopen( __DIR__ . '/cmbird_zcrm_order_sync.txt', 'w+' );
22 $order = wc_get_order( $order_id );
23 if ( ! $order || ! $order->get_billing_email() ) {
24 return;
25 }
26
27 $contact_id = $this->get_or_resolve_contact_id( $order );
28 if ( is_wp_error( $contact_id ) || ! $contact_id ) {
29 // save error as order note
30 $order->add_order_note( 'Zoho Contact not found for order ' . $order_id );
31 return;
32 }
33
34 $account_id = $this->get_or_resolve_account_id( $order );
35 if ( is_wp_error( $account_id ) ) {
36 // save error as order note
37 $order->add_order_note( 'Zoho Account lookup failed: ' . $account_id->get_error_message() );
38 return;
39 }
40
41 // Step 1. if zcrm sales order id is in the order meta, we will update the record
42 $sales_order_id = $order->get_meta( 'zcrm_sales_order_id', true );
43 if ( $sales_order_id ) {
44 $body = $this->format_order_for_zoho( $order, $contact_id, $account_id, true );
45 // log the body for debugging
46 // fwrite( $fd, 'Zoho Sales Order Update Body: ' . wp_json_encode( $body ) . PHP_EOL );
47 // Update the existing Sales Order in Zoho
48 $response = $this->zcrm_modules->cmbird_update_record( 'Sales_Orders', $sales_order_id, $body );
49 if ( is_wp_error( $response ) ) {
50 $order->update_meta_data( 'zcrm_sales_order_request_body', wp_json_encode( $body ) );
51 // log the error message as order note
52 $order->add_order_note( 'Zoho CRM Update Failed: ' . $response->get_error_message() );
53 return;
54 }
55 $order->add_order_note( 'Zoho CRM Sales Order updated successfully with ID: ' . $sales_order_id );
56 // fwrite( $fd, 'Zoho Sales Order updated successfully with ID: ' . $sales_order_id . PHP_EOL );
57 return;
58 }
59 $body = $this->format_order_for_zoho( $order, $contact_id, $account_id );
60 // Step 2. If no existing sales order ID, create a new one
61 $response = $this->zcrm_modules->cmbird_create_record( 'Sales_Orders', $body );
62
63 if ( is_wp_error( $response ) ) {
64 // Save the body as order meta for debugging
65 $order->update_meta_data( 'zcrm_sales_order_request_body', wp_json_encode( $body ) );
66 // log the error message as order note
67 $order->add_order_note( 'Zoho CRM Sync Failed: ' . $response->get_error_message() );
68 } else {
69 $sales_order_id = $response->data[0]->details->id;
70 // Get the Sales Order and save the id of Ordered_Items
71 $sales_order_record = $this->zcrm_modules->cmbird_get_record( 'Sales_Orders', $sales_order_id );
72 if ( ! empty( $sales_order_record->data[0]->Ordered_Items ) ) {
73 foreach ( $sales_order_record->data[0]->Ordered_Items as $item ) {
74 $line_item_id = $item->id;
75 // Get Product_Code from Zoho
76 $product_code = isset( $item->Product_Name->Product_Code ) ? $item->Product_Name->Product_Code : '';
77 if ( empty( $product_code ) ) {
78 continue;
79 }
80 // Loop through Woo order items and match by SKU
81 foreach ( $order->get_items() as $order_item ) {
82 $product = $order_item->get_product();
83 if ( ! $product ) {
84 continue;
85 }
86 $sku = $product->get_sku();
87 if ( $sku === $product_code ) {
88 // Match found, save line item ID
89 $order_item->update_meta_data( 'zcrm_line_item_id', $line_item_id );
90 $order_item->save();
91 break;
92 }
93 }
94 }
95 }
96 $order->update_meta_data( 'zcrm_sales_order_id', $sales_order_id );
97 $order->save();
98 $order->add_order_note( 'Zoho CRM Order synced successfully with ID: ' . $sales_order_id );
99 }
100 // fclose( $fd );
101 }
102
103 protected function get_or_resolve_contact_id( $order ) {
104 $contact_id = $order->get_customer_id()
105 ? get_user_meta( $order->get_customer_id(), 'zcrm_contact_id', true )
106 : $order->get_meta( 'zcrm_contact_id', true );
107
108 if ( $contact_id ) {
109 return $contact_id;
110 }
111
112 $contact_id = $this->find_contact_id_by_email( $order->get_billing_email() );
113
114 if ( ! is_wp_error( $contact_id ) && $contact_id ) {
115 if ( $order->get_customer_id() ) {
116 update_user_meta( $order->get_customer_id(), 'zcrm_contact_id', $contact_id );
117 } else {
118 $order->update_meta_data( 'zcrm_contact_id', $contact_id );
119 $order->save();
120 }
121 }
122
123 return $contact_id;
124 }
125
126 protected function get_or_resolve_account_id( $order ) {
127 if ( ! $order->get_billing_company() ) {
128 return null;
129 }
130
131 $account_id = $order->get_customer_id()
132 ? get_user_meta( $order->get_customer_id(), 'zcrm_account_id', true )
133 : $order->get_meta( 'zcrm_account_id', true );
134
135 if ( $account_id ) {
136 return $account_id;
137 }
138
139 $account_id = $this->find_account_id_by_name( $order->get_billing_company() );
140
141 if ( ! is_wp_error( $account_id ) && $account_id ) {
142 if ( $order->get_customer_id() ) {
143 update_user_meta( $order->get_customer_id(), 'zcrm_account_id', $account_id );
144 } else {
145 $order->update_meta_data( 'zcrm_account_id', $account_id );
146 $order->save();
147 }
148 }
149
150 return $account_id;
151 }
152
153 protected function find_contact_id_by_email( $email ) {
154 $url = $this->zoho_crm_url . 'crm/v7/Contacts/search?email=' . urlencode( $email );
155 $api = new \CMBIRD_API_Handler_Zoho();
156 $response = $api->execute_curl_call_get( $url );
157
158 if ( is_wp_error( $response ) ) {
159 return $response;
160 }
161
162 return ! empty( $response->data[0]->id ) ? $response->data[0]->id : new \WP_Error( 'zcrm_no_contact', 'No matching contact found' );
163 }
164
165 protected function find_account_id_by_name( $name ) {
166 $url = $this->zoho_crm_url . 'crm/v7/Accounts/search?word=' . urlencode( $name );
167 $api = new \CMBIRD_API_Handler_Zoho();
168 $response = $api->execute_curl_call_get( $url );
169
170 if ( is_wp_error( $response ) ) {
171 return $response;
172 }
173
174 return ! empty( $response->data[0]->id ) ? $response->data[0]->id : new \WP_Error( 'zcrm_no_account', 'No matching account found' );
175 }
176
177 protected function get_zoho_product_id( $product ) {
178 $woo_product_id = $product->get_id();
179 $zcrm_product_id = get_post_meta( $woo_product_id, 'zcrm_product_id', true );
180
181 if ( ! empty( $zcrm_product_id ) ) {
182 return $zcrm_product_id;
183 }
184
185 // Step 1: Try to find product in Zoho by name
186 $search_url = $this->zoho_crm_url . 'crm/v7/Products/search?word=' . urlencode( $product->get_name() );
187 $api = new \CMBIRD_API_Handler_Zoho();
188 $response = $api->execute_curl_call_get( $search_url );
189
190 if ( ! is_wp_error( $response ) && ! empty( $response->data[0]->id ) ) {
191 $zcrm_product_id = $response->data[0]->id;
192 // Save to Woo meta
193 update_post_meta( $woo_product_id, 'zcrm_product_id', $zcrm_product_id );
194 // update the product in Zoho if necessary
195 $product_payload = array(
196 'data' => array(
197 array(
198 'id' => $zcrm_product_id,
199 'Product_Name' => $product->get_name(),
200 'Unit_Price' => $product->get_price(),
201 'Description' => $product->get_description(),
202 'Product_Code' => $product->get_sku(),
203 'Product_Category' => $product->get_category_ids() ? implode( ',', $product->get_category_ids() ) : '',
204 ),
205 ),
206 );
207 $update_response = $this->zcrm_modules->cmbird_update_record( 'Products', $zcrm_product_id, $product_payload );
208 if ( is_wp_error( $update_response ) || empty( $update_response->data[0]->details->id ) ) {
209 return null;
210 }
211 return $zcrm_product_id;
212 } else {
213 // Step 2: Create new product
214 $product_payload = array(
215 'data' => array(
216 array(
217 'Product_Name' => $product->get_name(),
218 'Unit_Price' => $product->get_price(),
219 'Description' => $product->get_description(),
220 'Product_Code' => $product->get_sku(),
221 'Product_Category' => $product->get_category_ids() ? implode( ',', $product->get_category_ids() ) : '',
222 ),
223 ),
224 );
225 $create_response = $this->zcrm_modules->cmbird_create_record( 'Products', $product_payload );
226
227 if ( is_wp_error( $create_response ) || empty( $create_response->data[0]->details->id ) ) {
228 return null;
229 }
230
231 $zcrm_product_id = $create_response->data[0]->details->id;
232 }
233
234 // Save to Woo meta
235 update_post_meta( $woo_product_id, 'zcrm_product_id', $zcrm_product_id );
236
237 return $zcrm_product_id;
238 }
239
240 protected function format_order_for_zoho( $order, $contact_id, $account_id, $update = false ) {
241 $ordered_items = array();
242
243 foreach ( $order->get_items() as $item ) {
244 $product = $item->get_product();
245 $zcrm_product_id = $this->get_zoho_product_id( $product );
246
247 if ( ! $zcrm_product_id ) {
248 continue;
249 }
250
251 if ( $update ) {
252 $existing_line_item_id = $item->get_meta( 'zcrm_line_item_id', true );
253 if ( ! empty( $existing_line_item_id ) ) {
254 $line_item_id = $existing_line_item_id;
255 }
256 }
257
258 $zoho_tax = $this->get_zoho_tax_data_for_item( $item );
259
260 $ordered_items[] = array(
261 'Product_Name' => array(
262 'name' => $item->get_name(),
263 'id' => $zcrm_product_id,
264 ),
265 'Quantity' => $item->get_quantity(),
266 'List_Price' => $item->get_subtotal(),
267 'id' => $line_item_id ?: '',
268 'Line_Tax' => $zoho_tax ? array( $zoho_tax ) : array(),
269 'Taxable' => true,
270 );
271 }
272 if ( empty( $ordered_items ) ) {
273 return array( 'data' => array() ); // No items to process
274 }
275 $custom_fields = $this->get_custom_mapped_fields_from_order( $order );
276
277 // Merge with core payload
278 $sales_order = array(
279 'Contact_Name' => array( 'id' => $contact_id ),
280 'Account_Name' => $account_id ? array( 'id' => $account_id ) : null,
281 'Subject' => 'WC-' . $order->get_id(),
282 'Billing_Street' => $order->get_billing_address_1(),
283 'Billing_City' => $order->get_billing_city(),
284 'Billing_State' => $order->get_billing_state(),
285 'Billing_Country' => $order->get_billing_country(),
286 'Billing_Code' => $order->get_billing_postcode(),
287 'Shipping_Street' => $order->get_shipping_address_1(),
288 'Shipping_City' => $order->get_shipping_city(),
289 'Shipping_State' => $order->get_shipping_state(),
290 'Shipping_Country' => $order->get_shipping_country(),
291 'Shipping_Code' => $order->get_shipping_postcode(),
292 'Currency' => $order->get_currency(),
293 // 'Discount' => floatval( $order->get_discount_total() ),
294 'Shipping' => floatval( $order->get_shipping_total() + $order->get_shipping_tax() ),
295 'Adjustment' => floatval( $order->get_total_fees() ),
296 'Excise_Duty' => 0,
297 'Status' => 'Approved',
298 'Ordered_Items' => $ordered_items,
299 'Due_Date' => gmdate( 'Y-m-d', strtotime( '+7 days' ) ),
300 );
301
302 // Merge mapped fields
303 $sales_order = array_merge( $sales_order, $custom_fields );
304
305 return array( 'data' => array( $sales_order ) );
306 }
307
308 /**
309 * Get Zoho tax data for a given order item.
310 *
311 * @param WC_Order_Item_Product $item
312 * @return array|null
313 */
314 protected function get_zoho_tax_data_for_item( $item ) {
315 $taxes = $item->get_taxes();
316 if ( empty( $taxes['total'] ) ) {
317 return null;
318 }
319
320 foreach ( $taxes['total'] as $rate_id => $amount ) {
321 if ( $amount > 0 ) {
322 $rate_code = WC_Tax::get_rate_code( $rate_id );
323 $mapped = $this->get_zoho_tax_by_woo_tax_code( $rate_code );
324
325 $tax_name = isset( $mapped['name'] ) ? $mapped['name'] : 'Standard';
326 $tax_value = isset( $mapped['value'] ) ? floatval( $mapped['value'] ) : 0;
327
328 if ( $tax_value ) {
329 return array(
330 'name' => $tax_name,
331 'percentage' => $tax_value,
332 );
333 }
334 }
335 }
336
337 return null;
338 }
339
340 /**
341 * Get custom mapped fields from order meta
342 *
343 * @param WC_Order $order
344 * @return array
345 */
346 protected function get_custom_mapped_fields_from_order( $order ) {
347 $mapped_fields_json = get_option( 'zcrm_sales_orders_custom_fields' );
348 if ( empty( $mapped_fields_json ) ) {
349 return array();
350 }
351
352 $mapped_fields = json_decode( $mapped_fields_json, true );
353 if ( ! is_array( $mapped_fields ) ) {
354 return array();
355 }
356
357 // get all custom fields for Sales Orders
358 if ( ! empty( $this->zcrm_custom_fields ) ) {
359 $custom_fields = $this->zcrm_custom_fields;
360 }
361
362 $zoho_fields = array();
363
364 foreach ( $mapped_fields as $woo_meta_key => $zoho_field_api_name ) {
365 $value = $order->get_meta( $woo_meta_key, true );
366
367 if ( ! empty( $value ) ) {
368 // Use Zoho CRM field api name as key in payload
369 $zoho_fields[ $zoho_field_api_name ] = $value;
370 }
371 }
372
373 return $zoho_fields;
374 }
375
376 protected function get_zoho_tax_by_woo_tax_code( $woo_tax_code ) {
377 // $fd = fopen( __DIR__ . '/cmbird_zoho_tax_sync.txt', 'a+' );
378 if ( ! $woo_tax_code ) {
379 return null;
380 }
381 // get the rate code from the woo_tax_id last integer value after the last dash.
382 $woo_tax_id = intval( preg_replace( '/.*-(\d+)$/', '$1', $woo_tax_code ) );
383 if ( ! $woo_tax_id ) {
384 return null;
385 }
386 // fwrite( $fd, 'WooCommerce Tax ID: ' . $woo_tax_id . PHP_EOL );
387 $option = get_option( 'cmbird_zoho_crm_tax_rate_' . $woo_tax_id );
388 if ( ! $option ) {
389 return null;
390 }
391
392 $parts = explode( '|', $option );
393 if ( count( $parts ) < 1 ) {
394 return null;
395 }
396 // format the name by replacing the @@ with a space
397 $name = isset( $parts[1] ) ? str_replace( '@@', ' ', $parts[1] ) : '';
398
399 return array(
400 'id' => $parts[0],
401 'name' => $name,
402 'value' => isset( $parts[2] ) ? $parts[2] : '',
403 );
404 }
405
406
407 /**
408 * Cancel the Zoho Sales Order
409 */
410 public function cancel_zoho_sales_order( $order_id ) {
411 $order = wc_get_order( $order_id );
412 if ( ! $order || ! $order->get_meta( 'zcrm_sales_order_id' ) ) {
413 return;
414 }
415
416 $zcrm_sales_order_id = $order->get_meta( 'zcrm_sales_order_id' );
417 $url = $this->zoho_crm_url . 'crm/v7/Sales_Orders/' . $zcrm_sales_order_id . '/actions/cancel';
418 $api = new \CMBIRD_API_Handler_Zoho();
419 $response = $api->execute_curl_call_post( $url, array() );
420
421 if ( is_wp_error( $response ) ) {
422 error_log( 'Zoho Sales Order Cancellation Failed: ' . $response->get_error_message() );
423 return;
424 }
425
426 // Optionally, update order status or add a note
427 $order->update_status( 'cancelled', 'Zoho Sales Order cancelled.' );
428 }
429 }
430 $CMBIRD_ZCRM_SalesOrder = new CMBIRD_ZCRM_SalesOrder();
431