Consolibyte QB for Windows API 尝试创建估算时出错...为什么?
Consolibyte QB for Windows API error trying to create an estimate... why?
XML 通过在 quickbooks_log table 中找到的“添加估计”请求发送到 quickbooks 的
XML 如下:
<?xml version="1.0" encoding="utf-8"?>
<?qbxml version="13.0"?>
<QBXML>
<QBXMLMsgsRq onError="stopOnError">
<EstimateAddRq requestID="212">
<EstimateAdd>
<CustomerRef>
<ListID>800007A5-1480913677</ListID>
</CustomerRef>
<TxnDate>2016-12-04</TxnDate>
<BillAddress>
<Addr1>2532 S. Franklin Street</Addr1>
<City>Philadelphia</City>
<Province>PA</Province>
<PostalCode>19148</PostalCode>
<Country>United States</Country>
</BillAddress>
<ShipAddress>
<Addr1>2406 E. York Street</Addr1>
<Addr2>Apartment #2B</Addr2>
<City>Philadelphia</City>
<Province>PA</Province>
<PostalCode>19125</PostalCode>
<Country>United States</Country>
</ShipAddress>
<IsToBeEmailed>true</IsToBeEmailed>
<EstimateLineAdd>
<ItemRef>
<ListID>800000C1-1480913684</ListID>
</ItemRef>
<Quantity>45</Quantity>
</EstimateLineAdd>
<EstimateLineAdd>
<ItemRef>
<ListID>800000BE-1480913680</ListID>
</ItemRef>
<Quantity>10</Quantity>
</EstimateLineAdd>
<EstimateLineAdd>
<ItemRef>
<ListID>800000C0-1480913683</ListID>
</ItemRef>
<Quantity>500</Quantity>
</EstimateLineAdd>
<EstimateLineAdd>
<ItemRef>
<ListID>800000BD-1480913679</ListID>
</ItemRef>
<Quantity>5</Quantity>
<Amount>0.00</Amount>
<Other1>NO BID</Other1>
</EstimateLineAdd>
<EstimateLineAdd>
<ItemRef>
<ListID>800000BF-1480913681</ListID>
</ItemRef>
<Quantity>10</Quantity>
</EstimateLineAdd>
</EstimateAdd>
</EstimateAddRq>
</QBXMLMsgsRq>
</QBXML>
估计函数的创建如下:
function hunter_create_estimate()
{
global $wpdb;
$submission_id = !empty($_POST['submission_id']) ? $_POST['submission_id'] : 0;
$submit_time = !empty($_POST['submit_time']) ? $_POST['submit_time'] : '';
$results = array(
'type' => 'error',
'message' => 'An Error Occurred when trying to create an Estimate for this Quote Request. Please try again.'
);
if (empty($submission_id) || empty($submit_time) || !isset($_POST['instance']))
{
set_transient('create-estimate-results', $results, HOUR_IN_SECONDS);
wp_redirect(admin_url('admin.php?page=quickbook-rfqs'));
exit(0);
}
if (!isset($_POST['_wpnonce_create_estimate_from_' . $submission_id]))
{
set_transient('create-estimate-results', $results, HOUR_IN_SECONDS);
wp_redirect(admin_url('admin.php?page=quickbook-rfqs'));
exit(0);
}
$nonce = $_POST['_wpnonce_create_estimate_from_' . $submission_id];
if (!wp_verify_nonce($nonce, 'hunter-create-estimate'))
{
set_transient('create-estimate-results', $results, HOUR_IN_SECONDS);
wp_redirect(admin_url('admin.php?page=quickbook-rfqs'));
exit(0);
}
if (!current_user_can('manage_options'))
{
$results['message'] = 'Sorry, but you do not have permission to create estimates.';
set_transient('create-estimate-results', $results, HOUR_IN_SECONDS);
wp_redirect(admin_url('admin.php?page=quickbook-rfqs'));
exit(0);
}
$instance = !empty($_POST['instance']) ? (int) $_POST['instance'] : 0;
$instance = empty($instance) ? 0 : $instance;
$form_name = get_option('quickbooks_cf7_form', 'Personal Info');
$form_status = $wpdb->get_var('
SELECT MAX(IF(field_name = "status", field_value, NULL)) AS status
FROM ' . $wpdb->prefix . 'cf7dbplugin_submits
WHERE form_name = "' . $form_name . '" AND submit_time = "' . $submit_time . '" AND instance = ' . $instance);
if ($form_status != 'quote_sent')
{
$results['message'] = 'You have to send the quote to the Customer before you will be able to create an Estimate from it in Quickbooks.';
set_transient('create-estimate-results', $results, HOUR_IN_SECONDS);
wp_redirect(admin_url('admin.php?page=quickbook-rfqs'));
exit(0);
}
$quote_data = get_transient('submission-quote-sent_' . $submission_id);
if (!empty($quote_data) && !empty($quote_data[$instance]) && !empty($quote_data[$instance][0]))
{
ini_set('memory_limit', '-1');
ini_set('max_input_time', '-1');
ini_set('max_execution_time', '-1');
set_time_limit(0);
$data = $quote_data[$instance][0];
$csv_lines = explode("\n", $data['csv_items_file']);
$csv_items = processItemsInCSV($csv_lines);
if (!empty($csv_items))
$data = array_merge($data, $csv_items);
if (function_exists('date_default_timezone_set'))
date_default_timezone_set('America/New_York');
$output = $qb_ajax_submissions = $estimate_data = array();
$noninventory_items = $estimate_lineitems = array();
foreach($data['parts'] as $index => $part)
{
$noninventory_items[$index] = array(
'noBid' => $part['noBid'],
'name' => htmlspecialchars(stripslashes($part['part']), ENT_NOQUOTES),
'quantity' => stripdoublequotes($part['quantity']),
'vendor' => !empty($part['vendor']) ? htmlspecialchars(stripslashes($part['vendor']), ENT_NOQUOTES) : '',
'source' => htmlspecialchars(stripdoublequotes($part['source']), ENT_NOQUOTES),
'type' => htmlspecialchars(stripdoublequotes($part['type']), ENT_NOQUOTES),
'cost' => !empty($part['cost']) ? stripdoublequotes($part['cost']) : 0,
'price' => !empty($part['price']) ? stripdoublequotes($part['price']) : 0,
'effectDate' => !empty($part['effectDate']) ? $part['effectDate'] : '',
'purchase_desc' => '', // Default
'sales_desc' => '' // Default
);
$itemDescriptions = $wpdb->get_row("
SELECT IF(qi.PurchaseDesc IS NULL OR qi.PurchaseDesc = '', qvi.ItemDescription, qi.PurchaseDesc) AS purchase_desc, IF(qi.SalesDesc IS NULL OR qi.SalesDesc = '', qvi.ItemDescription, qi.SalesDesc) AS sales_desc
FROM " . $wpdb->prefix . "quickbook_vendor_items AS qvi, " . $wpdb->prefix . "quickbook_items AS qi
WHERE (qvi.EstimateID = 0 AND qvi.ItemName = '" . addcslashes($part['part'], "'") . "' AND qvi.IsActive = 1) OR qi.Name = '" . addcslashes($part['part'], "'") . "'", ARRAY_A);
if (!empty($itemDescriptions))
{
$noninventory_items[$index]['purchase_desc'] = !empty($itemDescriptions['purchase_desc']) ? htmlspecialchars($itemDescriptions['purchase_desc'], ENT_NOQUOTES) : '';
$noninventory_items[$index]['sales_desc'] = !empty($itemDescriptions['sales_desc']) ? htmlspecialchars($itemDescriptions['sales_desc'], ENT_NOQUOTES) : '';
}
}
// Adding the Class file.
require_once(get_stylesheet_directory() . '/QuickBooks.php');
$fsubmit_time = str_replace('.', '_', $submit_time);
delete_transient('vQBIDs_' . $submission_id . '_' . $fsubmit_time);
$estimate_data = array(
'action' => 'create_estimate',
'submission_id' => $submission_id,
'submit_time' => $submit_time,
'instance' => $instance,
'additional_columns' => array(
'PaymentTerm' => array(
'value' => !empty($data['payment_term']) ? htmlspecialchars($data['payment_term'], ENT_NOQUOTES) : '',
'format' => '%s'
),
'OrderNotes' => array(
'value' => !empty($data['order_notes']) ? htmlspecialchars(stripslashes($data['order_notes']), ENT_NOQUOTES) : '',
'format' => '%s'
),
'PreparedBy' => array(
'value' => !empty($data['prepared_by']) ? htmlspecialchars($data['prepared_by'], ENT_NOQUOTES) : '',
'format' => '%s'
),
'QuoteSent' => array(
'value' => !empty($data['quote_sent_timestamp']) ? date('Y-m-d H:i:s', (int) $data['quote_sent_timestamp']) : date('Y-m-d H:i:s'),
'format' => '%s'
)
),
'additional_secondary_columns' => array(
'items' => array_column($data['parts'], 'part'),
'columns' => array(
'ConditionCode' => array(
'values' => array_column($data['parts'], 'conditionCode'),
'format' => '%s'
),
'DeliveryTerm' => array(
'values' => array_column($data['parts'], 'deliveryTerm'),
'format' => '%s'
)
)
)
);
$item_data = array(
'submission_id' => $submission_id,
'submit_time' => $submit_time
);
$customer_listid = $wpdb->get_var("SELECT ListID
FROM " . $wpdb->prefix . "quickbook_customers
WHERE Email = '" . $data['email'] . "'");
$addresses = array(
'BillAddress' => array(
'billing_addr1' => 'Addr1',
'billing_addr2' => 'Addr2',
'billing_city' => 'City',
'billing_state' => 'State',
'billing_zip' => 'PostalCode',
'billing_country' => 'Country'
),
'ShipAddress' => array(
'shipping_addr1' => 'Addr1',
'shipping_addr2' => 'Addr2',
'shipping_city' => 'City',
'shipping_state' => 'State',
'shipping_zip' => 'PostalCode',
'shipping_country' => 'Country'
)
);
if (empty($customer_listid))
{
// NEW CUSTOMER
$data['has_email'] = false;
$customer_data = array(
'request' => array(
'Name' => $data['fName'] . ' ' . $data['lName'],
'CompanyName' => htmlspecialchars(html_entity_decode($data['company']), ENT_NOQUOTES),
'FirstName' => $data['fName'],
'LastName' => $data['lName'],
'BillAddress' => array(),
'ShipAddress' => array(),
'Phone' => !empty($data['tel']) ? $data['tel'] : '',
'Email' => $data['email']
),
'submit_time' => $submit_time,
'instance' => $instance
);
foreach($addresses as $address_type => $address)
{
if ($address_type == 'ShipAddress' && empty($data['has_shipping']))
continue;
foreach($address as $type => $key)
if (!empty($data[$type]))
$customer_data['request'][$address_type][$key] = htmlspecialchars($data[$type], ENT_NOQUOTES);
}
if (!isset($Queue))
{
$dsn = new mysqli(DB_HOST, DB_USER, DB_PASSWORD, DB_NAME);
$Queue = new QuickBooks_WebConnector_Queue($dsn);
}
// High Priority
$Queue->enqueue(QUICKBOOKS_ADD_CUSTOMER, null, 30, $customer_data);
}
else
{
$data['has_email'] = true;
$estimate_data['ListID'] = $customer_listid;
}
$vendors = array_column($data['parts'], 'vendor');
$vendors = !empty($vendors) ? array_filter($vendors) : array();
if (!empty($vendors))
{
$qbdb_vendors = $wpdb->get_results("
SELECT ListID, Name
FROM " . $wpdb->prefix . "quickbook_vendors
WHERE Name IN ('" . implode('\',\'', array_map('addslashes', $vendors)) . "')
", ARRAY_A);
}
$vendor_db = array(
'Names' => array(),
'ListIDs' => array()
);
$vendors_to_create = $items_to_create = array();
if (!empty($qbdb_vendors))
{
foreach($qbdb_vendors as $qvendors)
{
$vendor_db['Names'][] = $qvendors['Name'];
foreach($noninventory_items as $in => $ni)
{
if ($ni['vendor'] == $qvendors['Name'])
$noninventory_items[$in]['VendorListID'] = $qvendors['ListID'];
}
$vendor_db['ListIDs'][] = $qvendors['ListID'];
}
$vendors_to_create = array_unique(array_diff($vendors, $vendor_db['Names']));
}
else
{
if (!empty($vendors))
$vendors_to_create = array_unique($vendors);
}
if (!empty($vendors_to_create))
{
if (!isset($Queue))
{
$dsn = new mysqli(DB_HOST, DB_USER, DB_PASSWORD, DB_NAME);
$Queue = new QuickBooks_WebConnector_Queue($dsn);
}
foreach($vendors_to_create as $vendor_name)
$Queue->enqueue(QUICKBOOKS_ADD_VENDOR, null, 30, htmlspecialchars($vendor_name, ENT_NOQUOTES));
}
$items = array_column($data['parts'], 'part');
$qbdb_items = $wpdb->get_results("
SELECT ListID, Name, EditSequence
FROM " . $wpdb->prefix . "quickbook_items
WHERE Name IN ('" . implode('\',\'', array_map('addslashes', $items)) . "')
", ARRAY_A);
$item_db = array(
'Names' => array(),
'ListIDs' => array(),
'EditSequences' => array()
);
if (!empty($qbdb_items))
{
foreach($qbdb_items as $qitems)
{
$item_db['Names'][] = $qitems['Name'];
foreach($noninventory_items as $in => $ni)
{
if ($ni['name'] == $qitems['Name'])
$noninventory_items[$in]['ItemListID'] = $qitems['ListID'];
}
$item_db['ListIDs'][] = $qitems['ListID'];
$item_db['EditSequences'][] = $qitems['EditSequence'];
}
$items_to_create = array_unique(array_diff($items, $item_db['Names']));
}
else
{
// New Items
$items_to_create = array_unique($items);
}
if (!empty($items_to_create))
{
if (!isset($Queue))
{
$dsn = new mysqli(DB_HOST, DB_USER, DB_PASSWORD, DB_NAME);
$Queue = new QuickBooks_WebConnector_Queue($dsn);
}
// Adding items.
foreach($items_to_create as $item_name)
$Queue->enqueue(QUICKBOOKS_ADD_NONINVENTORYITEM, null, 20, array_merge($item_data, array('name' => htmlspecialchars($item_name, ENT_NOQUOTES), 'all_items' => $noninventory_items)));
}
if (!empty($item_db['Names']))
{
if (!isset($Queue))
{
$dsn = new mysqli(DB_HOST, DB_USER, DB_PASSWORD, DB_NAME);
$Queue = new QuickBooks_WebConnector_Queue($dsn);
}
foreach($item_db['Names'] as $iK => $iKName)
$Queue->enqueue(QUICKBOOKS_MOD_NONINVENTORYITEM, null, 20, array_merge($item_data, array('item_ListID' => $item_db['ListIDs'][$iK], 'item_EditSequence' => $item_db['EditSequences'][$iK], 'item_Name' => htmlspecialchars($iKName, ENT_NOQUOTES), 'all_items' => $noninventory_items)));
}
foreach($addresses as $address_type => $address)
{
// Is shipping?
if ($address_type == 'ShipAddress' && empty($data['has_shipping']))
continue;
foreach($address as $type => $key)
$estimate_data[$address_type][$key] = !empty($data[$type]) ? htmlspecialchars($data[$type], ENT_NOQUOTES) : '';
}
// Submit the Estimate Now...
if (!isset($Queue))
{
$dsn = new mysqli(DB_HOST, DB_USER, DB_PASSWORD, DB_NAME);
$Queue = new QuickBooks_WebConnector_Queue($dsn);
}
$estimate_data = array_merge($estimate_data, array('all_items' => $noninventory_items));
$Queue->enqueue(QUICKBOOKS_ADD_ESTIMATE, null, 10, $estimate_data);
$wpdb->update(
$wpdb->prefix . 'cf7dbplugin_submits',
array(
'field_value' => 'quote_approved'
),
array(
'form_name' => get_option('quickbooks_cf7_form', 'Personal Info'),
'submit_time' => $submit_time,
'instance' => $instance,
'field_name' => 'status'
),
array('%s'),
array('%s', '%s', '%d', '%s')
);
// success...
$results = array('type' => 'success', 'message' => sprintf('An Estimate for Quote #%1$s-%2$d has been successfully queued in Quickbooks. Once it has been created in Quickbooks, it will show up in the Quotes Tab. If an error occurs while trying to create the estimate, the quote will remain available from within this section to resubmit again.', $submission_id, $instance));
set_transient('create-estimate-results', $results, HOUR_IN_SECONDS);
wp_redirect(admin_url('admin.php?page=quickbook-rfqs'));
exit(0);
}
// If error occurred, this gets sent to the transient...
set_transient('create-estimate-results', $results, HOUR_IN_SECONDS);
wp_redirect(admin_url('admin.php?page=quickbook-rfqs'));
exit(0);
}
下面是处理 QUICKBOOKS_ADD_ESTIMATE
的请求和响应的函数
function _quickbooks_estimate_add_request($requestID, $user, $action, $ID, $extra, &$err, $last_action_time, $last_actionident_time, $version, $locale)
{
global $wpdb;
$Estimate = new QuickBooks_QBXML_Object_Estimate();
$form_instance = !empty($extra['instance']) ? (int) $extra['instance'] : 0;
$form_instance = empty($form_instance) ? 0 : $form_instance;
if (!empty($extra['ListID']))
$Estimate->setCustomerListID($extra['ListID']);
else
{
// Get the customer list id
$customers_id = $wpdb->get_var("SELECT field_value FROM " . $wpdb->prefix . "cf7dbplugin_submits WHERE submit_time = " . $extra['submit_time'] . " AND instance = " . $form_instance . " AND field_name = 'customer_id' AND form_name = '" . get_option('quickbooks_cf7_form', 'Personal Info') . "'");
$customers_listid = $wpdb->get_var("SELECT ListID FROM " . $wpdb->prefix . "quickbook_customers WHERE id = " . intval($customers_id));
$Estimate->setCustomerListID($customers_listid);
}
$Estimate->setTxnDate(date('Y-m-d', time()));
$province = !empty($extra['BillAddress']['Country']) ? htmlspecialchars_decode($extra['BillAddress']['State'], ENT_NOQUOTES) : '';
$state = empty($extra['BillAddress']['Country']) ? htmlspecialchars_decode($extra['BillAddress']['State'], ENT_NOQUOTES) : '';
$Estimate->setBillAddress(htmlspecialchars_decode($extra['BillAddress']['Addr1'], ENT_NOQUOTES), htmlspecialchars_decode($extra['BillAddress']['Addr2'], ENT_NOQUOTES), '', '', '', htmlspecialchars_decode($extra['BillAddress']['City'], ENT_NOQUOTES), $state, $province, htmlspecialchars_decode($extra['BillAddress']['PostalCode'], ENT_NOQUOTES), htmlspecialchars_decode($extra['BillAddress']['Country'], ENT_NOQUOTES), '');
if (!empty($extra['ShipAddress']))
{
$ship_province = !empty($extra['ShipAddress']['Country']) ? htmlspecialchars_decode($extra['ShipAddress']['State'], ENT_NOQUOTES) : '';
$ship_state = empty($extra['ShipAddress']['Country']) ? htmlspecialchars_decode($extra['ShipAddress']['State'], ENT_NOQUOTES) : '';
$Estimate->setShipAddress(htmlspecialchars_decode($extra['ShipAddress']['Addr1'], ENT_NOQUOTES), htmlspecialchars_decode($extra['ShipAddress']['Addr2'], ENT_NOQUOTES), '', '', '', htmlspecialchars_decode($extra['ShipAddress']['City'], ENT_NOQUOTES), $ship_state, $ship_province, htmlspecialchars_decode($extra['ShipAddress']['PostalCode'], ENT_NOQUOTES), htmlspecialchars_decode($extra['ShipAddress']['Country'], ENT_NOQUOTES), '');
}
$Estimate->setIsToBeEmailed('true');
if (!empty($extra['all_items']))
{
foreach($extra['all_items'] as $item_data)
{
$EstimateLineItem = new QuickBooks_QBXML_Object_Estimate_EstimateLine();
$description = array();
if (!isset($item_data['ItemListID']))
{
$itemListID = $wpdb->get_var("SELECT ListID FROM " . $wpdb->prefix . "quickbook_items WHERE Name = '" . htmlspecialchars_decode(addcslashes($item_data['name'], "'"), ENT_NOQUOTES) . "' ORDER BY NULL LIMIT 1");
$item_data['ItemListID'] = $itemListID;
}
$EstimateLineItem->setItemListID($item_data['ItemListID']);
$description = array();
if (!empty($item_data['sales_desc']))
$description[] = htmlspecialchars_decode($item_data['sales_desc'], ENT_NOQUOTES);
if (!empty($item_data['purchase_desc']))
$description[] = htmlspecialchars_decode($item_data['purchase_desc'], ENT_NOQUOTES);
if (!empty($description))
$EstimateLineItem->setDescription(implode(' ', $description));
$EstimateLineItem->setQuantity($item_data['quantity']);
if (!empty($item_data['noBid']))
{
$EstimateLineItem->setAmount(0);
$EstimateLineItem->setOther1('NO BID');
}
$Estimate->addEstimateLine($EstimateLineItem);
}
}
$qbxml = $Estimate->asQBXML(QUICKBOOKS_ADD_ESTIMATE);
$xml = '<?xml version="1.0" encoding="utf-8"?>
<?qbxml version="13.0"?>
<QBXML>
<QBXMLMsgsRq onError="stopOnError">
' . $qbxml . '
</QBXMLMsgsRq>
</QBXML>';
file_put_contents(dirname(__FILE__) . '/xml.log', $xml . PHP_EOL . var_export($xml, true) . PHP_EOL . PHP_EOL , FILE_APPEND | LOCK_EX);
return $xml;
}
function _quickbooks_estimate_add_response($requestID, $user, $action, $ID, $extra, &$err, $last_action_time, $last_actionident_time, $xml, $idents)
{
global $wpdb, $tables_response;
$estimate = json_decode(json_encode(simplexml_load_string($xml, "SimpleXMLElement", LIBXML_NOCDATA)), true);
if (!empty($tables_response[$action]) && !empty($estimate['QBXMLMsgsRs']) && !empty($estimate['QBXMLMsgsRs'][$tables_response[$action]['response_action']]) && !empty($estimate['QBXMLMsgsRs'][$tables_response[$action]['response_action']][$tables_response[$action]['response_subaction']]))
{
$estimate_data = $estimate['QBXMLMsgsRs'][$tables_response[$action]['response_action']][$tables_response[$action]['response_subaction']];
$is_magic_quotes = get_magic_quotes_gpc();
if (isset($estimate_data['EstimateLineRet']))
{
$estimate_lineitems = $estimate_data['EstimateLineRet'];
unset($estimate_data['EstimateLineRet']);
}
$estimate_info = hunter_build_table_data_array($estimate_data, $tables_response[$action]['columns']);
// Insert Customers...
if (!empty($estimate_info['add']))
{
$add_columns = array(
'names_and_values' => array(),
'formats' => array()
);
foreach($tables_response[$action]['columns'] as $column_name => $format)
{
if (isset($estimate_info['add'][$column_name]))
{
$add_columns['names_and_values'][$column_name] = htmlspecialchars_decode($estimate_info['add'][$column_name], ENT_NOQUOTES);
$add_columns['formats'][] = $format;
}
}
// Add in the additional columns that are used for tracking, but not included in Quickbooks.
if (!empty($extra['additional_columns']))
{
foreach($extra['additional_columns'] as $column_name => $column_data)
{
$add_columns['names_and_values'][$column_name] = htmlspecialchars_decode($extra['additional_columns'][$column_name]['value'], ENT_NOQUOTES);
$add_columns['formats'][] = $extra['additional_columns'][$column_name]['format'];
}
}
// Add Estimate into Database!
$wpdb->insert(
$tables_response[$action]['table'],
$add_columns['names_and_values'],
$add_columns['formats']
);
$submit_time = str_replace('.', '_', $extra['submit_time']);
$vendor_item_ids_transient = get_transient('vQBIDs_' . $extra['submission_id'] . '_' . $submit_time);
if (!empty($vendor_item_ids_transient))
{
$vendor_item_ids = implode(',', $vendor_item_ids_transient);
$wpdb->query("UPDATE " . $wpdb->prefix . "quickbook_vendor_items SET EstimateID = " . $wpdb->insert_id . " WHERE id IN(" . $vendor_item_ids . ")");
delete_transient('vQBIDs_' . $extra['submission_id'] . '_' . $submit_time);
}
$estimate_info['lineitems'] = array(
'EstimateTxnID' => isset($estimate_info['add']['TxnID']) ? $estimate_info['add']['TxnID'] : '',
'Items' => !empty($estimate_lineitems) ? $estimate_lineitems : array()
);
if (isset($estimate_info['add']['TxnID'], $estimate_info['add']['EditSequence']))
$table_data[str_replace('-', '_', $estimate_info['add']['TxnID'])] = $estimate_info['add']['EditSequence'];
}
if (!empty($tables_response[$action]['secondary_table']) && !empty($tables_response[$action]['secondary_columns']) && !empty($estimate_info['lineitems']))
{
$lineitems = array();
$estimate_lineitems = isAssociativeArray($estimate_info['lineitems']['Items']) ? array($estimate_info['lineitems']['Items']) : $estimate_info['lineitems']['Items'];
foreach($estimate_lineitems as $index => $information)
{
foreach($information as $key => $item_info)
{
if ($key == 'Quantity')
{
$item_info = (float) $item_info;
if (empty($item_info) || $item_info == '0.00')
$item_info = 0;
}
// Desc is reserved in MYSQL, so change to Description
if ($key == 'Desc')
$key = 'Description';
if (is_array($item_info))
{
foreach($item_info as $subkey => $value)
{
if (!is_array($value) && isset($tables_response[$action]['secondary_columns'][$key . '_' . $subkey]))
$lineitems[$index][$key . '_' . $subkey] = $is_magic_quotes ? htmlspecialchars_decode(stripslashes($value), ENT_NOQUOTES) : htmlspecialchars_decode($value, ENT_NOQUOTES);
}
}
else if (isset($tables_response[$action]['secondary_columns'][$key]))
$lineitems[$index][$key] = $is_magic_quotes ? htmlspecialchars_decode(stripslashes($item_info), ENT_NOQUOTES) : htmlspecialchars_decode($item_info, ENT_NOQUOTES);
}
}
if (!empty($lineitems))
{
foreach($lineitems as $lineitem)
{
$secondary_columns = array(
'names_and_values' => array(),
'formats' => array()
);
foreach($tables_response[$action]['secondary_columns'] as $secondary_column_name => $secondary_format)
{
if (isset($lineitem[$secondary_column_name]))
{
$secondary_columns['names_and_values'][$secondary_column_name] = $lineitem[$secondary_column_name];
$secondary_columns['formats'][] = $secondary_format;
}
}
// Additional columns
if (!empty($extra['additional_secondary_columns']))
{
$founds = array_keys($extra['additional_secondary_columns']['items'], $lineitem['ItemRef_FullName']);
if (!empty($founds))
{
foreach($founds as $key)
{
foreach($extra['additional_secondary_columns']['columns'] as $db_column_name => $col_data)
{
// start with empty array...
$dsata = array();
$dsata[$db_column_name] = $col_data['values'][$key];
$secondary_columns['names_and_values'] = array_merge($seconday_columns['names_and_values'], $dsata);
$secondary_columns['formats'][] = $col_data['format'];
}
}
}
}
$secondary_columns['names_and_values'] = array_merge(array('EstimateTxnID' => $estimate_info['lineitems']['EstimateTxnID']), $secondary_columns['names_and_values']);
$secondary_columns['formats'] = array_merge(array('%s'), $secondary_columns['formats']);
$wpdb->insert(
$tables_response[$action]['secondary_table'],
$secondary_columns['names_and_values'],
$secondary_columns['formats']
);
}
}
}
}
}
当我file_put_contents
在请求函数中看到$xml
的结果时,我得到的是我之前发布的xml,这没有错。但是 Quickbooks 正在返回解析错误并且仅在添加 Estimate 时发生! Items、Vendors 和 Customer 都在 Quickbooks 中正确创建,只是添加 Estimate 是这里的问题。
任何时候你看到这个:
QuickBooks found an error when parsing the provided XML text stream.
您应该做的第一件事是从日志中获取 qbXML
,然后 运行 XML Validator
工具包含在 QuickBooks SDK
中。
XML Validator
工具会告诉您 运行 qbXML
到底出了什么问题。
查看 quickbooks_log
SQL table 并找到发送到 Web 连接器的实际 qbXML。或者,将 Web 连接器设置为 VERBOSE
模式并从 Web 连接器日志中获取 qbXML 请求。不要使用您自己的日志记录。这是不准确的。该框架插入了一个用于跟踪的 requestID
属性,因此您放置的任何日志记录都不会是实际发送的请求。
从日志中取出 qbXML,并通过 XML Validator 工具将其放入。它会告诉您行号和错误消息。
如果您 运行 通过 QuickBooks SDK 附带的 XML Validator
工具,您将获得:
Line: 14
LinePos: 23
Src Text: <Province>PA</Province>
Reason: Element content is invalid according to the DTD/Schema.
Expecting: State, PostalCode, Country, Note.
如果您查看 QuickBooks OSR
,您会注意到 OSR 中没有显示 <Province>
标签。
验证器告诉您 Province
标签无效,它期望您改为指定以下标签之一:State, PostalCode, Country, Note
。
XML 通过在 quickbooks_log table 中找到的“添加估计”请求发送到 quickbooks 的
XML 如下:
<?xml version="1.0" encoding="utf-8"?>
<?qbxml version="13.0"?>
<QBXML>
<QBXMLMsgsRq onError="stopOnError">
<EstimateAddRq requestID="212">
<EstimateAdd>
<CustomerRef>
<ListID>800007A5-1480913677</ListID>
</CustomerRef>
<TxnDate>2016-12-04</TxnDate>
<BillAddress>
<Addr1>2532 S. Franklin Street</Addr1>
<City>Philadelphia</City>
<Province>PA</Province>
<PostalCode>19148</PostalCode>
<Country>United States</Country>
</BillAddress>
<ShipAddress>
<Addr1>2406 E. York Street</Addr1>
<Addr2>Apartment #2B</Addr2>
<City>Philadelphia</City>
<Province>PA</Province>
<PostalCode>19125</PostalCode>
<Country>United States</Country>
</ShipAddress>
<IsToBeEmailed>true</IsToBeEmailed>
<EstimateLineAdd>
<ItemRef>
<ListID>800000C1-1480913684</ListID>
</ItemRef>
<Quantity>45</Quantity>
</EstimateLineAdd>
<EstimateLineAdd>
<ItemRef>
<ListID>800000BE-1480913680</ListID>
</ItemRef>
<Quantity>10</Quantity>
</EstimateLineAdd>
<EstimateLineAdd>
<ItemRef>
<ListID>800000C0-1480913683</ListID>
</ItemRef>
<Quantity>500</Quantity>
</EstimateLineAdd>
<EstimateLineAdd>
<ItemRef>
<ListID>800000BD-1480913679</ListID>
</ItemRef>
<Quantity>5</Quantity>
<Amount>0.00</Amount>
<Other1>NO BID</Other1>
</EstimateLineAdd>
<EstimateLineAdd>
<ItemRef>
<ListID>800000BF-1480913681</ListID>
</ItemRef>
<Quantity>10</Quantity>
</EstimateLineAdd>
</EstimateAdd>
</EstimateAddRq>
</QBXMLMsgsRq>
</QBXML>
估计函数的创建如下:
function hunter_create_estimate()
{
global $wpdb;
$submission_id = !empty($_POST['submission_id']) ? $_POST['submission_id'] : 0;
$submit_time = !empty($_POST['submit_time']) ? $_POST['submit_time'] : '';
$results = array(
'type' => 'error',
'message' => 'An Error Occurred when trying to create an Estimate for this Quote Request. Please try again.'
);
if (empty($submission_id) || empty($submit_time) || !isset($_POST['instance']))
{
set_transient('create-estimate-results', $results, HOUR_IN_SECONDS);
wp_redirect(admin_url('admin.php?page=quickbook-rfqs'));
exit(0);
}
if (!isset($_POST['_wpnonce_create_estimate_from_' . $submission_id]))
{
set_transient('create-estimate-results', $results, HOUR_IN_SECONDS);
wp_redirect(admin_url('admin.php?page=quickbook-rfqs'));
exit(0);
}
$nonce = $_POST['_wpnonce_create_estimate_from_' . $submission_id];
if (!wp_verify_nonce($nonce, 'hunter-create-estimate'))
{
set_transient('create-estimate-results', $results, HOUR_IN_SECONDS);
wp_redirect(admin_url('admin.php?page=quickbook-rfqs'));
exit(0);
}
if (!current_user_can('manage_options'))
{
$results['message'] = 'Sorry, but you do not have permission to create estimates.';
set_transient('create-estimate-results', $results, HOUR_IN_SECONDS);
wp_redirect(admin_url('admin.php?page=quickbook-rfqs'));
exit(0);
}
$instance = !empty($_POST['instance']) ? (int) $_POST['instance'] : 0;
$instance = empty($instance) ? 0 : $instance;
$form_name = get_option('quickbooks_cf7_form', 'Personal Info');
$form_status = $wpdb->get_var('
SELECT MAX(IF(field_name = "status", field_value, NULL)) AS status
FROM ' . $wpdb->prefix . 'cf7dbplugin_submits
WHERE form_name = "' . $form_name . '" AND submit_time = "' . $submit_time . '" AND instance = ' . $instance);
if ($form_status != 'quote_sent')
{
$results['message'] = 'You have to send the quote to the Customer before you will be able to create an Estimate from it in Quickbooks.';
set_transient('create-estimate-results', $results, HOUR_IN_SECONDS);
wp_redirect(admin_url('admin.php?page=quickbook-rfqs'));
exit(0);
}
$quote_data = get_transient('submission-quote-sent_' . $submission_id);
if (!empty($quote_data) && !empty($quote_data[$instance]) && !empty($quote_data[$instance][0]))
{
ini_set('memory_limit', '-1');
ini_set('max_input_time', '-1');
ini_set('max_execution_time', '-1');
set_time_limit(0);
$data = $quote_data[$instance][0];
$csv_lines = explode("\n", $data['csv_items_file']);
$csv_items = processItemsInCSV($csv_lines);
if (!empty($csv_items))
$data = array_merge($data, $csv_items);
if (function_exists('date_default_timezone_set'))
date_default_timezone_set('America/New_York');
$output = $qb_ajax_submissions = $estimate_data = array();
$noninventory_items = $estimate_lineitems = array();
foreach($data['parts'] as $index => $part)
{
$noninventory_items[$index] = array(
'noBid' => $part['noBid'],
'name' => htmlspecialchars(stripslashes($part['part']), ENT_NOQUOTES),
'quantity' => stripdoublequotes($part['quantity']),
'vendor' => !empty($part['vendor']) ? htmlspecialchars(stripslashes($part['vendor']), ENT_NOQUOTES) : '',
'source' => htmlspecialchars(stripdoublequotes($part['source']), ENT_NOQUOTES),
'type' => htmlspecialchars(stripdoublequotes($part['type']), ENT_NOQUOTES),
'cost' => !empty($part['cost']) ? stripdoublequotes($part['cost']) : 0,
'price' => !empty($part['price']) ? stripdoublequotes($part['price']) : 0,
'effectDate' => !empty($part['effectDate']) ? $part['effectDate'] : '',
'purchase_desc' => '', // Default
'sales_desc' => '' // Default
);
$itemDescriptions = $wpdb->get_row("
SELECT IF(qi.PurchaseDesc IS NULL OR qi.PurchaseDesc = '', qvi.ItemDescription, qi.PurchaseDesc) AS purchase_desc, IF(qi.SalesDesc IS NULL OR qi.SalesDesc = '', qvi.ItemDescription, qi.SalesDesc) AS sales_desc
FROM " . $wpdb->prefix . "quickbook_vendor_items AS qvi, " . $wpdb->prefix . "quickbook_items AS qi
WHERE (qvi.EstimateID = 0 AND qvi.ItemName = '" . addcslashes($part['part'], "'") . "' AND qvi.IsActive = 1) OR qi.Name = '" . addcslashes($part['part'], "'") . "'", ARRAY_A);
if (!empty($itemDescriptions))
{
$noninventory_items[$index]['purchase_desc'] = !empty($itemDescriptions['purchase_desc']) ? htmlspecialchars($itemDescriptions['purchase_desc'], ENT_NOQUOTES) : '';
$noninventory_items[$index]['sales_desc'] = !empty($itemDescriptions['sales_desc']) ? htmlspecialchars($itemDescriptions['sales_desc'], ENT_NOQUOTES) : '';
}
}
// Adding the Class file.
require_once(get_stylesheet_directory() . '/QuickBooks.php');
$fsubmit_time = str_replace('.', '_', $submit_time);
delete_transient('vQBIDs_' . $submission_id . '_' . $fsubmit_time);
$estimate_data = array(
'action' => 'create_estimate',
'submission_id' => $submission_id,
'submit_time' => $submit_time,
'instance' => $instance,
'additional_columns' => array(
'PaymentTerm' => array(
'value' => !empty($data['payment_term']) ? htmlspecialchars($data['payment_term'], ENT_NOQUOTES) : '',
'format' => '%s'
),
'OrderNotes' => array(
'value' => !empty($data['order_notes']) ? htmlspecialchars(stripslashes($data['order_notes']), ENT_NOQUOTES) : '',
'format' => '%s'
),
'PreparedBy' => array(
'value' => !empty($data['prepared_by']) ? htmlspecialchars($data['prepared_by'], ENT_NOQUOTES) : '',
'format' => '%s'
),
'QuoteSent' => array(
'value' => !empty($data['quote_sent_timestamp']) ? date('Y-m-d H:i:s', (int) $data['quote_sent_timestamp']) : date('Y-m-d H:i:s'),
'format' => '%s'
)
),
'additional_secondary_columns' => array(
'items' => array_column($data['parts'], 'part'),
'columns' => array(
'ConditionCode' => array(
'values' => array_column($data['parts'], 'conditionCode'),
'format' => '%s'
),
'DeliveryTerm' => array(
'values' => array_column($data['parts'], 'deliveryTerm'),
'format' => '%s'
)
)
)
);
$item_data = array(
'submission_id' => $submission_id,
'submit_time' => $submit_time
);
$customer_listid = $wpdb->get_var("SELECT ListID
FROM " . $wpdb->prefix . "quickbook_customers
WHERE Email = '" . $data['email'] . "'");
$addresses = array(
'BillAddress' => array(
'billing_addr1' => 'Addr1',
'billing_addr2' => 'Addr2',
'billing_city' => 'City',
'billing_state' => 'State',
'billing_zip' => 'PostalCode',
'billing_country' => 'Country'
),
'ShipAddress' => array(
'shipping_addr1' => 'Addr1',
'shipping_addr2' => 'Addr2',
'shipping_city' => 'City',
'shipping_state' => 'State',
'shipping_zip' => 'PostalCode',
'shipping_country' => 'Country'
)
);
if (empty($customer_listid))
{
// NEW CUSTOMER
$data['has_email'] = false;
$customer_data = array(
'request' => array(
'Name' => $data['fName'] . ' ' . $data['lName'],
'CompanyName' => htmlspecialchars(html_entity_decode($data['company']), ENT_NOQUOTES),
'FirstName' => $data['fName'],
'LastName' => $data['lName'],
'BillAddress' => array(),
'ShipAddress' => array(),
'Phone' => !empty($data['tel']) ? $data['tel'] : '',
'Email' => $data['email']
),
'submit_time' => $submit_time,
'instance' => $instance
);
foreach($addresses as $address_type => $address)
{
if ($address_type == 'ShipAddress' && empty($data['has_shipping']))
continue;
foreach($address as $type => $key)
if (!empty($data[$type]))
$customer_data['request'][$address_type][$key] = htmlspecialchars($data[$type], ENT_NOQUOTES);
}
if (!isset($Queue))
{
$dsn = new mysqli(DB_HOST, DB_USER, DB_PASSWORD, DB_NAME);
$Queue = new QuickBooks_WebConnector_Queue($dsn);
}
// High Priority
$Queue->enqueue(QUICKBOOKS_ADD_CUSTOMER, null, 30, $customer_data);
}
else
{
$data['has_email'] = true;
$estimate_data['ListID'] = $customer_listid;
}
$vendors = array_column($data['parts'], 'vendor');
$vendors = !empty($vendors) ? array_filter($vendors) : array();
if (!empty($vendors))
{
$qbdb_vendors = $wpdb->get_results("
SELECT ListID, Name
FROM " . $wpdb->prefix . "quickbook_vendors
WHERE Name IN ('" . implode('\',\'', array_map('addslashes', $vendors)) . "')
", ARRAY_A);
}
$vendor_db = array(
'Names' => array(),
'ListIDs' => array()
);
$vendors_to_create = $items_to_create = array();
if (!empty($qbdb_vendors))
{
foreach($qbdb_vendors as $qvendors)
{
$vendor_db['Names'][] = $qvendors['Name'];
foreach($noninventory_items as $in => $ni)
{
if ($ni['vendor'] == $qvendors['Name'])
$noninventory_items[$in]['VendorListID'] = $qvendors['ListID'];
}
$vendor_db['ListIDs'][] = $qvendors['ListID'];
}
$vendors_to_create = array_unique(array_diff($vendors, $vendor_db['Names']));
}
else
{
if (!empty($vendors))
$vendors_to_create = array_unique($vendors);
}
if (!empty($vendors_to_create))
{
if (!isset($Queue))
{
$dsn = new mysqli(DB_HOST, DB_USER, DB_PASSWORD, DB_NAME);
$Queue = new QuickBooks_WebConnector_Queue($dsn);
}
foreach($vendors_to_create as $vendor_name)
$Queue->enqueue(QUICKBOOKS_ADD_VENDOR, null, 30, htmlspecialchars($vendor_name, ENT_NOQUOTES));
}
$items = array_column($data['parts'], 'part');
$qbdb_items = $wpdb->get_results("
SELECT ListID, Name, EditSequence
FROM " . $wpdb->prefix . "quickbook_items
WHERE Name IN ('" . implode('\',\'', array_map('addslashes', $items)) . "')
", ARRAY_A);
$item_db = array(
'Names' => array(),
'ListIDs' => array(),
'EditSequences' => array()
);
if (!empty($qbdb_items))
{
foreach($qbdb_items as $qitems)
{
$item_db['Names'][] = $qitems['Name'];
foreach($noninventory_items as $in => $ni)
{
if ($ni['name'] == $qitems['Name'])
$noninventory_items[$in]['ItemListID'] = $qitems['ListID'];
}
$item_db['ListIDs'][] = $qitems['ListID'];
$item_db['EditSequences'][] = $qitems['EditSequence'];
}
$items_to_create = array_unique(array_diff($items, $item_db['Names']));
}
else
{
// New Items
$items_to_create = array_unique($items);
}
if (!empty($items_to_create))
{
if (!isset($Queue))
{
$dsn = new mysqli(DB_HOST, DB_USER, DB_PASSWORD, DB_NAME);
$Queue = new QuickBooks_WebConnector_Queue($dsn);
}
// Adding items.
foreach($items_to_create as $item_name)
$Queue->enqueue(QUICKBOOKS_ADD_NONINVENTORYITEM, null, 20, array_merge($item_data, array('name' => htmlspecialchars($item_name, ENT_NOQUOTES), 'all_items' => $noninventory_items)));
}
if (!empty($item_db['Names']))
{
if (!isset($Queue))
{
$dsn = new mysqli(DB_HOST, DB_USER, DB_PASSWORD, DB_NAME);
$Queue = new QuickBooks_WebConnector_Queue($dsn);
}
foreach($item_db['Names'] as $iK => $iKName)
$Queue->enqueue(QUICKBOOKS_MOD_NONINVENTORYITEM, null, 20, array_merge($item_data, array('item_ListID' => $item_db['ListIDs'][$iK], 'item_EditSequence' => $item_db['EditSequences'][$iK], 'item_Name' => htmlspecialchars($iKName, ENT_NOQUOTES), 'all_items' => $noninventory_items)));
}
foreach($addresses as $address_type => $address)
{
// Is shipping?
if ($address_type == 'ShipAddress' && empty($data['has_shipping']))
continue;
foreach($address as $type => $key)
$estimate_data[$address_type][$key] = !empty($data[$type]) ? htmlspecialchars($data[$type], ENT_NOQUOTES) : '';
}
// Submit the Estimate Now...
if (!isset($Queue))
{
$dsn = new mysqli(DB_HOST, DB_USER, DB_PASSWORD, DB_NAME);
$Queue = new QuickBooks_WebConnector_Queue($dsn);
}
$estimate_data = array_merge($estimate_data, array('all_items' => $noninventory_items));
$Queue->enqueue(QUICKBOOKS_ADD_ESTIMATE, null, 10, $estimate_data);
$wpdb->update(
$wpdb->prefix . 'cf7dbplugin_submits',
array(
'field_value' => 'quote_approved'
),
array(
'form_name' => get_option('quickbooks_cf7_form', 'Personal Info'),
'submit_time' => $submit_time,
'instance' => $instance,
'field_name' => 'status'
),
array('%s'),
array('%s', '%s', '%d', '%s')
);
// success...
$results = array('type' => 'success', 'message' => sprintf('An Estimate for Quote #%1$s-%2$d has been successfully queued in Quickbooks. Once it has been created in Quickbooks, it will show up in the Quotes Tab. If an error occurs while trying to create the estimate, the quote will remain available from within this section to resubmit again.', $submission_id, $instance));
set_transient('create-estimate-results', $results, HOUR_IN_SECONDS);
wp_redirect(admin_url('admin.php?page=quickbook-rfqs'));
exit(0);
}
// If error occurred, this gets sent to the transient...
set_transient('create-estimate-results', $results, HOUR_IN_SECONDS);
wp_redirect(admin_url('admin.php?page=quickbook-rfqs'));
exit(0);
}
下面是处理 QUICKBOOKS_ADD_ESTIMATE
的请求和响应的函数function _quickbooks_estimate_add_request($requestID, $user, $action, $ID, $extra, &$err, $last_action_time, $last_actionident_time, $version, $locale)
{
global $wpdb;
$Estimate = new QuickBooks_QBXML_Object_Estimate();
$form_instance = !empty($extra['instance']) ? (int) $extra['instance'] : 0;
$form_instance = empty($form_instance) ? 0 : $form_instance;
if (!empty($extra['ListID']))
$Estimate->setCustomerListID($extra['ListID']);
else
{
// Get the customer list id
$customers_id = $wpdb->get_var("SELECT field_value FROM " . $wpdb->prefix . "cf7dbplugin_submits WHERE submit_time = " . $extra['submit_time'] . " AND instance = " . $form_instance . " AND field_name = 'customer_id' AND form_name = '" . get_option('quickbooks_cf7_form', 'Personal Info') . "'");
$customers_listid = $wpdb->get_var("SELECT ListID FROM " . $wpdb->prefix . "quickbook_customers WHERE id = " . intval($customers_id));
$Estimate->setCustomerListID($customers_listid);
}
$Estimate->setTxnDate(date('Y-m-d', time()));
$province = !empty($extra['BillAddress']['Country']) ? htmlspecialchars_decode($extra['BillAddress']['State'], ENT_NOQUOTES) : '';
$state = empty($extra['BillAddress']['Country']) ? htmlspecialchars_decode($extra['BillAddress']['State'], ENT_NOQUOTES) : '';
$Estimate->setBillAddress(htmlspecialchars_decode($extra['BillAddress']['Addr1'], ENT_NOQUOTES), htmlspecialchars_decode($extra['BillAddress']['Addr2'], ENT_NOQUOTES), '', '', '', htmlspecialchars_decode($extra['BillAddress']['City'], ENT_NOQUOTES), $state, $province, htmlspecialchars_decode($extra['BillAddress']['PostalCode'], ENT_NOQUOTES), htmlspecialchars_decode($extra['BillAddress']['Country'], ENT_NOQUOTES), '');
if (!empty($extra['ShipAddress']))
{
$ship_province = !empty($extra['ShipAddress']['Country']) ? htmlspecialchars_decode($extra['ShipAddress']['State'], ENT_NOQUOTES) : '';
$ship_state = empty($extra['ShipAddress']['Country']) ? htmlspecialchars_decode($extra['ShipAddress']['State'], ENT_NOQUOTES) : '';
$Estimate->setShipAddress(htmlspecialchars_decode($extra['ShipAddress']['Addr1'], ENT_NOQUOTES), htmlspecialchars_decode($extra['ShipAddress']['Addr2'], ENT_NOQUOTES), '', '', '', htmlspecialchars_decode($extra['ShipAddress']['City'], ENT_NOQUOTES), $ship_state, $ship_province, htmlspecialchars_decode($extra['ShipAddress']['PostalCode'], ENT_NOQUOTES), htmlspecialchars_decode($extra['ShipAddress']['Country'], ENT_NOQUOTES), '');
}
$Estimate->setIsToBeEmailed('true');
if (!empty($extra['all_items']))
{
foreach($extra['all_items'] as $item_data)
{
$EstimateLineItem = new QuickBooks_QBXML_Object_Estimate_EstimateLine();
$description = array();
if (!isset($item_data['ItemListID']))
{
$itemListID = $wpdb->get_var("SELECT ListID FROM " . $wpdb->prefix . "quickbook_items WHERE Name = '" . htmlspecialchars_decode(addcslashes($item_data['name'], "'"), ENT_NOQUOTES) . "' ORDER BY NULL LIMIT 1");
$item_data['ItemListID'] = $itemListID;
}
$EstimateLineItem->setItemListID($item_data['ItemListID']);
$description = array();
if (!empty($item_data['sales_desc']))
$description[] = htmlspecialchars_decode($item_data['sales_desc'], ENT_NOQUOTES);
if (!empty($item_data['purchase_desc']))
$description[] = htmlspecialchars_decode($item_data['purchase_desc'], ENT_NOQUOTES);
if (!empty($description))
$EstimateLineItem->setDescription(implode(' ', $description));
$EstimateLineItem->setQuantity($item_data['quantity']);
if (!empty($item_data['noBid']))
{
$EstimateLineItem->setAmount(0);
$EstimateLineItem->setOther1('NO BID');
}
$Estimate->addEstimateLine($EstimateLineItem);
}
}
$qbxml = $Estimate->asQBXML(QUICKBOOKS_ADD_ESTIMATE);
$xml = '<?xml version="1.0" encoding="utf-8"?>
<?qbxml version="13.0"?>
<QBXML>
<QBXMLMsgsRq onError="stopOnError">
' . $qbxml . '
</QBXMLMsgsRq>
</QBXML>';
file_put_contents(dirname(__FILE__) . '/xml.log', $xml . PHP_EOL . var_export($xml, true) . PHP_EOL . PHP_EOL , FILE_APPEND | LOCK_EX);
return $xml;
}
function _quickbooks_estimate_add_response($requestID, $user, $action, $ID, $extra, &$err, $last_action_time, $last_actionident_time, $xml, $idents)
{
global $wpdb, $tables_response;
$estimate = json_decode(json_encode(simplexml_load_string($xml, "SimpleXMLElement", LIBXML_NOCDATA)), true);
if (!empty($tables_response[$action]) && !empty($estimate['QBXMLMsgsRs']) && !empty($estimate['QBXMLMsgsRs'][$tables_response[$action]['response_action']]) && !empty($estimate['QBXMLMsgsRs'][$tables_response[$action]['response_action']][$tables_response[$action]['response_subaction']]))
{
$estimate_data = $estimate['QBXMLMsgsRs'][$tables_response[$action]['response_action']][$tables_response[$action]['response_subaction']];
$is_magic_quotes = get_magic_quotes_gpc();
if (isset($estimate_data['EstimateLineRet']))
{
$estimate_lineitems = $estimate_data['EstimateLineRet'];
unset($estimate_data['EstimateLineRet']);
}
$estimate_info = hunter_build_table_data_array($estimate_data, $tables_response[$action]['columns']);
// Insert Customers...
if (!empty($estimate_info['add']))
{
$add_columns = array(
'names_and_values' => array(),
'formats' => array()
);
foreach($tables_response[$action]['columns'] as $column_name => $format)
{
if (isset($estimate_info['add'][$column_name]))
{
$add_columns['names_and_values'][$column_name] = htmlspecialchars_decode($estimate_info['add'][$column_name], ENT_NOQUOTES);
$add_columns['formats'][] = $format;
}
}
// Add in the additional columns that are used for tracking, but not included in Quickbooks.
if (!empty($extra['additional_columns']))
{
foreach($extra['additional_columns'] as $column_name => $column_data)
{
$add_columns['names_and_values'][$column_name] = htmlspecialchars_decode($extra['additional_columns'][$column_name]['value'], ENT_NOQUOTES);
$add_columns['formats'][] = $extra['additional_columns'][$column_name]['format'];
}
}
// Add Estimate into Database!
$wpdb->insert(
$tables_response[$action]['table'],
$add_columns['names_and_values'],
$add_columns['formats']
);
$submit_time = str_replace('.', '_', $extra['submit_time']);
$vendor_item_ids_transient = get_transient('vQBIDs_' . $extra['submission_id'] . '_' . $submit_time);
if (!empty($vendor_item_ids_transient))
{
$vendor_item_ids = implode(',', $vendor_item_ids_transient);
$wpdb->query("UPDATE " . $wpdb->prefix . "quickbook_vendor_items SET EstimateID = " . $wpdb->insert_id . " WHERE id IN(" . $vendor_item_ids . ")");
delete_transient('vQBIDs_' . $extra['submission_id'] . '_' . $submit_time);
}
$estimate_info['lineitems'] = array(
'EstimateTxnID' => isset($estimate_info['add']['TxnID']) ? $estimate_info['add']['TxnID'] : '',
'Items' => !empty($estimate_lineitems) ? $estimate_lineitems : array()
);
if (isset($estimate_info['add']['TxnID'], $estimate_info['add']['EditSequence']))
$table_data[str_replace('-', '_', $estimate_info['add']['TxnID'])] = $estimate_info['add']['EditSequence'];
}
if (!empty($tables_response[$action]['secondary_table']) && !empty($tables_response[$action]['secondary_columns']) && !empty($estimate_info['lineitems']))
{
$lineitems = array();
$estimate_lineitems = isAssociativeArray($estimate_info['lineitems']['Items']) ? array($estimate_info['lineitems']['Items']) : $estimate_info['lineitems']['Items'];
foreach($estimate_lineitems as $index => $information)
{
foreach($information as $key => $item_info)
{
if ($key == 'Quantity')
{
$item_info = (float) $item_info;
if (empty($item_info) || $item_info == '0.00')
$item_info = 0;
}
// Desc is reserved in MYSQL, so change to Description
if ($key == 'Desc')
$key = 'Description';
if (is_array($item_info))
{
foreach($item_info as $subkey => $value)
{
if (!is_array($value) && isset($tables_response[$action]['secondary_columns'][$key . '_' . $subkey]))
$lineitems[$index][$key . '_' . $subkey] = $is_magic_quotes ? htmlspecialchars_decode(stripslashes($value), ENT_NOQUOTES) : htmlspecialchars_decode($value, ENT_NOQUOTES);
}
}
else if (isset($tables_response[$action]['secondary_columns'][$key]))
$lineitems[$index][$key] = $is_magic_quotes ? htmlspecialchars_decode(stripslashes($item_info), ENT_NOQUOTES) : htmlspecialchars_decode($item_info, ENT_NOQUOTES);
}
}
if (!empty($lineitems))
{
foreach($lineitems as $lineitem)
{
$secondary_columns = array(
'names_and_values' => array(),
'formats' => array()
);
foreach($tables_response[$action]['secondary_columns'] as $secondary_column_name => $secondary_format)
{
if (isset($lineitem[$secondary_column_name]))
{
$secondary_columns['names_and_values'][$secondary_column_name] = $lineitem[$secondary_column_name];
$secondary_columns['formats'][] = $secondary_format;
}
}
// Additional columns
if (!empty($extra['additional_secondary_columns']))
{
$founds = array_keys($extra['additional_secondary_columns']['items'], $lineitem['ItemRef_FullName']);
if (!empty($founds))
{
foreach($founds as $key)
{
foreach($extra['additional_secondary_columns']['columns'] as $db_column_name => $col_data)
{
// start with empty array...
$dsata = array();
$dsata[$db_column_name] = $col_data['values'][$key];
$secondary_columns['names_and_values'] = array_merge($seconday_columns['names_and_values'], $dsata);
$secondary_columns['formats'][] = $col_data['format'];
}
}
}
}
$secondary_columns['names_and_values'] = array_merge(array('EstimateTxnID' => $estimate_info['lineitems']['EstimateTxnID']), $secondary_columns['names_and_values']);
$secondary_columns['formats'] = array_merge(array('%s'), $secondary_columns['formats']);
$wpdb->insert(
$tables_response[$action]['secondary_table'],
$secondary_columns['names_and_values'],
$secondary_columns['formats']
);
}
}
}
}
}
当我file_put_contents
在请求函数中看到$xml
的结果时,我得到的是我之前发布的xml,这没有错。但是 Quickbooks 正在返回解析错误并且仅在添加 Estimate 时发生! Items、Vendors 和 Customer 都在 Quickbooks 中正确创建,只是添加 Estimate 是这里的问题。
任何时候你看到这个:
QuickBooks found an error when parsing the provided XML text stream.
您应该做的第一件事是从日志中获取 qbXML
,然后 运行 XML Validator
工具包含在 QuickBooks SDK
中。
XML Validator
工具会告诉您 运行 qbXML
到底出了什么问题。
查看 quickbooks_log
SQL table 并找到发送到 Web 连接器的实际 qbXML。或者,将 Web 连接器设置为 VERBOSE
模式并从 Web 连接器日志中获取 qbXML 请求。不要使用您自己的日志记录。这是不准确的。该框架插入了一个用于跟踪的 requestID
属性,因此您放置的任何日志记录都不会是实际发送的请求。
从日志中取出 qbXML,并通过 XML Validator 工具将其放入。它会告诉您行号和错误消息。
如果您 运行 通过 QuickBooks SDK 附带的 XML Validator
工具,您将获得:
Line: 14
LinePos: 23
Src Text: <Province>PA</Province>
Reason: Element content is invalid according to the DTD/Schema.
Expecting: State, PostalCode, Country, Note.
如果您查看 QuickBooks OSR
,您会注意到 OSR 中没有显示 <Province>
标签。
验证器告诉您 Province
标签无效,它期望您改为指定以下标签之一:State, PostalCode, Country, Note
。