Intuit QuickBooks Web 连接器 IteratorID 无效错误

Intuit QuickBooks web connector IteratorID not valid error

我一直在尝试将基于 PHP 的解决方案与 QuickBooks 企业版集成。我使用 QuickBooks PHP Dev Kit (http://consolibyte.com/quickbooks-open-source/) 连接应用程序以双向同步多个模块。

具有多个记录的第一个模块(QuickBooks 到 VtigerCRM)正在完美同步。当我尝试使用我的下一个模块时,只有少数记录得到同步并抛出错误 IteratorID 无效 。然后,我尝试在 XML 中发送带有新 IteratorID 的参数,但没有成功。

20180821.05:13:03 UTC    : QBWebConnector.SOAPWebService.do_receiveResponseXML() : hresult=""
20180821.05:13:03 UTC    : QBWebConnector.SOAPWebService.do_receiveResponseXML() : message=""
20180821.05:13:03 UTC    : QBWebConnector.SOAPWebService.do_receiveResponseXML() : Received from receiveResponseXML() following parameters:
20180821.05:13:03 UTC    : QBWebConnector.SOAPWebService.do_receiveResponseXML() : more="-1">
20180821.05:13:03 UTC    : QBWebConnector.SOAPWebService.do_getLastError() : * Calling getLastError() with following parameter:
20180821.05:13:03 UTC    : QBWebConnector.SOAPWebService.do_getLastError() : wcTicket="403c7856-d600-c664-5187-3d9e2036c0cc"
20180821.05:13:04 UTC    : QBWebConnector.SOAPWebService.do_getLastError() : Received from getLastError() following parameter:
20180821.05:13:04 UTC    : QBWebConnector.SOAPWebService.do_getLastError() : errorMsg="3391: The iteratorID "{80c35df0-ae92-43a3-af49-946da1c306fb}" is not valid."
20180821.05:13:04 UTC    : QBWebConnector.SOAPWebService.do_getLastError() : Received error from application: 3391: The iteratorID "{80c35df0-ae92-43a3-af49-946da1c306fb}" is not valid.
20180821.05:13:04 UTC    : QBWebConnector.CompanyFileLock.Send_CompanyQueryRqXML() : XML dump follows: -
<?xml version="1.0"?><?qbxml version="13.0"?><QBXML><QBXMLMsgsRq onError="stopOnError"><CompanyQueryRq requestID="1"><OwnerID>{90A44FB7-33D9-4815-AC85-AC86A7E7D1EB}</OwnerID></CompanyQueryRq></QBXMLMsgsRq></QBXML>

我添加了 Helper.php

的代码

<?php
// Support URL
if (!empty($_GET['support']))
{
 header('Location: http://www.consolibyte.com/');
 exit;
}

// We need to make sure the correct timezone is set, or some PHP installations will complain
if (function_exists('date_default_timezone_set'))
{
 // * MAKE SURE YOU SET THIS TO THE CORRECT TIMEZONE! *
 // List of valid timezones is here: http://us3.php.net/manual/en/timezones.php
 date_default_timezone_set('America/New_York');
}

// Require the framework
require_once 'QuickBooks.php';
require_once('config.inc.php');
global $dbconfig;
$user = 'quickbooks';
$pass = 'password';

  
define('QB_QUICKBOOKS_CONFIG_LAST', 'last');

   
define('QB_QUICKBOOKS_CONFIG_CURR', 'curr');
define('QB_QUICKBOOKS_MAX_RETURNED', 1);
define('QB_PRIORITY_PURCHASEORDER', 1);

define('QB_PRIORITY_ITEM', 6);
define('QB_PRIORITY_CUSTOMER', 5);
define('QB_PRIORITY_SALESORDER', 2);

/**
 * Request priorities, invoices last... 
 */
define('QB_PRIORITY_INVOICE', 3);

define('QB_PRIORITY_ESTIMATE',4);
define('QB_QUICKBOOKS_MAILTO', 'keith@consolibyte.com');
// Map QuickBooks actions to handler functions
$map = array(

 QUICKBOOKS_IMPORT_CUSTOMER => array( '_quickbooks_customer_import_request', '_quickbooks_customer_import_response' ), 
 QUICKBOOKS_IMPORT_ITEM => array( '_quickbooks_item_import_request', '_quickbooks_item_import_response' ), 
 QUICKBOOKS_IMPORT_ESTIMATE =>array( '_quickbooks_quote_import_request', '_quickbooks_quote_import_response' ), 
 QUICKBOOKS_IMPORT_SALESORDER => array( '_quickbooks_salesorder_import_request', '_quickbooks_salesorder_import_response' ), 
 QUICKBOOKS_IMPORT_INVOICE => array( '_quickbooks_invoice_import_request', '_quickbooks_invoice_import_response' ),
 QUICKBOOKS_IMPORT_PURCHASEORDER => array( '_quickbooks_purchaseorder_import_request', '_quickbooks_purchaseorder_import_response' ),
 
 );

// Error handlers
$errmap = array(
 500 => '_quickbooks_error_e500_notfound',    // Catch errors caused by searching for things not present in QuickBooks
 1 => '_quickbooks_error_e500_notfound', 
 '*' => '_quickbooks_error_catchall',     // Catch any other errors that might occur
 );

// An array of callback hooks
$hooks = array(
 QuickBooks_WebConnector_Handlers::HOOK_LOGINSUCCESS => '_quickbooks_hook_loginsuccess',  // call this whenever a successful login occurs
 );

// Logging level
//$log_level = QUICKBOOKS_LOG_NORMAL;
//$log_level = QUICKBOOKS_LOG_VERBOSE;
//$log_level = QUICKBOOKS_LOG_DEBUG;    // Use this level until you're sure everything works!!!
$log_level = QUICKBOOKS_LOG_DEVELOP;

// What SOAP server you're using 
//$soapserver = QUICKBOOKS_SOAPSERVER_PHP;   // The PHP SOAP extension, see: www.php.net/soap
$soapserver = QUICKBOOKS_SOAPSERVER_BUILTIN;  // A pure-PHP SOAP server (no PHP ext/soap extension required, also makes debugging easier)

$soap_options = array(   // See http://www.php.net/soap
 );

$handler_options = array(  // See the comments in the QuickBooks/Server/Handlers.php file
 'deny_concurrent_logins' => false, 
 'deny_reallyfast_logins' => false, 
 );  

$driver_options = array(  // See the comments in the QuickBooks/Driver/<YOUR DRIVER HERE>.php file ( i.e. 'Mysql.php', etc. )
 );

$callback_options = array(
 );
$dsn = $dbconfig['db_type']."://".$dbconfig['db_username'].":".$dbconfig['db_password']."@".$dbconfig['db_server']."/".$dbconfig['db_name'];
//$dsn = 'mysql://root:smackcoders@localhost/vtiger2';
//$dsn = 'mysql://testuser:testpassword@localhost/testdatabase';
define('QB_QUICKBOOKS_DSN', $dsn);

// If we haven't done our one-time initialization yet, do it now!
if (!QuickBooks_Utilities::initialized($dsn))
{
 // Create the example tables
 $file = dirname(__FILE__) . '/example.sql';
 if (file_exists($file))
 {
  $contents = file_get_contents($file); 
  foreach (explode(';', $contents) as $sql)
  {
   if (!trim($sql))
   {
    continue;
   }
   
   mysql_query($sql) or die(trigger_error(mysql_error()));
  }
 }
 else
 {
  die('Could not locate "./example.sql" to create the demo SQL schema!');
 }
 
 // Create the database tables
 QuickBooks_Utilities::initialize($dsn);
 
 // Add the default authentication username/password
 QuickBooks_Utilities::createUser($dsn, $user, $pass);
}

// Initialize the queue
QuickBooks_WebConnector_Queue_Singleton::initialize($dsn);

// Create a new server and tell it to handle the requests
$Server = new QuickBooks_WebConnector_Server($dsn, $map, $errmap, $hooks, $log_level, $soapserver, QUICKBOOKS_WSDL, $soap_options, $handler_options, $driver_options, $callback_options);
$response = $Server->handle(true, true);

/**
 * Login success hook - perform an action when a user logs in via the Web Connector
 *
 * 
 */
function _quickbooks_hook_loginsuccess($requestID, $user, $hook, &$err, $hook_data, $callback_config)
{
 // For new users, we need to set up a few things

 // Fetch the queue instance
 $Queue = QuickBooks_WebConnector_Queue_Singleton::getInstance();
 $date = date('Y-m-d H:i:s');
 
 // Set up the invoice imports
 if (!_quickbooks_get_last_run($user, QUICKBOOKS_IMPORT_INVOICE))
 {
  // And write the initial sync time
  _quickbooks_set_last_run($user, QUICKBOOKS_IMPORT_INVOICE, $date);
 }
 
 // Do the same for customers
 if (!_quickbooks_get_last_run($user, QUICKBOOKS_IMPORT_CUSTOMER))
 {
  _quickbooks_set_last_run($user, QUICKBOOKS_IMPORT_CUSTOMER, $date);
 }

 // ... and for sales orders
 if (!_quickbooks_get_last_run($user, QUICKBOOKS_IMPORT_SALESORDER))
 {
  _quickbooks_set_last_run($user, QUICKBOOKS_IMPORT_SALESORDER, $date);
 }
 
 // ... and for items
 if (!_quickbooks_get_last_run($user, QUICKBOOKS_IMPORT_ITEM))
 {
  _quickbooks_set_last_run($user, QUICKBOOKS_IMPORT_ITEM, $date);
 }
 
 // Make sure the requests get queued up
 $Queue->enqueue(QUICKBOOKS_IMPORT_CUSTOMER, 1, QB_PRIORITY_CUSTOMER, null, $user);
 $Queue->enqueue(QUICKBOOKS_IMPORT_ITEM, 1, QB_PRIORITY_ITEM, null, $user);
 $Queue->enqueue(QUICKBOOKS_IMPORT_ESTIMATE, 1, QB_PRIORITY_ESTIMATE, null, $user);
 $Queue->enqueue(QUICKBOOKS_IMPORT_SALESORDER, 1, QB_PRIORITY_SALESORDER, null, $user);
 $Queue->enqueue(QUICKBOOKS_IMPORT_INVOICE, 1, QB_PRIORITY_INVOICE, null, $user);
 $Queue->enqueue(QUICKBOOKS_IMPORT_PURCHASEORDER, 1, QB_PRIORITY_PURCHASEORDER, null, $user);

}

/**
 * Get the last date/time the QuickBooks sync ran
 * 
 * @param string $user  The web connector username 
 * @return string   A date/time in this format: "yyyy-mm-dd hh:ii:ss"
 */
function _quickbooks_get_last_run($user, $action)
{
 $type = null;
 $opts = null;
 return QuickBooks_Utilities::configRead(QB_QUICKBOOKS_DSN, $user, md5(__FILE__), QB_QUICKBOOKS_CONFIG_LAST . '-' . $action, $type, $opts);
}

 
function _quickbooks_set_last_run($user, $action, $force = null)
{
 $value = date('Y-m-d') . 'T' . date('H:i:s');
 
 if ($force)
 {
  $value = date('Y-m-d', strtotime($force)) . 'T' . date('H:i:s', strtotime($force));
 }
 
 return QuickBooks_Utilities::configWrite(QB_QUICKBOOKS_DSN, $user, md5(__FILE__), QB_QUICKBOOKS_CONFIG_LAST . '-' . $action, $value);
}

/**
 * 
 * 
 */
function _quickbooks_get_current_run($user, $action)
{
 $type = null;
 $opts = null;
 return QuickBooks_Utilities::configRead(QB_QUICKBOOKS_DSN, $user, md5(__FILE__), QB_QUICKBOOKS_CONFIG_CURR . '-' . $action, $type, $opts); 
}

/**
 * 
 * 
 */
function _quickbooks_set_current_run($user, $action, $force = null)
{
 $value = date('Y-m-d') . 'T' . date('H:i:s');
 
 if ($force)
 {
  $value = date('Y-m-d', strtotime($force)) . 'T' . date('H:i:s', strtotime($force));
 }
 
 return QuickBooks_Utilities::configWrite(QB_QUICKBOOKS_DSN, $user, md5(__FILE__), QB_QUICKBOOKS_CONFIG_CURR . '-' . $action, $value); 
}

/**
 * Build a request to import invoices already in QuickBooks into our application
 */
function _quickbooks_invoice_import_request($requestID, $user, $action, $ID, $extra, &$err, $last_action_time, $last_actionident_time, $version, $locale)
{
 // Iterator support (break the result set into small chunks)
 $attr_iteratorID = '';
 $attr_iterator = ' iterator="Start" ';

 if (empty($extra['iteratorID']))
 {
  $last = _quickbooks_get_last_run($user, $action);
  _quickbooks_set_last_run($user, $action);   // Update the last run time to NOW()

  // Set the current run to $last
  _quickbooks_set_current_run($user, $action, $last);

 }
 else
 {
  $extra['iteratorID'] = $saved_iterator_id; // From Table
  // This is a continuation of a batch
  $attr_iteratorID = ' iteratorID="' . $extra['iteratorID'] . '" ';
  $attr_iterator = ' iterator="Continue" ';

  $last = _quickbooks_get_current_run($user, $action);
 }
 
 // Build the request
 $xml = '<?xml version="1.0" encoding="utf-8"?>
  <?qbxml version="' . $version . '"?>
  <QBXML>
   <QBXMLMsgsRq onError="stopOnError">
    <InvoiceQueryRq ' . $attr_iterator . ' ' . $attr_iteratorID . ' requestID="' . $requestID . '">
     <MaxReturned>' . QB_QUICKBOOKS_MAX_RETURNED . '</MaxReturned>
     <ModifiedDateRangeFilter>
      <FromModifiedDate>' . $last . '</FromModifiedDate>
     </ModifiedDateRangeFilter>
     <IncludeLineItems>true</IncludeLineItems>
     <OwnerID>0</OwnerID>
    </InvoiceQueryRq> 
   </QBXMLMsgsRq>
  </QBXML>';
  
 return $xml;
}

/** 
 * Handle a response from QuickBooks 
 */
function _quickbooks_invoice_import_response($requestID, $user, $action, $ID, $extra, &$err, $last_action_time, $last_actionident_time, $xml, $idents)
{ 
 if (!empty($idents['iteratorRemainingCount']))
 {
  // Queue up another request
  
  $Queue = QuickBooks_WebConnector_Queue_Singleton::getInstance();
  $Queue->enqueue(QUICKBOOKS_IMPORT_INVOICE, null, QB_PRIORITY_INVOICE, array( 'iteratorID' => $idents['iteratorID'] ), $user);
 }else{

  return true;
 }
 
 // This piece of the response from QuickBooks is now stored in $xml. You 
 // can process the qbXML response in $xml in any way you like. Save it to 
 // a file, stuff it in a database, parse it and stuff the records in a 
 // database, etc. etc. etc. 
 // 
 // The following example shows how to use the built-in XML parser to parse 
 // the response and stuff it into a database. 
 
 // Import all of the records
 $errnum = 0;
 $errmsg = '';
 $Parser = new QuickBooks_XML_Parser($xml);
 if ($Doc = $Parser->parse($errnum, $errmsg))
 {
  $Root = $Doc->getRoot();
  $List = $Root->getChildAt('QBXML/QBXMLMsgsRs/InvoiceQueryRs');
        $smackHelper = new Quickbooks_vtigerHelper();
        $smackHelper->addInvoice($List->children());

 }
 
 return true;
}

/**
 * Build a request to import customers already in QuickBooks into our application
 */
function _quickbooks_customer_import_request($requestID, $user, $action, $ID, $extra, &$err, $last_action_time, $last_actionident_time, $version, $locale)
{
 // Iterator support (break the result set into small chunks)
 $attr_iteratorID = '';
 $attr_iterator = ' iterator="Start" ';
 if (empty($extra['iteratorID']))
 {
  // This is the first request in a new batch
  $last = _quickbooks_get_last_run($user, $action);
  _quickbooks_set_last_run($user, $action);   // Update the last run time to NOW()
  
  // Set the current run to $last
  _quickbooks_set_current_run($user, $action, $last);
 }
 else
 {
  $extra['iteratorID'] = $saved_iterator_id; // From Table

  // This is a continuation of a batch
        $attr_iteratorID = ' iteratorID="' . $extra['iteratorID'] . '" ';
  $attr_iterator = ' iterator="Continue" ';

  $last = _quickbooks_get_current_run($user, $action);
 }
 // Build the request
 $xml = '<?xml version="1.0" encoding="utf-8"?>
  <?qbxml version="' . $version . '"?>
  <QBXML>
   <QBXMLMsgsRq onError="stopOnError">
    <CustomerQueryRq ' . $attr_iterator . ' ' . $attr_iteratorID . ' requestID="' . $requestID . '">
     <MaxReturned>' . QB_QUICKBOOKS_MAX_RETURNED . '</MaxReturned>
     <FromModifiedDate>' . $last . '</FromModifiedDate>
     <OwnerID>0</OwnerID>
    </CustomerQueryRq> 
   </QBXMLMsgsRq>
  </QBXML>';
 return $xml;
}

/** 
 * Handle a response from QuickBooks 
 */
function _quickbooks_customer_import_response($requestID, $user, $action, $ID, $extra, &$err, $last_action_time, $last_actionident_time, $xml, $idents)
{ 
 if (!empty($idents['iteratorRemainingCount']))
 {
  // Queue up another request
  
  $Queue = QuickBooks_WebConnector_Queue_Singleton::getInstance();
  $Queue->enqueue(QUICKBOOKS_IMPORT_CUSTOMER, null, QB_PRIORITY_CUSTOMER, array( 'iteratorID' => $idents['iteratorID'] ), $user);
 }else{

  return true;
 }    
 // Import all of the records
 $errnum = 0;
 $errmsg = '';

 $Parser = new QuickBooks_XML_Parser($xml);
 if ($Doc = $Parser->parse($errnum, $errmsg))
 {
  $Root = $Doc->getRoot();
  $List = $Root->getChildAt('QBXML/QBXMLMsgsRs/CustomerQueryRs');
        $smackHelper = new Quickbooks_vtigerHelper();
        $smackHelper->addContacts($List->children());

 }
 
 return true;
}


function _quickbooks_quote_import_response($requestID, $user, $action, $ID, $extra, &$err, $last_action_time, $last_actionident_time, $xml, $idents)
{

 if (!empty($idents['iteratorRemainingCount']))
 {
  // Queue up another request

  $Queue = QuickBooks_WebConnector_Queue_Singleton::getInstance();
  $Queue->enqueue(QUICKBOOKS_IMPORT_ESTIMATE, null, QB_PRIORITY_ESTIMATE, array( 'iteratorID' => $idents['iteratorID'] ), $user);
 }else{
 
  return true;
 }
 // Import all of the records
 $errnum = 0;
 $errmsg = '';
 $Parser = new QuickBooks_XML_Parser($xml);
 if ($Doc = $Parser->parse($errnum, $errmsg))
 {
  $Root = $Doc->getRoot();
  $List = $Root->getChildAt('QBXML/QBXMLMsgsRs/EstimateQueryRs');
  $smackHelper = new Quickbooks_vtigerHelper();


  $smackHelper->addQuotes($List->children());


 }


 return true;
}


function _quickbooks_quote_import_request($requestID, $user, $action, $ID, $extra, &$err, $last_action_time, $last_actionident_time, $version, $locale)
{
 if (empty($extra['iteratorID']))
 {
  
  $last = _quickbooks_get_last_run($user, $action);
  _quickbooks_set_last_run($user, $action);   // Update the last run time to NOW()
  $attr_iterator = ' iterator="Start" ';
  // Set the current run to $last
  _quickbooks_set_current_run($user, $action, $last);

 }
 else
 {
  $extra['iteratorID'] = $saved_iterator_id; // From Table
  // This is a continuation of a batch
  $attr_iteratorID = ' iteratorID="' . $extra['iteratorID'] . '" ';
  $attr_iterator = ' iterator="Continue" ';
  $last = _quickbooks_get_current_run($user, $action);
  //$last = '2015-08-18T01:03:41';
  //$last = _quickbooks_get_current_run($user, $action);
 }

 // Build the request
 $xml = '<?xml version="1.0" encoding="utf-8"?>
  <?qbxml version="' . $version . '"?>
  <QBXML>
  <QBXMLMsgsRq onError="stopOnError">
  <EstimateQueryRq ' . $attr_iterator . ' ' . $attr_iteratorID . ' requestID="' . $requestID . '">
  <MaxReturned>' . QB_QUICKBOOKS_MAX_RETURNED . '</MaxReturned>
  <ModifiedDateRangeFilter>
  <FromModifiedDate>' . $last . '</FromModifiedDate>
  </ModifiedDateRangeFilter>
  <IncludeLineItems>true</IncludeLineItems>
  <OwnerID>0</OwnerID>
  </EstimateQueryRq> 
  </QBXMLMsgsRq>
  </QBXML>';

 return $xml;


}

function _quickbooks_salesorder_import_request($requestID, $user, $action, $ID, $extra, &$err, $last_action_time, $last_actionident_time, $version, $locale)
{
 // Iterator support (break the result set into small chunks)
 $attr_iteratorID = '';
 $attr_iterator = ' iterator="Start" ';
 if (empty($extra['iteratorID']))
 {
  // This is the first request in a new batch
  $last = _quickbooks_get_last_run($user, $action);
  _quickbooks_set_last_run($user, $action);   // Update the last run time to NOW()
  
  // Set the current run to $last
  _quickbooks_set_current_run($user, $action, $last);
 }
 else
 {
  $extra['iteratorID'] = $saved_iterator_id; // From Table
  
  // This is a continuation of a batch
  $attr_iteratorID = ' iteratorID="' . $extra['iteratorID'] . '" ';
  $attr_iterator = ' iterator="Continue" ';
  
  $last = _quickbooks_get_current_run($user, $action);
 }
 
 // Build the request
 $xml = '<?xml version="1.0" encoding="utf-8"?>
  <?qbxml version="' . $version . '"?>
  <QBXML>
   <QBXMLMsgsRq onError="stopOnError">
    <SalesOrderQueryRq ' . $attr_iterator . ' ' . $attr_iteratorID . ' requestID="' . $requestID . '">
     <MaxReturned>' . QB_QUICKBOOKS_MAX_RETURNED . '</MaxReturned>
     <ModifiedDateRangeFilter>
      <FromModifiedDate>' . $last . '</FromModifiedDate>
     </ModifiedDateRangeFilter>
     <IncludeLineItems>true</IncludeLineItems>
     <OwnerID>0</OwnerID>
    </SalesOrderQueryRq> 
   </QBXMLMsgsRq>
  </QBXML>';
  
 return $xml;
}

/** 
 * Handle a response from QuickBooks 
 */
function _quickbooks_salesorder_import_response($requestID, $user, $action, $ID, $extra, &$err, $last_action_time, $last_actionident_time, $xml, $idents)
{ 

 if (!empty($idents['iteratorRemainingCount']))
 {
  // Queue up another request
  
  $Queue = QuickBooks_WebConnector_Queue_Singleton::getInstance();
  $Queue->enqueue(QUICKBOOKS_IMPORT_SALESORDER, null, QB_PRIORITY_SALESORDER, array( 'iteratorID' => $idents['iteratorID'] ), $user);
 }else{

  return true;
 }

 // Import all of the records
 $errnum = 0;
 $errmsg = '';
 $Parser = new QuickBooks_XML_Parser($xml);
 if ($Doc = $Parser->parse($errnum, $errmsg))
 {
  $Root = $Doc->getRoot();
  $List = $Root->getChildAt('QBXML/QBXMLMsgsRs/SalesOrderQueryRs');
        $smackHelper = new Quickbooks_vtigerHelper();
        $smackHelper->addSalesOrder($List->children());

 }
 
 return true;
}

/**
 * Build a request to import customers already in QuickBooks into our application
 */
function _quickbooks_item_import_request($requestID, $user, $action, $ID, $extra, &$err, $last_action_time, $last_actionident_time, $version, $locale)
{
 // Iterator support (break the result set into small chunks)
 $attr_iteratorID = '';
 $attr_iterator = ' iterator="Start" ';
 if (empty($extra['iteratorID']))
 {
  // This is the first request in a new batch
  $last = _quickbooks_get_last_run($user, $action);
  _quickbooks_set_last_run($user, $action);   // Update the last run time to NOW()
  
  // Set the current run to $last
  _quickbooks_set_current_run($user, $action, $last);
 }
 else
 {
  $extra['iteratorID'] = $saved_iterator_id; // From Table

  // This is a continuation of a batch
  $attr_iteratorID = ' iteratorID="' . $extra['iteratorID'] . '" ';
  $attr_iterator = ' iterator="Continue" ';
  
  $last = _quickbooks_get_current_run($user, $action);
 }
 
 // Build the request
 $xml = '<?xml version="1.0" encoding="utf-8"?>
  <?qbxml version="' . $version . '"?>
  <QBXML>
   <QBXMLMsgsRq onError="stopOnError">
    <ItemQueryRq ' . $attr_iterator . ' ' . $attr_iteratorID . ' requestID="' . $requestID . '">
     <MaxReturned>' . QB_QUICKBOOKS_MAX_RETURNED . '</MaxReturned>
     <FromModifiedDate>' . $last . '</FromModifiedDate>
     <OwnerID>0</OwnerID>
    </ItemQueryRq> 
   </QBXMLMsgsRq>
  </QBXML>';
  
 return $xml;
}

/** 
 * Handle a response from QuickBooks 
 */
function _quickbooks_item_import_response($requestID, $user, $action, $ID, $extra, &$err, $last_action_time, $last_actionident_time, $xml, $idents)
{ 
 if (!empty($idents['iteratorRemainingCount']))
 {
  // Queue up another request
  
  $Queue = QuickBooks_WebConnector_Queue_Singleton::getInstance();
  $Queue->enqueue(QUICKBOOKS_IMPORT_ITEM, null, QB_PRIORITY_ITEM, array( 'iteratorID' => $idents['iteratorID'] ), $user);
 }else{

  return true;
 }
 
 // Import all of the records
 $errnum = 0;
 $errmsg = '';
 $Parser = new QuickBooks_XML_Parser($xml);
 if ($Doc = $Parser->parse($errnum, $errmsg))
 {
  $Root = $Doc->getRoot();
  $List = $Root->getChildAt('QBXML/QBXMLMsgsRs/ItemQueryRs');

  $smackHelper = new Quickbooks_vtigerHelper();
  $smackHelper->addProducts($List->children(),'Products');

 }
 
 return true;
}

/**
 * Build a request to import invoices already in QuickBooks into our application
 */
function _quickbooks_purchaseorder_import_request($requestID, $user, $action, $ID, $extra, &$err, $last_action_time, $last_actionident_time, $version, $locale)
{
 // Iterator support (break the result set into small chunks)
 $attr_iteratorID = '';
 $attr_iterator = ' iterator="Start" ';
 if (empty($extra['iteratorID']))
 {
  // This is the first request in a new batch
  $last = _quickbooks_get_last_run($user, $action);
  _quickbooks_set_last_run($user, $action);   // Update the last run time to NOW()
  
  // Set the current run to $last
  _quickbooks_set_current_run($user, $action, $last);
 }
 else
 {
  $extra['iteratorID'] = $saved_iterator_id; // From Table

  // This is a continuation of a batch
  $attr_iteratorID = ' iteratorID="' . $extra['iteratorID'] . '" ';
  $attr_iterator = ' iterator="Continue" ';
  
  $last = _quickbooks_get_current_run($user, $action);
 }
 
 // Build the request
 $xml = '<?xml version="1.0" encoding="utf-8"?>
  <?qbxml version="' . $version . '"?>
  <QBXML>
   <QBXMLMsgsRq onError="stopOnError">
    <PurchaseOrderQueryRq ' . $attr_iterator . ' ' . $attr_iteratorID . ' requestID="' . $requestID . '">
     <MaxReturned>' . QB_QUICKBOOKS_MAX_RETURNED . '</MaxReturned>
     <!--<ModifiedDateRangeFilter>
      <FromModifiedDate>' . $last . '</FromModifiedDate>
     </ModifiedDateRangeFilter>-->
     <IncludeLineItems>true</IncludeLineItems>
     <OwnerID>0</OwnerID>
    </PurchaseOrderQueryRq> 
   </QBXMLMsgsRq>
  </QBXML>';
  
 return $xml;
}

/** 
 * Handle a response from QuickBooks 
 */
function _quickbooks_purchaseorder_import_response($requestID, $user, $action, $ID, $extra, &$err, $last_action_time, $last_actionident_time, $xml, $idents)
{ 

 if (!empty($idents['iteratorRemainingCount']))
 {
  // Queue up another request
  
  $Queue = QuickBooks_WebConnector_Queue_Singleton::getInstance();
  $Queue->enqueue(QUICKBOOKS_IMPORT_PURCHASEORDER, null, QB_PRIORITY_PURCHASEORDER, array( 'iteratorID' => $idents['iteratorID'] ), $user);
 }else{

  return true;
 }
 
 // This piece of the response from QuickBooks is now stored in $xml. You 
 // can process the qbXML response in $xml in any way you like. Save it to 
 // a file, stuff it in a database, parse it and stuff the records in a 
 // database, etc. etc. etc. 
 // 
 // The following example shows how to use the built-in XML parser to parse 
 // the response and stuff it into a database. 
 
 // Import all of the records
 $errnum = 0;
 $errmsg = '';
 $Parser = new QuickBooks_XML_Parser($xml);
 if ($Doc = $Parser->parse($errnum, $errmsg))
 {
  $Root = $Doc->getRoot();
  $List = $Root->getChildAt('QBXML/QBXMLMsgsRs/PurchaseOrderQueryRs');
  
  // TODO 
 }
 
 return true;
}

/**
 * Handle a 500 not found error from QuickBooks
 * 
 * Instead of returning empty result sets for queries that don't find any 
 * records, QuickBooks returns an error message. This handles those error 
 * messages, and acts on them by adding the missing item to QuickBooks. 
 */
function _quickbooks_error_e500_notfound($requestID, $user, $action, $ID, $extra, &$err, $xml, $errnum, $errmsg)
{
 $Queue = QuickBooks_WebConnector_Queue_Singleton::getInstance();
 
 if ($action == QUICKBOOKS_IMPORT_INVOICE)
 {
  return true;
 }
 else if ($action == QUICKBOOKS_IMPORT_CUSTOMER)
 {
  return true;
 }
 else if ($action == QUICKBOOKS_IMPORT_SALESORDER)
 {
  return true;
 }
 else if ($action == QUICKBOOKS_IMPORT_ITEM)
 {
  return true;
 }
 else if ($action == QUICKBOOKS_IMPORT_PURCHASEORDER)
 {
  return true;
 }
 
 return false;
}

function _quickbooks_error_catchall($requestID, $user, $action, $ID, $extra, &$err, $xml, $errnum, $errmsg)
{
 $message = '';
 $message .= 'Request ID: ' . $requestID . "\r\n";
 $message .= 'User: ' . $user . "\r\n";
 $message .= 'Action: ' . $action . "\r\n";
 $message .= 'ID: ' . $ID . "\r\n";
 $message .= 'Extra: ' . print_r($extra, true) . "\r\n";
 //$message .= 'Error: ' . $err . "\r\n";
 $message .= 'Error number: ' . $errnum . "\r\n";
 $message .= 'Error message: ' . $errmsg . "\r\n";
 
 mail(QB_QUICKBOOKS_MAILTO, 
  'QuickBooks error occured!', 
  $message);
}

所以我至少看到了一些潜在的问题,并且有一些问题。首先,一些背景——

Iterators 在 QuickBooks 中就像数据库游标一样工作。这在一定程度上意味着您不能同时打开两个迭代器——您必须先完成一个迭代器,然后再开始 and/or 继续另一个迭代器。所以如果你这样做,你会得到你看到的错误:

  1. 启动迭代器 #1
  2. 从迭代器 #1 中获取一些数据
  3. 启动迭代器 #2
  4. 再次尝试从迭代器 #1 中获取一些数据

这将失败,因为启动第二个迭代器时 closed/killed 第一个迭代器。这可能就是你正在发生的事情。

这是什么?

$extra['iteratorID'] = $saved_iterator_id; // From Table

对我来说,这看起来像 undefined variable。您是否检查了 PHP 错误日志?

你为什么要将它保存到 table,你要将它保存到什么 table?您在代码的哪个位置将其从 table 中拉回?我没有看到任何从 table 获取它的代码。