Changeset 3454574
- Timestamp:
- 02/05/2026 11:42:21 AM (2 weeks ago)
- Location:
- shift8-integration-for-gravity-forms-and-sap-business-one/trunk
- Files:
-
- 4 edited
-
cli-test-submission.php (modified) (1 diff)
-
includes/class-shift8-gravitysap-sap-service.php (modified) (2 diffs)
-
readme.txt (modified) (3 diffs)
-
shift8-gravitysap.php (modified) (17 diffs)
Legend:
- Unmodified
- Added
- Removed
-
shift8-integration-for-gravity-forms-and-sap-business-one/trunk/cli-test-submission.php
r3454222 r3454574 1322 1322 1323 1323 WP_CLI::add_command('shift8-gravitysap-masterdata', 'Shift8_GravitySAP_MasterData_Command'); 1324 1325 /** 1326 * Test Business Partner lookup for duplicate detection 1327 * 1328 * This command tests the performance and accuracy of looking up existing 1329 * Business Partners in SAP B1 based on name, country, and postal code. 1330 */ 1331 class Shift8_GravitySAP_BP_Lookup_Command { 1332 1333 /** 1334 * Search for existing Business Partners matching criteria 1335 * 1336 * ## OPTIONS 1337 * 1338 * --name=<name> 1339 * : Business Partner name to search for (case-insensitive) 1340 * 1341 * --country=<country> 1342 * : 2-letter country code (e.g., US, CA, GB) 1343 * 1344 * --postal=<postal> 1345 * : Postal/ZIP code 1346 * 1347 * [--verbose] 1348 * : Show detailed timing and query information 1349 * 1350 * ## EXAMPLES 1351 * 1352 * wp shift8-gravitysap-bp-lookup search --name="Test Company" --country=US --postal=12345 1353 * wp shift8-gravitysap-bp-lookup search --name="Acme Corp" --country=CA --postal="M5V 1A1" --verbose 1354 * 1355 * @param array $args 1356 * @param array $assoc_args 1357 */ 1358 public function search($args, $assoc_args) { 1359 $name = isset($assoc_args['name']) ? sanitize_text_field($assoc_args['name']) : ''; 1360 $country = isset($assoc_args['country']) ? strtoupper(sanitize_text_field($assoc_args['country'])) : ''; 1361 $postal = isset($assoc_args['postal']) ? sanitize_text_field($assoc_args['postal']) : ''; 1362 $verbose = isset($assoc_args['verbose']); 1363 1364 if (empty($name)) { 1365 WP_CLI::error('Please specify a Business Partner name: --name="Company Name"'); 1366 return; 1367 } 1368 1369 if (empty($country)) { 1370 WP_CLI::error('Please specify a country code: --country=US'); 1371 return; 1372 } 1373 1374 if (empty($postal)) { 1375 WP_CLI::error('Please specify a postal code: --postal=12345'); 1376 return; 1377 } 1378 1379 WP_CLI::line(''); 1380 WP_CLI::line('=== Business Partner Lookup Test ==='); 1381 WP_CLI::line(''); 1382 WP_CLI::line('Search Criteria:'); 1383 WP_CLI::line(" Name: {$name} (case-insensitive)"); 1384 WP_CLI::line(" Country: {$country}"); 1385 WP_CLI::line(" Postal: {$postal}"); 1386 WP_CLI::line(''); 1387 1388 try { 1389 // Get SAP settings 1390 $sap_settings = get_option('shift8_gravitysap_settings', array()); 1391 1392 if (empty($sap_settings['sap_endpoint']) || empty($sap_settings['sap_username']) || empty($sap_settings['sap_password'])) { 1393 WP_CLI::error('SAP connection settings not configured.'); 1394 return; 1395 } 1396 1397 // Decrypt password 1398 $sap_settings['sap_password'] = shift8_gravitysap_decrypt_password($sap_settings['sap_password']); 1399 1400 // Create SAP service 1401 require_once plugin_dir_path(__FILE__) . 'includes/class-shift8-gravitysap-sap-service.php'; 1402 $sap_service = new Shift8_GravitySAP_SAP_Service($sap_settings); 1403 1404 // Authenticate 1405 $reflection = new ReflectionClass($sap_service); 1406 $auth_method = $reflection->getMethod('ensure_authenticated'); 1407 $auth_method->setAccessible(true); 1408 1409 $auth_start = microtime(true); 1410 if (!$auth_method->invoke($sap_service)) { 1411 WP_CLI::error('Failed to authenticate with SAP B1'); 1412 return; 1413 } 1414 $auth_time = round((microtime(true) - $auth_start) * 1000, 2); 1415 1416 if ($verbose) { 1417 WP_CLI::line("Authentication time: {$auth_time}ms"); 1418 } 1419 1420 // Perform the lookup using centralized method 1421 $lookup_start = microtime(true); 1422 $result = $sap_service->find_existing_business_partner($name, $country, $postal); 1423 $lookup_time = round((microtime(true) - $lookup_start) * 1000, 2); 1424 1425 WP_CLI::line(''); 1426 WP_CLI::line(str_repeat('-', 60)); 1427 WP_CLI::line(''); 1428 1429 if ($result['found']) { 1430 WP_CLI::success("Found matching Business Partner!"); 1431 WP_CLI::line(''); 1432 WP_CLI::line(" CardCode: {$result['card_code']}"); 1433 WP_CLI::line(" CardName: {$result['card_name']}"); 1434 if (!empty($result['address_country'])) { 1435 WP_CLI::line(" Country: {$result['address_country']}"); 1436 } 1437 if (!empty($result['address_postal'])) { 1438 WP_CLI::line(" Postal: {$result['address_postal']}"); 1439 } 1440 } else { 1441 WP_CLI::warning("No matching Business Partner found."); 1442 WP_CLI::line(" A new Business Partner would be created for this submission."); 1443 } 1444 1445 WP_CLI::line(''); 1446 WP_CLI::line('Performance Metrics:'); 1447 WP_CLI::line(" Authentication: {$auth_time}ms"); 1448 WP_CLI::line(" Lookup Query: {$lookup_time}ms"); 1449 WP_CLI::line(" Total Time: " . round($auth_time + $lookup_time, 2) . "ms"); 1450 1451 if (isset($result['records_scanned'])) { 1452 WP_CLI::line(" Records Scanned: {$result['records_scanned']}"); 1453 } 1454 1455 WP_CLI::line(''); 1456 1457 // Performance recommendation 1458 $total_time = $auth_time + $lookup_time; 1459 if ($total_time > 3000) { 1460 WP_CLI::warning("Lookup time exceeds 3 seconds. Consider moving SAP processing to a background cron job."); 1461 } elseif ($total_time > 1000) { 1462 WP_CLI::line("Note: Lookup time is moderate. For high-traffic forms, consider async processing."); 1463 } else { 1464 WP_CLI::success("Lookup time is acceptable for synchronous processing."); 1465 } 1466 1467 WP_CLI::line(''); 1468 1469 } catch (Exception $e) { 1470 WP_CLI::error('Error: ' . $e->getMessage()); 1471 } 1472 } 1473 1474 /** 1475 * Find an existing Business Partner matching the criteria 1476 * 1477 * SAP B1 Service Layer has limited OData support - it doesn't support: 1478 * - tolower() for case-insensitive comparisons 1479 * - any() for filtering on collections 1480 * 1481 * Strategy: Query BPs with CardName containing the search term, then filter 1482 * client-side for exact match (case-insensitive) and address criteria. 1483 * 1484 * @param ReflectionMethod $request_method The SAP make_request method 1485 * @param object $sap_service The SAP service instance 1486 * @param string $name Business Partner name (case-insensitive) 1487 * @param string $country 2-letter country code 1488 /** 1489 * Benchmark the lookup performance with multiple queries 1490 * 1491 * ## OPTIONS 1492 * 1493 * [--iterations=<num>] 1494 * : Number of test iterations (default: 5) 1495 * 1496 * ## EXAMPLES 1497 * 1498 * wp shift8-gravitysap-bp-lookup benchmark 1499 * wp shift8-gravitysap-bp-lookup benchmark --iterations=10 1500 * 1501 * @param array $args 1502 * @param array $assoc_args 1503 */ 1504 public function benchmark($args, $assoc_args) { 1505 $iterations = isset($assoc_args['iterations']) ? absint($assoc_args['iterations']) : 5; 1506 1507 if ($iterations < 1 || $iterations > 20) { 1508 WP_CLI::error('Iterations must be between 1 and 20'); 1509 return; 1510 } 1511 1512 WP_CLI::line(''); 1513 WP_CLI::line('=== Business Partner Lookup Benchmark ==='); 1514 WP_CLI::line(''); 1515 WP_CLI::line("Running {$iterations} iterations with sample queries..."); 1516 WP_CLI::line(''); 1517 1518 try { 1519 // Get SAP settings 1520 $sap_settings = get_option('shift8_gravitysap_settings', array()); 1521 1522 if (empty($sap_settings['sap_endpoint'])) { 1523 WP_CLI::error('SAP connection settings not configured.'); 1524 return; 1525 } 1526 1527 // Decrypt password 1528 $sap_settings['sap_password'] = shift8_gravitysap_decrypt_password($sap_settings['sap_password']); 1529 1530 // Create SAP service 1531 require_once plugin_dir_path(__FILE__) . 'includes/class-shift8-gravitysap-sap-service.php'; 1532 $sap_service = new Shift8_GravitySAP_SAP_Service($sap_settings); 1533 1534 // Authenticate once 1535 $reflection = new ReflectionClass($sap_service); 1536 $auth_method = $reflection->getMethod('ensure_authenticated'); 1537 $auth_method->setAccessible(true); 1538 1539 if (!$auth_method->invoke($sap_service)) { 1540 WP_CLI::error('Failed to authenticate with SAP B1'); 1541 return; 1542 } 1543 1544 $request_method = $reflection->getMethod('make_request'); 1545 $request_method->setAccessible(true); 1546 1547 // Run benchmark queries (simple count query, no filtering) 1548 $times = array(); 1549 1550 for ($i = 1; $i <= $iterations; $i++) { 1551 $start = microtime(true); 1552 1553 // Simple query to measure baseline latency 1554 $response = $request_method->invoke( 1555 $sap_service, 1556 'GET', 1557 '/BusinessPartners?$top=1&$select=CardCode,CardName' 1558 ); 1559 1560 $elapsed = round((microtime(true) - $start) * 1000, 2); 1561 $times[] = $elapsed; 1562 1563 $status = is_wp_error($response) ? 'ERROR' : 'OK'; 1564 WP_CLI::line(" Iteration {$i}: {$elapsed}ms ({$status})"); 1565 } 1566 1567 // Calculate statistics 1568 $avg = round(array_sum($times) / count($times), 2); 1569 $min = round(min($times), 2); 1570 $max = round(max($times), 2); 1571 1572 WP_CLI::line(''); 1573 WP_CLI::line(str_repeat('-', 40)); 1574 WP_CLI::line(''); 1575 WP_CLI::line('Results:'); 1576 WP_CLI::line(" Average: {$avg}ms"); 1577 WP_CLI::line(" Min: {$min}ms"); 1578 WP_CLI::line(" Max: {$max}ms"); 1579 WP_CLI::line(''); 1580 1581 // Recommendations 1582 if ($avg > 2000) { 1583 WP_CLI::warning("Average response time > 2s. Strongly recommend async processing via cron."); 1584 } elseif ($avg > 500) { 1585 WP_CLI::line("Average response time is moderate. Consider async processing for better UX."); 1586 } else { 1587 WP_CLI::success("Response times are good for synchronous processing."); 1588 } 1589 1590 WP_CLI::line(''); 1591 1592 } catch (Exception $e) { 1593 WP_CLI::error('Error: ' . $e->getMessage()); 1594 } 1595 } 1596 } 1597 1598 WP_CLI::add_command('shift8-gravitysap-bp-lookup', 'Shift8_GravitySAP_BP_Lookup_Command'); -
shift8-integration-for-gravity-forms-and-sap-business-one/trunk/includes/class-shift8-gravitysap-sap-service.php
r3375274 r3454574 247 247 throw new Exception('SAP Business Partner creation failed: ' . esc_html($error_message)); 248 248 } 249 } 250 251 /** 252 * Find an existing Contact Person on a Business Partner by name and/or email 253 * 254 * Searches the Business Partner's ContactEmployees for a match using case-insensitive 255 * comparison on Name AND Email (both must match if both are provided). 256 * 257 * @since 1.4.4 258 * @param string $card_code The Business Partner CardCode 259 * @param string $contact_name The contact person's full name to match 260 * @param string $contact_email The contact person's email to match (optional but recommended) 261 * @return array|null The matching contact data with InternalCode, or null if not found 262 */ 263 public function find_existing_contact($card_code, $contact_name, $contact_email = '') { 264 shift8_gravitysap_debug_log('=== SEARCHING FOR EXISTING CONTACT PERSON ===', array( 265 'CardCode' => $card_code, 266 'SearchName' => $contact_name, 267 'SearchEmail' => $contact_email 268 )); 269 270 if (empty($card_code) || empty($contact_name)) { 271 shift8_gravitysap_debug_log('Find Contact: Missing CardCode or contact name'); 272 return null; 273 } 274 275 // Fetch the Business Partner with its contacts 276 $bp_data = $this->get_business_partner($card_code); 277 278 if (!$bp_data || empty($bp_data['ContactEmployees'])) { 279 shift8_gravitysap_debug_log('Find Contact: No existing contacts found on BP', array( 280 'CardCode' => $card_code 281 )); 282 return null; 283 } 284 285 // Normalize search values for case-insensitive comparison 286 $search_name = strtolower(trim($contact_name)); 287 $search_email = !empty($contact_email) ? strtolower(trim($contact_email)) : ''; 288 289 shift8_gravitysap_debug_log('Find Contact: Searching through contacts', array( 290 'ContactCount' => count($bp_data['ContactEmployees']), 291 'NormalizedName' => $search_name, 292 'NormalizedEmail' => $search_email 293 )); 294 295 foreach ($bp_data['ContactEmployees'] as $contact) { 296 $existing_name = isset($contact['Name']) ? strtolower(trim($contact['Name'])) : ''; 297 $existing_email = isset($contact['E_Mail']) ? strtolower(trim($contact['E_Mail'])) : ''; 298 299 // Match logic: Name must match, and if email is provided, email must also match 300 $name_matches = ($existing_name === $search_name); 301 $email_matches = empty($search_email) || ($existing_email === $search_email); 302 303 if ($name_matches && $email_matches) { 304 shift8_gravitysap_debug_log('✅ Found matching contact', array( 305 'MatchedName' => $contact['Name'] ?? 'N/A', 306 'MatchedEmail' => $contact['E_Mail'] ?? 'N/A', 307 'InternalCode' => $contact['InternalCode'] ?? 'N/A' 308 )); 309 return $contact; 310 } 311 } 312 313 shift8_gravitysap_debug_log('Find Contact: No matching contact found', array( 314 'CardCode' => $card_code, 315 'SearchedName' => $contact_name, 316 'SearchedEmail' => $contact_email 317 )); 318 319 return null; 320 } 321 322 /** 323 * Add a Contact Person to an existing Business Partner 324 * 325 * Uses PATCH to update the Business Partner with a new ContactEmployees entry. 326 * SAP B1 will append the new contact to existing contacts. 327 * 328 * @since 1.4.2 329 * @param string $card_code The Business Partner CardCode 330 * @param array $contact_data Contact person data (FirstName, LastName, Phone1, E_Mail, Address) 331 * @return array|null The contact person data with InternalCode if successful, null on failure 332 */ 333 public function add_contact_to_business_partner($card_code, $contact_data) { 334 shift8_gravitysap_debug_log('=== ADDING CONTACT TO EXISTING BUSINESS PARTNER ===', array( 335 'CardCode' => $card_code, 336 'ContactData' => $contact_data 337 )); 338 339 // Validate inputs 340 if (empty($card_code)) { 341 shift8_gravitysap_debug_log('Add Contact: Missing CardCode'); 342 return null; 343 } 344 345 if (empty($contact_data) || !is_array($contact_data)) { 346 shift8_gravitysap_debug_log('Add Contact: No contact data provided'); 347 return null; 348 } 349 350 // Ensure authenticated 351 if (!$this->ensure_authenticated()) { 352 shift8_gravitysap_debug_log('Add Contact: Authentication failed'); 353 return null; 354 } 355 356 // Build the contact person object 357 $contact_person = array(); 358 359 // Check if Name is already provided in contact_data (from map_form_data_to_sap_fields) 360 $first_name = isset($contact_data['FirstName']) ? trim($contact_data['FirstName']) : ''; 361 $last_name = isset($contact_data['LastName']) ? trim($contact_data['LastName']) : ''; 362 $existing_name = isset($contact_data['Name']) ? trim($contact_data['Name']) : ''; 363 364 if (!empty($first_name) && !empty($last_name)) { 365 $contact_person['Name'] = $first_name . ' ' . $last_name; 366 $contact_person['FirstName'] = $first_name; 367 $contact_person['LastName'] = $last_name; 368 } elseif (!empty($first_name)) { 369 $contact_person['Name'] = $first_name; 370 $contact_person['FirstName'] = $first_name; 371 } elseif (!empty($last_name)) { 372 $contact_person['Name'] = $last_name; 373 $contact_person['LastName'] = $last_name; 374 } elseif (!empty($existing_name)) { 375 // Use the pre-constructed Name if FirstName/LastName not available 376 $contact_person['Name'] = $existing_name; 377 } else { 378 // No name provided - use a default or skip 379 shift8_gravitysap_debug_log('Add Contact: No name provided, skipping contact creation'); 380 return null; 381 } 382 383 // Add optional contact fields 384 if (!empty($contact_data['Phone1'])) { 385 $contact_person['Phone1'] = $contact_data['Phone1']; 386 } 387 if (!empty($contact_data['E_Mail'])) { 388 $contact_person['E_Mail'] = $contact_data['E_Mail']; 389 } 390 if (!empty($contact_data['Address'])) { 391 $contact_person['Address'] = $contact_data['Address']; 392 } 393 394 // Build PATCH payload - SAP B1 appends to existing ContactEmployees 395 $patch_data = array( 396 'ContactEmployees' => array($contact_person) 397 ); 398 399 shift8_gravitysap_debug_log('Add Contact: Sending PATCH request', array( 400 'CardCode' => $card_code, 401 'PatchData' => $patch_data 402 )); 403 404 // Send PATCH request to update the Business Partner 405 $endpoint = "/BusinessPartners('" . rawurlencode($card_code) . "')"; 406 $response = $this->make_request('PATCH', $endpoint, $patch_data); 407 408 if (is_wp_error($response)) { 409 shift8_gravitysap_debug_log('Add Contact: PATCH request failed', array( 410 'error' => $response->get_error_message() 411 )); 412 return null; 413 } 414 415 $response_code = wp_remote_retrieve_response_code($response); 416 417 // 204 No Content is success for PATCH 418 if ($response_code === 204) { 419 shift8_gravitysap_debug_log('✅ Contact Person added successfully to ' . $card_code); 420 421 // Return the contact person data (SAP doesn't return the created contact in PATCH response) 422 // We need to fetch the BP to get the InternalCode of the newly added contact 423 $contact_person['CardCode'] = $card_code; 424 425 // Try to get the newly created contact's InternalCode by fetching the BP 426 $bp_response = $this->get_business_partner($card_code); 427 if ($bp_response && isset($bp_response['ContactEmployees'])) { 428 // Find the contact we just added (last one in the array, or match by name) 429 $contacts = $bp_response['ContactEmployees']; 430 foreach (array_reverse($contacts) as $contact) { 431 if (isset($contact['Name']) && $contact['Name'] === $contact_person['Name']) { 432 $contact_person['InternalCode'] = $contact['InternalCode'] ?? null; 433 break; 434 } 435 } 436 } 437 438 return $contact_person; 439 } else { 440 // Handle error response 441 $body = wp_remote_retrieve_body($response); 442 $error_data = json_decode($body, true); 443 444 $error_message = 'Unknown error'; 445 if (!empty($error_data['error']['message']['value'])) { 446 $error_message = $error_data['error']['message']['value']; 447 } 448 449 shift8_gravitysap_debug_log('Add Contact: PATCH failed', array( 450 'status_code' => $response_code, 451 'error' => $error_message 452 )); 453 454 return null; 455 } 456 } 457 458 /** 459 * Get a Business Partner by CardCode 460 * 461 * @since 1.4.2 462 * @param string $card_code The Business Partner CardCode 463 * @return array|null The Business Partner data or null on failure 464 */ 465 public function get_business_partner($card_code) { 466 if (empty($card_code)) { 467 return null; 468 } 469 470 if (!$this->ensure_authenticated()) { 471 return null; 472 } 473 474 $endpoint = "/BusinessPartners('" . rawurlencode($card_code) . "')"; 475 $response = $this->make_request('GET', $endpoint); 476 477 if (is_wp_error($response)) { 478 return null; 479 } 480 481 $response_code = wp_remote_retrieve_response_code($response); 482 if ($response_code === 200) { 483 $body = wp_remote_retrieve_body($response); 484 return json_decode($body, true); 485 } 486 487 return null; 249 488 } 250 489 … … 1061 1300 return false; 1062 1301 } 1302 1303 /** 1304 * Find an existing Business Partner matching name, country, and postal code 1305 * 1306 * This method searches SAP B1 for an existing Business Partner that matches: 1307 * - CardName (case-insensitive) 1308 * - Country (from BPAddresses) 1309 * - ZipCode/Postal (from BPAddresses) 1310 * 1311 * @since 1.3.9 1312 * @param string $name Business Partner name (case-insensitive comparison) 1313 * @param string $country 2-letter country code 1314 * @param string $postal Postal/ZIP code 1315 * @return array Result with 'found', 'card_code', 'card_name', etc. 1316 */ 1317 public function find_existing_business_partner($name, $country, $postal) { 1318 $result = array( 1319 'found' => false, 1320 'card_code' => null, 1321 'card_name' => null, 1322 'address_country' => null, 1323 'address_postal' => null, 1324 'records_scanned' => 0, 1325 'error' => null 1326 ); 1327 1328 // Validate inputs 1329 if (empty($name) || empty($country) || empty($postal)) { 1330 $result['error'] = 'Name, country, and postal code are all required for lookup'; 1331 shift8_gravitysap_debug_log('BP Lookup: Missing required parameters', array( 1332 'name' => !empty($name), 1333 'country' => !empty($country), 1334 'postal' => !empty($postal) 1335 )); 1336 return $result; 1337 } 1338 1339 // Ensure authenticated 1340 if (!$this->ensure_authenticated()) { 1341 $result['error'] = 'Failed to authenticate with SAP'; 1342 return $result; 1343 } 1344 1345 shift8_gravitysap_debug_log('=== STARTING BUSINESS PARTNER LOOKUP ===', array( 1346 'name' => $name, 1347 'country' => $country, 1348 'postal' => $postal 1349 )); 1350 1351 // Normalize search name for case-insensitive comparison 1352 $search_name_normalized = strtolower(trim($name)); 1353 1354 // Escape single quotes for OData query 1355 $escaped_name = str_replace("'", "''", $name); 1356 1357 // Select fields including BPAddresses 1358 $select = 'CardCode,CardName,BPAddresses'; 1359 1360 // Strategy 1: Try exact CardName match first (most efficient) 1361 $filter = "CardName eq '{$escaped_name}'"; 1362 $query = "/BusinessPartners?\$filter=" . rawurlencode($filter) . "&\$select={$select}"; 1363 1364 shift8_gravitysap_debug_log('BP Lookup Strategy 1: Exact match', array('filter' => $filter)); 1365 1366 $response = $this->make_request('GET', $query); 1367 $exact_matches = $this->parse_bp_lookup_response($response); 1368 1369 foreach ($exact_matches as $bp) { 1370 if ($this->bp_has_matching_address($bp, $country, $postal)) { 1371 $result['found'] = true; 1372 $result['card_code'] = $bp['CardCode']; 1373 $result['card_name'] = $bp['CardName']; 1374 $result['address_country'] = $country; 1375 $result['address_postal'] = $postal; 1376 $result['records_scanned'] = count($exact_matches); 1377 1378 shift8_gravitysap_debug_log('BP Lookup: MATCH FOUND (exact)', array( 1379 'card_code' => $bp['CardCode'], 1380 'card_name' => $bp['CardName'] 1381 )); 1382 return $result; 1383 } 1384 } 1385 1386 // Strategy 2: Broader search with 'startswith' for case variations 1387 $first_word = explode(' ', trim($name))[0]; 1388 $escaped_first_word = str_replace("'", "''", $first_word); 1389 1390 if (strlen($first_word) >= 3) { 1391 $filter = "startswith(CardName, '{$escaped_first_word}')"; 1392 $query = "/BusinessPartners?\$filter=" . rawurlencode($filter) . "&\$select={$select}&\$top=100"; 1393 1394 shift8_gravitysap_debug_log('BP Lookup Strategy 2: Startswith', array('filter' => $filter)); 1395 1396 $response = $this->make_request('GET', $query); 1397 $startswith_matches = $this->parse_bp_lookup_response($response); 1398 1399 $result['records_scanned'] += count($startswith_matches); 1400 1401 foreach ($startswith_matches as $bp) { 1402 $bp_name_normalized = strtolower(trim($bp['CardName'] ?? '')); 1403 1404 // Case-insensitive name match AND address match 1405 if ($bp_name_normalized === $search_name_normalized && 1406 $this->bp_has_matching_address($bp, $country, $postal)) { 1407 1408 $result['found'] = true; 1409 $result['card_code'] = $bp['CardCode']; 1410 $result['card_name'] = $bp['CardName']; 1411 $result['address_country'] = $country; 1412 $result['address_postal'] = $postal; 1413 1414 shift8_gravitysap_debug_log('BP Lookup: MATCH FOUND (case-insensitive)', array( 1415 'card_code' => $bp['CardCode'], 1416 'card_name' => $bp['CardName'] 1417 )); 1418 return $result; 1419 } 1420 } 1421 } 1422 1423 shift8_gravitysap_debug_log('BP Lookup: No match found', array( 1424 'records_scanned' => $result['records_scanned'] 1425 )); 1426 1427 return $result; 1428 } 1429 1430 /** 1431 * Parse Business Partner lookup response 1432 * 1433 * @since 1.3.9 1434 * @param mixed $response HTTP response 1435 * @return array Array of Business Partners 1436 */ 1437 private function parse_bp_lookup_response($response) { 1438 if (is_wp_error($response)) { 1439 shift8_gravitysap_debug_log('BP Lookup query failed', array( 1440 'error' => $response->get_error_message() 1441 )); 1442 return array(); 1443 } 1444 1445 $code = wp_remote_retrieve_response_code($response); 1446 if ($code !== 200) { 1447 $body = wp_remote_retrieve_body($response); 1448 $error_data = json_decode($body, true); 1449 shift8_gravitysap_debug_log('BP Lookup query error', array( 1450 'http_code' => $code, 1451 'error' => $error_data['error']['message']['value'] ?? 'Unknown error' 1452 )); 1453 return array(); 1454 } 1455 1456 $body = wp_remote_retrieve_body($response); 1457 $data = json_decode($body, true); 1458 1459 if (!isset($data['value'])) { 1460 return array(); 1461 } 1462 1463 shift8_gravitysap_debug_log('BP Lookup query result', array( 1464 'count' => count($data['value']) 1465 )); 1466 1467 return $data['value']; 1468 } 1469 1470 /** 1471 * Check if Business Partner has matching address 1472 * 1473 * @since 1.3.9 1474 * @param array $bp Business Partner data 1475 * @param string $country Country code 1476 * @param string $postal Postal code 1477 * @return bool True if address matches 1478 */ 1479 private function bp_has_matching_address($bp, $country, $postal) { 1480 if (!isset($bp['BPAddresses']) || !is_array($bp['BPAddresses'])) { 1481 return false; 1482 } 1483 1484 foreach ($bp['BPAddresses'] as $addr) { 1485 $addr_country = $addr['Country'] ?? ''; 1486 $addr_postal = $addr['ZipCode'] ?? ''; 1487 1488 if ($addr_country === $country && $addr_postal === $postal) { 1489 return true; 1490 } 1491 } 1492 1493 return false; 1494 } 1063 1495 } -
shift8-integration-for-gravity-forms-and-sap-business-one/trunk/readme.txt
r3454441 r3454574 5 5 * Requires at least: 5.0 6 6 * Tested up to: 6.8 7 * Stable tag: 1. 3.87 * Stable tag: 1.4.4 8 8 * Requires PHP: 7.4 9 9 * License: GPLv3 … … 96 96 This is ideal for sample request forms, multi-product orders, and service selection forms. 97 97 98 = How do I prevent duplicate Business Partners? = 99 100 Enable **Check for existing Business Partner** in your form's SAP Integration settings. The plugin will search SAP B1 for existing Business Partners matching: 101 102 * Business Partner Name (case-insensitive) 103 * Country (from address) 104 * Postal/ZIP Code (from address) 105 106 If a match is found, the plugin uses the existing Business Partner instead of creating a duplicate. You can test this with WP-CLI: 107 108 `wp shift8-gravitysap-bp-lookup search --name="Test Company" --country="CA" --postal="M5V 1A1"` 109 110 = How are Contact Persons linked to Sales Quotations? = 111 112 When a Business Partner match is found (or a new one is created), the plugin automatically: 113 114 1. Adds a Contact Person to the Business Partner using the form's contact data 115 2. Retrieves the Contact Person's InternalCode from SAP 116 3. Links the Contact Person to the Sales Quotation via SAP's ContactPersonCode field 117 118 **Result in SAP B1:** 119 * Contact Person appears in the Business Partner's "Contact Persons" tab 120 * Sales Quotation dropdown shows the correct Contact Person selected 121 * Contact Person is properly linked for subsequent documents 122 123 **Technical Note:** SAP B1's ContactPersonCode field requires the numeric InternalCode, not the text Name. The plugin handles this automatically. 124 98 125 == Screenshots == 99 126 … … 104 131 105 132 == Changelog == 133 134 = 1.4.4 = 135 * **NEW**: Duplicate contact detection - checks if contact already exists before adding 136 * **NEW**: `find_existing_contact()` method for case-insensitive name + email matching 137 * **IMPROVED**: Reuses existing contacts instead of creating duplicates on repeat submissions 138 * **TESTING**: Added 10 new tests for contact duplicate detection (edge cases included) 139 * **TESTING**: Now 138 tests with 306 assertions - All passing 140 141 = 1.4.3 = 142 * **FIX**: Contact Person now correctly linked to Sales Quotation using SAP's InternalCode (numeric) instead of Name (string) 143 * **IMPROVED**: After adding Contact Person to existing BP, plugin now fetches BP to retrieve the contact's InternalCode 144 * **IMPROVED**: Sales Quotation ContactPersonCode field now uses correct integer value for proper SAP B1 linking 145 * **TESTING**: Added 3 additional Contact Person tests (InternalCode retrieval, existing Name field, first name only) 146 * **TESTING**: Now 128 tests with 291 assertions - All passing 147 * **DOCUMENTATION**: Added comprehensive contactPersonLinking section to .cursorrules 148 149 = 1.4.2 = 150 * **NEW**: Contact Person now added to existing Business Partner when match is found 151 * **NEW**: Contact Person linked to Sales Quotation via ContactPersonCode field 152 * **IMPROVED**: SAP Service class now includes `add_contact_to_business_partner()` and `get_business_partner()` methods 153 * **TESTING**: Added 6 new unit tests for Contact Person functionality 154 * **TESTING**: Now 125 tests with 283 assertions - All passing 155 156 = 1.4.1 = 157 * **TESTING**: Added comprehensive test coverage for async processing (9 new tests) 158 * **TESTING**: Added comprehensive test coverage for Business Partner lookup (10 new tests) 159 * **TESTING**: Now 119 tests with 272 assertions - All passing 160 * **DOCUMENTATION**: Updated .cursorrules with async processing, BP lookup, and testing patterns 161 162 = 1.4.0 = 163 * **NEW**: Async processing for form submissions - SAP integration now runs in a non-blocking background request 164 * **NEW**: "Check for Existing Business Partner" now integrated into form submission flow 165 * **NEW**: Test Integration button now supports existing BP lookup when enabled 166 * **IMPROVED**: Form submissions no longer block on SAP API response time (1-2 seconds faster) 167 * **IMPROVED**: Centralized Business Partner lookup logic for code reuse across WP-CLI, form processing, and test integration 168 169 = 1.3.9 = 170 * **NEW**: Added "Check for Existing Business Partner" toggle setting 171 * **NEW**: Added WP-CLI command `wp shift8-gravitysap-bp-lookup search` to test duplicate detection 172 * **NEW**: Added WP-CLI command `wp shift8-gravitysap-bp-lookup benchmark` to measure SAP query performance 173 * **ENHANCEMENT**: Duplicate detection matches on Business Partner Name (case-insensitive), Country, and Postal Code 106 174 107 175 = 1.3.8 = -
shift8-integration-for-gravity-forms-and-sap-business-one/trunk/shift8-gravitysap.php
r3454441 r3454574 4 4 * Plugin URI: https://github.com/stardothosting/shift8-gravitysap 5 5 * Description: Integrates Gravity Forms with SAP Business One, automatically creating Business Partners from form submissions. 6 * Version: 1. 3.86 * Version: 1.4.4 7 7 * Author: Shift8 Web 8 8 * Author URI: https://shift8web.ca … … 28 28 29 29 // Plugin constants 30 define('SHIFT8_GRAVITYSAP_VERSION', '1. 3.8');30 define('SHIFT8_GRAVITYSAP_VERSION', '1.4.4'); 31 31 define('SHIFT8_GRAVITYSAP_PLUGIN_FILE', __FILE__); 32 32 define('SHIFT8_GRAVITYSAP_PLUGIN_DIR', plugin_dir_path(__FILE__)); … … 230 230 add_action('wp_ajax_load_itemcodes', array($this, 'ajax_load_itemcodes')); 231 231 add_action('admin_footer', array($this, 'add_retry_button_script')); 232 233 // Async SAP processing endpoint (accessible without login for loopback requests) 234 add_action('wp_ajax_shift8_gravitysap_async_process', array($this, 'ajax_async_sap_process')); 235 add_action('wp_ajax_nopriv_shift8_gravitysap_async_process', array($this, 'ajax_async_sap_process')); 232 236 233 237 … … 525 529 <p class="description"> 526 530 <?php esc_html_e('When enabled, a Sales Quotation will be created and linked to the newly created Business Partner. Configure quotation field mapping below.', 'shift8-gravity-forms-sap-b1-integration'); ?> 531 </p> 532 </td> 533 </tr> 534 535 <tr> 536 <th scope="row"> 537 <label for="sap_check_existing_bp"><?php esc_html_e('Check for Existing Business Partner', 'shift8-gravity-forms-sap-b1-integration'); ?></label> 538 </th> 539 <td> 540 <input type="checkbox" id="sap_check_existing_bp" name="sap_check_existing_bp" value="1" <?php checked(rgar($settings, 'check_existing_bp'), '1'); ?> /> 541 <label for="sap_check_existing_bp"><?php esc_html_e('Check if a Business Partner already exists before creating a new one', 'shift8-gravity-forms-sap-b1-integration'); ?></label> 542 <p class="description"> 543 <?php esc_html_e('When enabled, the system will search SAP for an existing Business Partner matching:', 'shift8-gravity-forms-sap-b1-integration'); ?> 544 <br> 545 <strong>•</strong> <?php esc_html_e('Business Partner Name (case-insensitive)', 'shift8-gravity-forms-sap-b1-integration'); ?> 546 <br> 547 <strong>•</strong> <?php esc_html_e('Country', 'shift8-gravity-forms-sap-b1-integration'); ?> 548 <br> 549 <strong>•</strong> <?php esc_html_e('Postal/ZIP Code', 'shift8-gravity-forms-sap-b1-integration'); ?> 550 <br><br> 551 <?php esc_html_e('If a match is found, a Sales Quotation will be added to the existing Business Partner instead of creating a new one.', 'shift8-gravity-forms-sap-b1-integration'); ?> 527 552 </p> 528 553 </td> … … 1496 1521 'card_code_prefix' => sanitize_text_field(rgpost('sap_card_code_prefix')), 1497 1522 'create_quotation' => rgpost('sap_create_quotation') === '1' ? '1' : '0', 1523 'check_existing_bp' => rgpost('sap_check_existing_bp') === '1' ? '1' : '0', 1498 1524 'field_mapping' => array(), 1499 1525 'quotation_field_mapping' => array(), … … 1600 1626 $settings = rgar($form, 'sap_integration_settings'); 1601 1627 1602 // Initialize status tracking1603 if (!empty($entry['id'])) {1604 $this->update_entry_sap_status($entry['id'], 'processing', '', '');1605 }1606 1607 1628 if (empty($settings['enabled']) || $settings['enabled'] !== '1') { 1608 1629 if (!empty($entry['id'])) { … … 1611 1632 return; 1612 1633 } 1634 1635 // Get plugin settings (SAP connection details) 1636 $plugin_settings = get_option('shift8_gravitysap_settings', array()); 1637 1638 // Check if connection settings are configured 1639 if (empty($plugin_settings['sap_endpoint']) || empty($plugin_settings['sap_username']) || empty($plugin_settings['sap_password'])) { 1640 $this->update_entry_sap_status($entry['id'], 'failed', '', 'SAP connection settings incomplete'); 1641 shift8_gravitysap_debug_log('SAP connection settings INCOMPLETE'); 1642 return; 1643 } 1644 1645 // Set initial status to pending (will be processed async) 1646 $this->update_entry_sap_status($entry['id'], 'pending', '', ''); 1647 1648 // Generate a secure token for async processing 1649 $async_token = wp_generate_password(32, false); 1650 gform_update_meta($entry['id'], 'sap_async_token', wp_hash($async_token)); 1651 1652 // Fire non-blocking loopback request to process SAP integration asynchronously 1653 $this->fire_async_sap_process($entry['id'], $form['id'], $async_token); 1654 1655 shift8_gravitysap_debug_log('🚀 SAP async processing initiated', array( 1656 'entry_id' => $entry['id'], 1657 'form_id' => $form['id'] 1658 )); 1659 } 1660 1661 /** 1662 * Fire non-blocking loopback request for async SAP processing 1663 * 1664 * @since 1.4.0 1665 * @param int $entry_id Entry ID 1666 * @param int $form_id Form ID 1667 * @param string $async_token Security token for verification 1668 */ 1669 private function fire_async_sap_process($entry_id, $form_id, $async_token) { 1670 $ajax_url = admin_url('admin-ajax.php'); 1671 1672 $args = array( 1673 'timeout' => 0.01, // Essentially non-blocking 1674 'blocking' => false, // Don't wait for response 1675 'sslverify' => apply_filters('https_local_ssl_verify', false), 1676 'body' => array( 1677 'action' => 'shift8_gravitysap_async_process', 1678 'entry_id' => $entry_id, 1679 'form_id' => $form_id, 1680 'async_token' => $async_token, 1681 ), 1682 ); 1683 1684 wp_remote_post($ajax_url, $args); 1685 } 1686 1687 /** 1688 * AJAX handler for async SAP processing 1689 * 1690 * Processes the SAP integration asynchronously via loopback request. 1691 * This is called by fire_async_sap_process() and runs in a separate PHP process. 1692 * 1693 * @since 1.4.0 1694 */ 1695 public function ajax_async_sap_process() { 1696 // Verify request parameters 1697 $entry_id = isset($_POST['entry_id']) ? absint(wp_unslash($_POST['entry_id'])) : 0; 1698 $form_id = isset($_POST['form_id']) ? absint(wp_unslash($_POST['form_id'])) : 0; 1699 $async_token = isset($_POST['async_token']) ? sanitize_text_field(wp_unslash($_POST['async_token'])) : ''; 1700 1701 if (empty($entry_id) || empty($form_id) || empty($async_token)) { 1702 shift8_gravitysap_debug_log('❌ Async SAP process: Missing required parameters'); 1703 wp_die(); 1704 } 1705 1706 // Verify the async token 1707 $stored_token_hash = gform_get_meta($entry_id, 'sap_async_token'); 1708 if (empty($stored_token_hash) || !hash_equals($stored_token_hash, wp_hash($async_token))) { 1709 shift8_gravitysap_debug_log('❌ Async SAP process: Invalid token', array( 1710 'entry_id' => $entry_id 1711 )); 1712 wp_die(); 1713 } 1714 1715 // Clear the token (single use) 1716 gform_delete_meta($entry_id, 'sap_async_token'); 1717 1718 shift8_gravitysap_debug_log('=== ASYNC SAP PROCESSING STARTED ===', array( 1719 'entry_id' => $entry_id, 1720 'form_id' => $form_id 1721 )); 1722 1723 // Get entry and form data 1724 $entry = GFAPI::get_entry($entry_id); 1725 $form = GFAPI::get_form($form_id); 1726 1727 if (is_wp_error($entry) || !$form) { 1728 shift8_gravitysap_debug_log('❌ Async SAP process: Failed to load entry or form', array( 1729 'entry_id' => $entry_id, 1730 'form_id' => $form_id 1731 )); 1732 wp_die(); 1733 } 1734 1735 // Process the SAP integration synchronously (we're now in async context) 1736 $this->process_sap_integration_sync($entry, $form); 1737 1738 wp_die(); 1739 } 1740 1741 /** 1742 * Synchronous SAP integration processing 1743 * 1744 * This is the actual SAP processing logic, called either by async handler 1745 * or directly for retry/testing purposes. 1746 * 1747 * @since 1.4.0 1748 * @param array $entry Entry data 1749 * @param array $form Form data 1750 */ 1751 public function process_sap_integration_sync($entry, $form) { 1752 $settings = rgar($form, 'sap_integration_settings'); 1613 1753 1614 1754 try { … … 1618 1758 throw new Exception('SAP connection settings are incomplete - check plugin settings'); 1619 1759 } 1760 1761 // Set status to processing 1762 $this->update_entry_sap_status($entry['id'], 'processing', '', ''); 1620 1763 1621 1764 // STEP 2: Validate required fields BEFORE attempting SAP connection … … 1653 1796 $sap_service = new Shift8_GravitySAP_SAP_Service($plugin_settings); 1654 1797 1655 // STEP 6: Create Business Partner in SAP 1656 $result = $sap_service->create_business_partner($business_partner_data); 1657 1658 // STEP 7: Handle success 1659 if ($result && isset($result['CardCode'])) { 1660 $this->update_entry_sap_status($entry['id'], 'success', $result['CardCode'], ''); 1661 1662 // Add success note to entry 1798 // STEP 6: Check for existing Business Partner if enabled 1799 $existing_card_code = null; 1800 if (rgar($settings, 'check_existing_bp') === '1') { 1801 $existing_card_code = $this->check_for_existing_business_partner($sap_service, $business_partner_data); 1802 } 1803 1804 // STEP 7: Use existing CardCode or create new Business Partner 1805 // Initialize contact person info for use in Sales Quotation 1806 // SAP B1 uses InternalCode (numeric) for ContactPersonCode on documents 1807 $contact_person_code = null; 1808 $contact_person_name = null; 1809 1810 if ($existing_card_code) { 1811 // Use existing Business Partner 1812 $card_code = $existing_card_code; 1813 1814 // Handle Contact Person for existing Business Partner 1815 if (!empty($business_partner_data['ContactEmployees']) && is_array($business_partner_data['ContactEmployees'])) { 1816 $contact_data = $business_partner_data['ContactEmployees'][0]; 1817 1818 // Build contact name for matching 1819 $search_name = ''; 1820 if (!empty($contact_data['Name'])) { 1821 $search_name = $contact_data['Name']; 1822 } elseif (!empty($contact_data['FirstName']) || !empty($contact_data['LastName'])) { 1823 $search_name = trim(($contact_data['FirstName'] ?? '') . ' ' . ($contact_data['LastName'] ?? '')); 1824 } 1825 $search_email = $contact_data['E_Mail'] ?? ''; 1826 1827 shift8_gravitysap_debug_log('Checking for existing Contact Person', array( 1828 'CardCode' => $card_code, 1829 'SearchName' => $search_name, 1830 'SearchEmail' => $search_email 1831 )); 1832 1833 // First, check if contact already exists (case-insensitive name + email match) 1834 $existing_contact = null; 1835 if (!empty($search_name)) { 1836 $existing_contact = $sap_service->find_existing_contact($card_code, $search_name, $search_email); 1837 } 1838 1839 if ($existing_contact) { 1840 // Use existing contact 1841 $contact_person_name = $existing_contact['Name'] ?? null; 1842 $contact_person_code = $existing_contact['InternalCode'] ?? null; 1843 1844 GFFormsModel::add_note( 1845 $entry['id'], 1846 0, 1847 'Shift8 SAP Integration', 1848 sprintf( 1849 /* translators: 1: Contact name, 2: CardCode, 3: InternalCode */ 1850 esc_html__('👤 Found existing Contact Person "%1$s" (Code: %3$s) on Business Partner %2$s', 'shift8-gravity-forms-sap-b1-integration'), 1851 esc_html($contact_person_name ?? 'Unknown'), 1852 esc_html($card_code), 1853 esc_html($contact_person_code ?? 'N/A') 1854 ) 1855 ); 1856 1857 shift8_gravitysap_debug_log('✅ Using existing Contact Person', array( 1858 'ContactName' => $contact_person_name, 1859 'ContactPersonCode' => $contact_person_code, 1860 'CardCode' => $card_code 1861 )); 1862 } else { 1863 // No existing contact found - add new one 1864 shift8_gravitysap_debug_log('No existing contact found, adding new Contact Person', array( 1865 'CardCode' => $card_code, 1866 'ContactData' => $contact_data 1867 )); 1868 1869 $contact_result = $sap_service->add_contact_to_business_partner($card_code, $contact_data); 1870 1871 if ($contact_result) { 1872 $contact_person_name = $contact_result['Name'] ?? null; 1873 // SAP B1 uses InternalCode (numeric) for ContactPersonCode on documents 1874 $contact_person_code = $contact_result['InternalCode'] ?? null; 1875 1876 GFFormsModel::add_note( 1877 $entry['id'], 1878 0, 1879 'Shift8 SAP Integration', 1880 sprintf( 1881 /* translators: 1: Contact name, 2: CardCode, 3: InternalCode */ 1882 esc_html__('👤 Contact Person "%1$s" (Code: %3$s) added to existing Business Partner %2$s', 'shift8-gravity-forms-sap-b1-integration'), 1883 esc_html($contact_person_name ?? 'Unknown'), 1884 esc_html($card_code), 1885 esc_html($contact_person_code ?? 'N/A') 1886 ) 1887 ); 1888 1889 shift8_gravitysap_debug_log('✅ Contact Person added successfully', array( 1890 'ContactName' => $contact_person_name, 1891 'ContactPersonCode' => $contact_person_code, 1892 'CardCode' => $card_code, 1893 'InternalCode' => $contact_result['InternalCode'] ?? 'N/A' 1894 )); 1895 } else { 1896 // Log warning but don't fail - contact is optional 1897 shift8_gravitysap_debug_log('⚠️ Could not add Contact Person to existing BP (non-fatal)', array( 1898 'CardCode' => $card_code, 1899 'ContactData' => $contact_data 1900 )); 1901 1902 GFFormsModel::add_note( 1903 $entry['id'], 1904 0, 1905 'Shift8 SAP Integration', 1906 sprintf( 1907 /* translators: %s: CardCode */ 1908 esc_html__('⚠️ Could not add Contact Person to existing Business Partner %s (Sales Quotation will proceed without contact)', 'shift8-gravity-forms-sap-b1-integration'), 1909 esc_html($card_code) 1910 ) 1911 ); 1912 } 1913 } 1914 } 1915 1916 $this->update_entry_sap_status($entry['id'], 'success', $card_code, ''); 1917 1918 GFFormsModel::add_note( 1919 $entry['id'], 1920 0, 1921 'Shift8 SAP Integration', 1922 sprintf( 1923 /* translators: %s: SAP CardCode */ 1924 esc_html__('🔗 MATCHED: Found existing Business Partner in SAP with CardCode: %s', 'shift8-gravity-forms-sap-b1-integration'), 1925 esc_html($card_code) 1926 ) 1927 ); 1928 1929 shift8_gravitysap_debug_log('🔗 MATCHED EXISTING BUSINESS PARTNER', array( 1930 'entry_id' => $entry['id'], 1931 'CardCode' => $card_code 1932 )); 1933 } else { 1934 // Create new Business Partner in SAP 1935 $result = $sap_service->create_business_partner($business_partner_data); 1936 1937 if (!$result || !isset($result['CardCode'])) { 1938 throw new Exception('SAP returned success but no CardCode - check SAP logs'); 1939 } 1940 1941 $card_code = $result['CardCode']; 1942 1943 // Extract contact person info from the created BP 1944 // The contact person is included in the BP creation - check if SAP returned the InternalCode 1945 if (!empty($result['ContactEmployees']) && is_array($result['ContactEmployees'])) { 1946 // SAP returned the ContactEmployees with InternalCodes 1947 $contact_person_name = $result['ContactEmployees'][0]['Name'] ?? null; 1948 $contact_person_code = $result['ContactEmployees'][0]['InternalCode'] ?? null; 1949 } elseif (!empty($business_partner_data['ContactEmployees']) && is_array($business_partner_data['ContactEmployees'])) { 1950 // Fallback: get name from input data, but we need to fetch BP to get InternalCode 1951 $contact_person_name = $business_partner_data['ContactEmployees'][0]['Name'] ?? null; 1952 1953 // Fetch the created BP to get the contact's InternalCode 1954 $created_bp = $sap_service->get_business_partner($card_code); 1955 if ($created_bp && !empty($created_bp['ContactEmployees'])) { 1956 foreach ($created_bp['ContactEmployees'] as $contact) { 1957 if (isset($contact['Name']) && $contact['Name'] === $contact_person_name) { 1958 $contact_person_code = $contact['InternalCode'] ?? null; 1959 break; 1960 } 1961 } 1962 } 1963 } 1964 1965 $this->update_entry_sap_status($entry['id'], 'success', $card_code, ''); 1966 1663 1967 GFFormsModel::add_note( 1664 1968 $entry['id'], … … 1668 1972 /* translators: %s: SAP Business Partner Card Code */ 1669 1973 esc_html__('✅ SUCCESS: Business Partner created in SAP B1. Card Code: %s', 'shift8-gravity-forms-sap-b1-integration'), 1670 esc_html($ result['CardCode'])1974 esc_html($card_code) 1671 1975 ) 1672 1976 ); 1673 1977 1674 1978 shift8_gravitysap_debug_log('✅ SAP CONFIRMATION: Business Partner created successfully', array( 1675 'CardCode' => $ result['CardCode'],1979 'CardCode' => $card_code, 1676 1980 'CardName' => $result['CardName'] ?? 'N/A', 1677 1981 'CardType' => $result['CardType'] ?? 'N/A', 1678 1982 'Series' => $result['Series'] ?? 'N/A', 1679 1983 'EntryID' => $entry['id'], 1680 'FormID' => $form['id'] 1984 'FormID' => $form['id'], 1985 'ContactPersonName' => $contact_person_name ?? 'N/A', 1986 'ContactPersonCode' => $contact_person_code ?? 'N/A' 1681 1987 )); 1682 1683 // STEP 8: Create Sales Quotation if enabled 1684 if (!empty($settings['create_quotation']) && $settings['create_quotation'] === '1') { 1685 try { 1686 $quotation_result = $this->create_sales_quotation_from_entry($entry, $form, $settings, $result['CardCode'], $sap_service); 1988 } 1989 1990 // STEP 8: Create Sales Quotation if enabled 1991 if (!empty($settings['create_quotation']) && $settings['create_quotation'] === '1') { 1992 try { 1993 // Pass the InternalCode for ContactPersonCode (SAP expects numeric code, not name) 1994 $quotation_result = $this->create_sales_quotation_from_entry($entry, $form, $settings, $card_code, $sap_service, $contact_person_code); 1995 1996 if ($quotation_result && isset($quotation_result['DocEntry'])) { 1997 // Update entry with quotation info 1998 gform_update_meta($entry['id'], 'sap_b1_quotation_docentry', $quotation_result['DocEntry']); 1999 gform_update_meta($entry['id'], 'sap_b1_quotation_docnum', $quotation_result['DocNum'] ?? ''); 1687 2000 1688 if ($quotation_result && isset($quotation_result['DocEntry'])) { 1689 // Update entry with quotation info 1690 gform_update_meta($entry['id'], 'sap_b1_quotation_docentry', $quotation_result['DocEntry']); 1691 gform_update_meta($entry['id'], 'sap_b1_quotation_docnum', $quotation_result['DocNum'] ?? ''); 1692 1693 // Add quotation note 1694 GFFormsModel::add_note( 1695 $entry['id'], 1696 0, 1697 'Shift8 SAP Integration', 1698 sprintf( 1699 /* translators: 1: DocNum, 2: DocEntry */ 1700 esc_html__('✅ SUCCESS: Sales Quotation created in SAP B1. Doc #%1$s (Entry: %2$s)', 'shift8-gravity-forms-sap-b1-integration'), 1701 esc_html($quotation_result['DocNum'] ?? 'N/A'), 1702 esc_html($quotation_result['DocEntry']) 1703 ) 1704 ); 1705 1706 shift8_gravitysap_debug_log('✅ SAP QUOTATION: Sales Quotation created successfully', array( 1707 'DocEntry' => $quotation_result['DocEntry'], 1708 'DocNum' => $quotation_result['DocNum'] ?? 'N/A', 1709 'CardCode' => $result['CardCode'], 1710 'EntryID' => $entry['id'] 1711 )); 1712 } 1713 } catch (Exception $quotation_error) { 1714 // Log quotation error but don't fail the whole submission 1715 shift8_gravitysap_debug_log('⚠️ SAP QUOTATION FAILED (BP was created successfully)', array( 1716 'error' => $quotation_error->getMessage(), 1717 'CardCode' => $result['CardCode'], 1718 'EntryID' => $entry['id'] 1719 )); 1720 2001 // Add quotation note 1721 2002 GFFormsModel::add_note( 1722 2003 $entry['id'], … … 1724 2005 'Shift8 SAP Integration', 1725 2006 sprintf( 1726 /* translators: %s: Error message */ 1727 esc_html__('⚠️ WARNING: Sales Quotation creation failed: %s (Business Partner was created successfully)', 'shift8-gravity-forms-sap-b1-integration'), 1728 esc_html($quotation_error->getMessage()) 2007 /* translators: 1: DocNum, 2: DocEntry */ 2008 esc_html__('✅ SUCCESS: Sales Quotation created in SAP B1. Doc #%1$s (Entry: %2$s)', 'shift8-gravity-forms-sap-b1-integration'), 2009 esc_html($quotation_result['DocNum'] ?? 'N/A'), 2010 esc_html($quotation_result['DocEntry']) 1729 2011 ) 1730 2012 ); 2013 2014 shift8_gravitysap_debug_log('✅ SAP QUOTATION: Sales Quotation created successfully', array( 2015 'DocEntry' => $quotation_result['DocEntry'], 2016 'DocNum' => $quotation_result['DocNum'] ?? 'N/A', 2017 'CardCode' => $card_code, 2018 'EntryID' => $entry['id'] 2019 )); 1731 2020 } 2021 } catch (Exception $quotation_error) { 2022 // Log quotation error but don't fail the whole submission 2023 shift8_gravitysap_debug_log('⚠️ SAP QUOTATION FAILED (BP was created/matched successfully)', array( 2024 'error' => $quotation_error->getMessage(), 2025 'CardCode' => $card_code, 2026 'EntryID' => $entry['id'] 2027 )); 2028 2029 GFFormsModel::add_note( 2030 $entry['id'], 2031 0, 2032 'Shift8 SAP Integration', 2033 sprintf( 2034 /* translators: %s: Error message */ 2035 esc_html__('⚠️ WARNING: Sales Quotation creation failed: %s (Business Partner was created/matched successfully)', 'shift8-gravity-forms-sap-b1-integration'), 2036 esc_html($quotation_error->getMessage()) 2037 ) 2038 ); 1732 2039 } 1733 } else {1734 throw new Exception('SAP returned success but no CardCode - check SAP logs');1735 2040 } 1736 2041 … … 1758 2063 )); 1759 2064 } 2065 } 2066 2067 /** 2068 * Check for existing Business Partner in SAP 2069 * 2070 * Uses the centralized lookup function in SAP Service to find matching BPs. 2071 * 2072 * @since 1.4.0 2073 * @param Shift8_GravitySAP_SAP_Service $sap_service SAP Service instance 2074 * @param array $business_partner_data BP data with lookup fields 2075 * @return string|null CardCode if found, null otherwise 2076 */ 2077 private function check_for_existing_business_partner($sap_service, $business_partner_data) { 2078 // Extract lookup fields from business partner data 2079 $name = rgar($business_partner_data, 'CardName', ''); 2080 $country = ''; 2081 $postal = ''; 2082 2083 // Get country and postal from BPAddresses if present 2084 $bp_addresses = rgar($business_partner_data, 'BPAddresses', array()); 2085 if (!empty($bp_addresses) && isset($bp_addresses[0])) { 2086 $country = rgar($bp_addresses[0], 'Country', ''); 2087 $postal = rgar($bp_addresses[0], 'ZipCode', ''); 2088 } 2089 2090 // If no address data in BPAddresses, check root level (for test data) 2091 if (empty($country)) { 2092 $country = rgar($business_partner_data, 'Country', ''); 2093 } 2094 if (empty($postal)) { 2095 $postal = rgar($business_partner_data, 'ZipCode', ''); 2096 } 2097 2098 // Need at least name and country for lookup 2099 if (empty($name) || empty($country)) { 2100 shift8_gravitysap_debug_log('⚠️ Cannot check for existing BP: Missing required fields', array( 2101 'name' => $name, 2102 'country' => $country, 2103 'postal' => $postal 2104 )); 2105 return null; 2106 } 2107 2108 shift8_gravitysap_debug_log('🔍 Checking for existing Business Partner', array( 2109 'name' => $name, 2110 'country' => $country, 2111 'postal' => $postal 2112 )); 2113 2114 // Use centralized lookup function from SAP Service 2115 $result = $sap_service->find_existing_business_partner($name, $country, $postal); 2116 2117 if ($result['found'] && !empty($result['card_code'])) { 2118 shift8_gravitysap_debug_log('✅ Found existing Business Partner', array( 2119 'card_code' => $result['card_code'], 2120 'card_name' => $result['card_name'], 2121 'match_type' => $result['match_type'] 2122 )); 2123 return $result['card_code']; 2124 } 2125 2126 shift8_gravitysap_debug_log('ℹ️ No existing Business Partner found'); 2127 return null; 1760 2128 } 1761 2129 … … 2040 2408 * 2041 2409 * @since 1.2.2 2042 * @param array $entry Entry data 2043 * @param array $form Form data 2044 * @param array $settings Form settings 2045 * @param string $card_code Business Partner CardCode 2046 * @param object $sap_service SAP Service instance 2410 * @param array $entry Entry data 2411 * @param array $form Form data 2412 * @param array $settings Form settings 2413 * @param string $card_code Business Partner CardCode 2414 * @param object $sap_service SAP Service instance 2415 * @param int|null $contact_person_code Contact Person InternalCode to link (optional) 2047 2416 * @return array Created quotation data 2048 2417 * @throws Exception If creation fails 2049 2418 */ 2050 private function create_sales_quotation_from_entry($entry, $form, $settings, $card_code, $sap_service ) {2419 private function create_sales_quotation_from_entry($entry, $form, $settings, $card_code, $sap_service, $contact_person_code = null) { 2051 2420 shift8_gravitysap_debug_log('=== STARTING SALES QUOTATION CREATION ==='); 2052 2421 … … 2057 2426 'quotation_field_mapping' => $quotation_mapping, 2058 2427 'itemcode_mapping' => $itemcode_mapping, 2059 'entry_id' => $entry['id'] 2428 'entry_id' => $entry['id'], 2429 'contact_person_code' => $contact_person_code 2060 2430 )); 2061 2431 … … 2069 2439 'DocumentLines' => array() 2070 2440 ); 2441 2442 // Link Contact Person to the quotation if provided 2443 // SAP B1 uses ContactPersonCode which is the InternalCode (numeric) of the contact 2444 if (!empty($contact_person_code)) { 2445 $quotation_data['ContactPersonCode'] = intval($contact_person_code); 2446 2447 shift8_gravitysap_debug_log('Linking Contact Person to Sales Quotation', array( 2448 'ContactPersonCode' => $contact_person_code, 2449 'CardCode' => $card_code 2450 )); 2451 } 2071 2452 2072 2453 // Add Comments if mapped … … 2881 3262 $sap_service = new Shift8_GravitySAP_SAP_Service($plugin_settings); 2882 3263 2883 // Create Business Partner in SAP 3264 // Check for existing Business Partner if enabled 3265 if (rgar($settings, 'check_existing_bp') === '1') { 3266 $existing_card_code = $this->check_for_existing_business_partner($sap_service, $business_partner_data); 3267 3268 if ($existing_card_code) { 3269 return array( 3270 'success' => true, 3271 'message' => '🔗 MATCHED: Found existing Business Partner with Card Code: ' . esc_html($existing_card_code) 3272 ); 3273 } 3274 } 3275 3276 // Create Business Partner in SAP (no match found or check disabled) 2884 3277 $result = $sap_service->create_business_partner($business_partner_data); 2885 3278 … … 2887 3280 return array( 2888 3281 'success' => true, 2889 'message' => ' Card Code: ' . esc_html($result['CardCode'])3282 'message' => '✅ CREATED: New Business Partner with Card Code: ' . esc_html($result['CardCode']) 2890 3283 ); 2891 3284 } else {
Note: See TracChangeset
for help on using the changeset viewer.