使用QuickBooks PHP Dev Kit / QuickBooks Web Connector编辑QuickBooks for Windows库存数量

时间:2016-12-22 16:26:40

标签: php mysql sql quickbooks qbxml

摘要

所以我设置了example_mysql_mirror并使其全部正常工作但由于某种原因它无论qbsql_modify_timestamp如何都会覆盖MySQL中的更改。我注意到镜像维基页面http://www.consolibyte.com/wiki/doku.php/quickbooks_integration_php_consolibyte_sqlmirror引用了一个不同的字段(qbsql_modify_datetime),但只是将其归结为维基中从未更改过的内部更改。我正在为任何/所有行更新表“qb_iteminventory”和密钥“QuantityOnHand”。

我的最终目标是能够更新商品的库存数量。镜像示例只是第一个工作示例,它不仅可行而且容易完成(可能与创建它的原因相同)。

在进一步挖掘后,我得出了一些结论,一个是QuickBooks PHP Dev Kit example_mysql_mirror.php未完成。我确实看到了警告,我正在使用最新的GitHub代码。

我觉得这是时间问题(内部qbsql_modify_timestamp处理)或父问题(qb_iteminventory是父项的子项/子项,也需要更新等),甚至缺少字段(我注意到了) QuantityOnHand但在MySQL中没有看到QuantityAvailable。可能需要为QB库存交换使用不同的报告(不确定是否需要这样做?)。

CODE

example_mysql_mirror.php

// I always program in E_STRICT error mode with error reporting turned on... 
error_reporting(E_ALL | E_STRICT);
ini_set('display_errors', 1);
// Set the include path
require_once dirname(__FILE__) . '/../../QuickBooks.php';
// You should make sure this matches the time-zone QuickBooks is running in
if (function_exists('date_default_timezone_set'))
{
    date_default_timezone_set('America/Los_Angeles');
}
// The username and password the Web Connector will use to connect with
$username = '';
$password = '';
// Database connection string
//
// You *MUST* start with a fresh database! If the database you use has any 
//  quickbooks_* or qb_* related tables in it, then the schema *WILL NOT* build 
//  correctly! 
//  
//  Currently, only MySQL is supported/tested. 
$dsn = 'mysqli://:@localhost/spray_quickbooks';
// If the database has not been initialized, we need to initialize it (create 
//  schema and set up the username/password, etc.)
if (!QuickBooks_Utilities::initialized($dsn))
{
    header('Content-Type: text/plain');

    // It takes a really long time to build the schema... 
    set_time_limit(0);

    $driver_options = array(
        );

    $init_options = array(
        'quickbooks_sql_enabled' => true, 
        );

    QuickBooks_Utilities::initialize($dsn, $driver_options, $init_options);
    QuickBooks_Utilities::createUser($dsn, $username, $password);

    exit;
}
// What mode do we want to run the mirror in? 
//$mode = QuickBooks_WebConnector_Server_SQL::MODE_READONLY;        // Read from QuickBooks only (no data will be pushed back to QuickBooks)
//$mode = QuickBooks_WebConnector_Server_SQL::MODE_WRITEONLY;       // Write to QuickBooks only (no data will be copied into the SQL database)
$mode = QuickBooks_WebConnector_Server_SQL::MODE_READWRITE;     // Keep both QuickBooks and the database in sync, reading and writing changes back and forth)
// What should we do if a conflict is found? (a record has been changed by another user or process that we're trying to update)
$conflicts = QuickBooks_WebConnector_Server_SQL::CONFLICT_LOG;
// What should we do with records deleted from QuickBooks? 
//$delete = QuickBooks_WebConnector_Server_SQL::DELETE_REMOVE;      // Delete the record from the database too
$delete = QuickBooks_WebConnector_Server_SQL::DELETE_FLAG;      // Just flag it as deleted
// Hooks (optional stuff)
$hooks = array();
/*
// Hooks (optional stuff)
$hook_obj = new MyHookClass2('Keith Palmer');
$hooks = array(
    // Register a hook which occurs when we perform an INSERT into the SQL database for a record from QuickBooks
    // QuickBooks_SQL::HOOK_SQL_INSERT => 'my_function_name_for_inserts', 
    // QuickBooks_SQL::HOOK_SQL_INSERT => 'MyHookClass::myMethod',

    // Register a hook which occurs when we perform an UPDATE on the SQL database for a record from QuickBooks
    // QuickBooks_SQL::HOOK_SQL_UPDATE => 'my_function_name_for_updates',
    // Example of registering multiple hooks for one hook type 
    // QuickBooks_SQL::HOOK_PREHANDLE => array(
    //  'my_prehandle_function',
    //  array( $hook_obj, 'myMethod' ),
    //  ),

    // Example of using the hook factory to use a pre-defined hook
    // QuickBooks_SQL::HOOK_SQL_INSERT => QuickBooks_Hook_Factory::create(
    //  'Relay_POST',                               // Relay the hook data to a remote URL via a HTTP POST
    //  'http://localhost:8888/your_script.php'),

    QuickBooks_SQL::SQL_INSERT => array(
        QuickBooks_Hook_Factory::create(
            'Relay_POST', 
            'http://localhost:8888/your_script.php', 
            array( '_secret' => 'J03lsN3at@pplication' ) ), 
        ), 
    );
class MyHookClass
{
    static public function myMethod($requestID, $user, $hook, &$err, $hook_data, $callback_config)
    {
        // do something here...
        return true;
    }
}
function my_prehandle_function($requestID, $user, $hook, &$err, $hook_data, $callback_config)
{
    //print('here we are!');
    return true;
}
class MyHookClass2
{
    protected $_var;

    public function __construct($var)
    {
        $this->_var = $var;
    }

    public function myMethod($requestID, $user, $hook, &$err, $hook_data, $callback_config)
    {
        //print('variable equals: ' . $this->_var);
        return true;
    }
}
*/
// 
$soap_options = array();
// 
$handler_options = array(
    'deny_concurrent_logins' => false,
    'deny_reallyfast_logins' => false, 
    );
// 
$driver_options = array();
$ops = array(
    QUICKBOOKS_OBJECT_SALESTAXITEM, 
    QUICKBOOKS_OBJECT_SALESTAXCODE, 
    QUICKBOOKS_OBJECT_CUSTOMER, 
    QUICKBOOKS_OBJECT_VENDOR, 

    QUICKBOOKS_OBJECT_TEMPLATE, 

    QUICKBOOKS_OBJECT_CUSTOMERTYPE, 
    QUICKBOOKS_OBJECT_VENDORTYPE, 
    QUICKBOOKS_OBJECT_ESTIMATE, 
    QUICKBOOKS_OBJECT_INVOICE, 
    QUICKBOOKS_OBJECT_CLASS, 

    QUICKBOOKS_OBJECT_INVOICE, 

    QUICKBOOKS_OBJECT_INVENTORYITEM,

    /* Not quite sure why these are not being used
    QUICKBOOKS_OBJECT_NONINVENTORYITEM, 
    QUICKBOOKS_OBJECT_SERVICEITEM, 
    QUICKBOOKS_OBJECT_SHIPMETHOD, 
    QUICKBOOKS_OBJECT_PAYMENTMETHOD, 
    QUICKBOOKS_OBJECT_TERMS, 
    QUICKBOOKS_OBJECT_PRICELEVEL, 
    QUICKBOOKS_OBJECT_ITEM,
    */ 

    QUICKBOOKS_OBJECT_PAYMENTMETHOD, 

    QUICKBOOKS_OBJECT_COMPANY, 
    QUICKBOOKS_OBJECT_HOST, 
    QUICKBOOKS_OBJECT_PREFERENCES,
    );
$ops_misc = array(      // For fetching inventory levels, deleted transactions, etc. 
    QUICKBOOKS_DERIVE_INVENTORYLEVELS,
    QUICKBOOKS_QUERY_DELETEDLISTS,
    QUICKBOOKS_QUERY_DELETEDTRANSACTIONS,
    // 'nothing', 
    );
// 
$sql_options = array(
    'only_import' => $ops,
    'only_add' => $ops, 
    'only_modify' => $ops, 
    'only_misc' => $ops_misc, 
    );
// 
$callback_options = array();
// $dsn_or_conn, $how_often, $mode, $conflicts, $users = null, 
//  $map = array(), $onerror = array(), $hooks = array(), $log_level, $soap = QUICKBOOKS_SOAPSERVER_BUILTIN, $wsdl = QUICKBOOKS_WSDL, $soap_options = array(), $handler_options = array(), $driver_options = array()
$Server = new QuickBooks_WebConnector_Server_SQL(
    $dsn, 
    '1 minute', 
    $mode, 
    $conflicts, 
    $delete,
    $username, 
    array(), 
    array(), 
    $hooks, 
    QUICKBOOKS_LOG_DEVELOP, 
    QUICKBOOKS_SOAPSERVER_BUILTIN, 
    QUICKBOOKS_WSDL,
    $soap_options, 
    $handler_options, 
    $driver_options,
    $sql_options, 
    $callback_options);
$Server->handle(true, true);

save.php

<?php
if (count($_POST) == 0) {
    dErr("There is nothing to save. Try again later.");
}

// Compile list of rows to update
$updates = array();
foreach ($_POST as $key => $value) {
    array_push($updates, array(explode('_', $key)[1], $value));
}
unset($key);
unset($value);

// TODO: Database updating
// IMPORTANT: Add validation and other selective functionality before adding saving
$qb = new mysqli('127.0.0.1', '', '', 'spray_quickbooks');

// Oh no! A connect_errno exists so the connection attempt failed!
if ($qb->connect_errno) {
    dErr("Error: Failed to make a MySQL connection, here is why: <br />Errno: " . $qb->connect_errno . "<br />Error: " . $qb->connect_error);
}

$qbe = new mysqli('127.0.0.1', '', '', 'spray_qb_extras');

// Oh no! A connect_errno exists so the connection attempt failed!
if ($qbe->connect_errno) {
    dErr("Error: Failed to make a MySQL connection, here is why: <br />Errno: " . $qbe->connect_errno . "<br />Error: " . $qbe->connect_error);
}

foreach ($updates as $update) {
    // Perform an SQL query
    $sql = "UPDATE qb_iteminventory SET QuantityOnHand='" . $update[1] . "' WHERE qbsql_id='" . $update[0] . "'";
    if (!$qb_result = $qb->query($sql)) {
        dErr("Error: Our query failed to execute and here is why: <br />Query: " . $sql . "<br />Errno: " . $qb->errno . "<br />Error: " . $qb->error);
    }
}
unset($updates);
unset($update);

// Redirect back to where they came from
echo "<meta http-equiv=\"refresh\" content=\"0;url=".$_SERVER['HTTP_REFERER']."\"/>";

// Extra Functions
function dErr($msg) {
    echo "<center><b>Sorry, we have encountered an error.</b><br /><br />";
    echo $msg;
    echo "</center>";
    exit;
}
?>

更新了save.php (广告资源调整)

<?php
if (count($_POST) == 0) {
    dErr("There is nothing to save. Please try again later.");
}

// Compile list of rows to update
$updates = array();
foreach ($_POST as $key => $value) {
    array_push($updates, array(explode('_', $key)[1], $value));
}
/*unset($key);
unset($value);*/

// IMPORTANT: Add validation and other selective functionality before adding saving
//            and stop saving unchanged items just because we can
$qb = new mysqli('127.0.0.1', '', '', 'spray_quickbooks');

// Oh no! A connect_errno exists so the connection attempt failed!
if ($qb->connect_errno) {
    dErr("Error: Failed to make a MySQL connection, here is why: <br />Errno: " . $qb->connect_errno . "<br />Error: " . $qb->connect_error);
}

// IMPORTANT: ONLY UPDATE CHANGED ROWS. WE DONT WANT INVENTORY ADJUSTMENTS FOR UNCHANGED ITEMS!
foreach ($updates as $update) {
    // Update QuantityOnHand still so our web interface can easily see the new quantity before QB sync
    $sql = "UPDATE qb_iteminventory SET QuantityOnHand='" . $update[1] . "' WHERE qbsql_id='" . $update[0] . "'";
    if (!$qb_result = $qb->query($sql)) {
        dErr("Error: Our query failed to execute and here is why: <br />Query: " . $sql . "<br />Errno: " . $qb->errno . "<br />Error: " . $qb->error);
    }

    // Get a newly updated item so we can extract Item's FullName
     $sql = "SELECT * FROM qb_iteminventory WHERE qbsql_id='" . $update[0] . "'";
    if (!$qb_result = $qb->query($sql)) {
        dErr("Error: Our query failed to execute and here is why: <br />Query: " . $sql . "<br />Errno: " . $qbe->errno . "<br />Error: " . $qbe->error);
    }

    $row = mysqli_fetch_assoc($qb_result);
    //print_r($row);

    // Generate unique TxnID
    // Apparently QuickBooks will overwrite it with the permanent TxnID when it syncs
    $tID = rand(1000, 9999);

    // Insert new Item Adjustment
    $sql = "INSERT INTO `qb_inventoryadjustment` ( `TxnID`, `TimeCreated`, `TimeModified`,  `Account_FullName`, `TxnDate`, `RefNumber`,  `Memo`, `qbsql_discov_datetime`, `qbsql_resync_datetime`, `qbsql_modify_timestamp` ) VALUES ( 'TxnID-" . $tID . "', now(), now(),  'Inventory Adjustments', CURDATE(), '" . $tID . "', NULL, NULL, NULL, now() )";
    if (!$qb_result = $qb->query($sql)) {
        dErr("Error: Our query failed to execute and here is why: <br />Query: " . $sql . "<br />Errno: " . $qb->errno . "<br />Error: " . $qb->error);
    }

    // Insert new Item Adjustment Line
    $sql = "INSERT INTO `qb_inventoryadjustment_inventoryadjustmentline` ( `InventoryAdjustment_TxnID`, `SortOrder`, `TxnLineID`, `Item_FullName`, `QuantityAdjustment_NewQuantity` ) VALUES ( 'TxnID-" . $tID . "', '0', 'TxnLID-" . $tID . "', '" . $row['FullName'] . "', " . $update[1] . ");";
    if (!$qb_result = $qb->query($sql)) {
        dErr("Error: Our query failed to execute and here is why: <br />Query: " . $sql . "<br />Errno: " . $qb->errno . "<br />Error: " . $qb->error);
    }
}

// TODO: Research whether this is really required, and to what extent
// INFO: Not sure why I feel like this is important
/*$qb_result->free();
$qb->close();
unset($updates);
unset($update);
unset($sql);*/

// Redirect back to where they came from
//echo "<meta http-equiv=\"refresh\" content=\"0;url=".$_SERVER['HTTP_REFERER']."\"/>";

// Extra Functions
function dErr($msg) {
    echo "<center><b>Sorry, we have encountered an error.</b><br /><br />";
    echo $msg;
    echo "</center>";
    exit;
}
?>

1 个答案:

答案 0 :(得分:1)

库存调整的SQL镜像:

首先,通常的免责声明 - SQL镜像的东西是测试版,所以不要指望100%的功能(如发行说明中所示)。随着说......

QuickBooks不允许您通过编辑库存项目直接更新数量。如果你进入QuickBooks用户界面,你也会看到同样的行为。

所以这样的将不起作用

$sql = "UPDATE qb_iteminventory SET QuantityOnHand='" . $update[1] . "' WHERE qbsql_id='" . $update[0] . "'";

上述查询将告诉QuickBooks只是替换现有的给定数量,这实际上在“会计世界”中不起作用,因为库存变化具有税务影响(企业对其承载的库存量征税),收入影响(库存变化通常意味着您要么从制造商那里购买更多物品,要么向顾客出售物品),以及物理影响(数量或某些变化,这意味着物理产品流向客户等)会计师/企业需要密切关注并且审核日志显示 以及为什么数量发生了变化。

相反,您在QuickBooks中更改库存的方式是通过单独的事务。例如:

  • Invoice会减少手头的数量(你卖东西)
  • Item Receipt会增加手头的数量(您从制造商或供应商处购买的东西)
  • 如果只是需要进行手动调整(例如仓库中的某人错误地破坏了产品,或者您因盗窃而丢失了库存等),那么您使用Inventory Adjustment

QuickBooks SDK(以及镜像代码)遵循此约定 - 如果要更改数量,则需要创建事务

您可能想要创建Inventory Adjustmentqb_inventoryadjustment SQL表)。像这样:

INSERT INTO `qb_inventoryadjustment` ( `TxnID`, `TimeCreated`, `TimeModified`,  `Account_FullName`, `TxnDate`, `RefNumber`,  `Memo`, `qbsql_discov_datetime`, `qbsql_resync_datetime`, `qbsql_modify_timestamp` )
VALUES ( 'TxnID-1234', now(), now(),  'Inventory Adjustments', '2016-12-23', '1234', 'Test adjustment', NULL, NULL, now() );

INSERT INTO `qb_inventoryadjustment_inventoryadjustmentline` ( `InventoryAdjustment_TxnID`, `SortOrder`, `TxnLineID`, `NewQuantity` )
VALUES ( 'TxnID-1234', '0', 'ABCD-1234', '10' );

如果您查看QuickBooks用户界面,您会看到Inventory Adjustment个交易同时具有基本详细信息(参考编号,日期等)和行项目级别详细信息(项目和数量),因此您有确保同时提供(基本详细信息的qb_inventoryadjustment表和行的qb_inventoryadjustment_inventoryadjustmentline表)

确保您的$ops数组中已启用Inventory Adjustments

$ops = array(
   ... 
   QUICKBOOKS_OBJECT_INVENTORYADJUSTMENT
   ...
   );

QUICKBOOKS_OBJECT_INVENTORYITEM仅同步实际产品本身不会同步产品的实际数量变化。

如果您发现SQL Mirror因任何原因无法正常运行......

SQL镜像的东西是一个实验,它并不总是正常工作。嘘。 :-(

但是,如果你不介意编写一些代码,那就有一个很好的选择(嘿,你在StackOverflow上,所以你没有!)。

相反,请遵循GitHub项目链接的快速入门:

基本上,您最终会将Web连接器指向以下内容:

// Require the framework
require_once '../../QuickBooks.php';

// A username and password you'll use in: 
//  a) Your .QWC file
//  b) The Web Connector
//  c) The QuickBooks framework
$user = 'quickbooks';
$pass = 'password';

// Map QuickBooks actions to handler functions
$map = array(
    // ...
    );

// This is entirely optional, use it to trigger actions when an error is returned by QuickBooks
$errmap = array();

// An array of callback hooks
$hooks = array();

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

// * MAKE SURE YOU CHANGE THE DATABASE CONNECTION STRING BELOW TO A VALID MYSQL USERNAME/PASSWORD/HOSTNAME *
$dsn = 'mysql://root:root@localhost/quickbooks_server';

if (!QuickBooks_Utilities::initialized($dsn))
{
    // Initialize creates the neccessary database schema for queueing up requests and logging
    QuickBooks_Utilities::initialize($dsn);

    // This creates a username and password which is used by the Web Connector to authenticate
    QuickBooks_Utilities::createUser($dsn, $user, $pass);
}

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

然后,您可以通过以下方式轻松调整发送InventoryAdjustmentAdd交易:

  • QUICKBOOKS_ADD_INVENTORYADJUSTMENT => array( '_quickbooks_inventoryadjustment_add_request', '_quickbooks_inventoryadjustment_add_response' )添加到$map
  • 复制以下功能并调整以满足您的需求:

-

/**
 * Generate a qbXML response to add a particular customer to QuickBooks
 * 
 * @return string                           A valid qbXML request
 */
function _quickbooks_inventoryadjustment_add_request($requestID, $user, $action, $ID, $extra, &$err, $last_action_time, $last_actionident_time, $version, $locale, $callback_config)
{
    $Driver = QuickBooks_Driver_Singleton::getInstance();

    $errnum = null;
    $errmsg = null;
    $data = $Driver->fetch($Driver->query("SELECT * FROM qb_inventoryadjustment WHERE qbsql_id = %d", $errnum, $errmsg, 0, 1, array( $ID )));

    $res_lines = $Driver->query("SELECT * FROM qb_inventoryadjustment_inventoryadjustmentline WHERE InventoryAdjustment_TxnID = '%s' ORDER BY SortOrder ASC", $errnum, $errmsg, null, null, array( $data['TxnID'] ));

    foreach ($data as $key => $value)
    {
        //$data[$key] = QuickBooks_Cast::cast(QUICKBOOKS_OBJECT_CUSTOMER, str_replace('_', ' ', $key), $value);
    }

    $str_action = 'InventoryAdjustmentAdd';
    $TxnID = '';
    $EditSequence = '';
    if ($action == 'InventoryAdjustmentMod')
    {
        $str_action = 'InventoryAdjustmentMod';
        $TxnID = '<TxnID>' . $data['TxnID'] . '</TxnID>';
        $EditSequence = '<EditSequence>' . $data['EditSequence'] . '</EditSequence>';
    }

    $xml = '<?xml version="1.0" encoding="utf-8"?>
        <?qbxml version="' . $version . '"?>
        <QBXML>
            <QBXMLMsgsRq onError="stopOnError">
                <' . $str_action . 'Rq requestID="' . $requestID . '">
                    <' . $str_action . '>
                        ' . $TxnID . '
                        ' . $EditSequence . '

                        <AccountRef>
                            <FullName>' . $data['Account_FullName'] . '</FullName>
                        </AccountRef>

                        <TxnDate>' . $data['TxnDate'] . '</TxnDate>
                        <!--<RefNumber>' . $data['RefNumber'] . '</RefNumber>-->

                        <Memo>' . $data['Memo'] . '</Memo>
                        ';

    while ($line = $Driver->fetch($res_lines))
    {
        $xml .= '
                        <InventoryAdjustmentLineAdd>
                            <ItemRef>';

        if ($line['Item_ListID'])
        {
            $xml .= '
                                <ListID>' . $line['Item_ListID'] . '</ListID>';
        }
        else
        {
            $xml .= '
                                <FullName>' . $line['Item_FullName'] . '</FullName>';
        }

        $xml .= '
                            </ItemRef>

                            <QuantityAdjustment>';

        if ($line['QuantityDifference'])
        {
            $xml .= '
                                <QuantityDifference>' . $line['QuantityDifference'] . '</QuantityDifference>';
        }
        else
        {
            $xml .= '
                                <NewQuantity>' . $line['NewQuantity'] . '</NewQuantity>';
        }

        $xml .= '
                            </QuantityAdjustment>
                        </InventoryAdjustmentLineAdd>
                ';
    }

    $xml .= '

                    </' . $str_action . '>
                </' . $str_action . 'Rq>
            </QBXMLMsgsRq>
        </QBXML>';

    return $xml;
}

/**
 * Receive a response from QuickBooks 
 */
function _quickbooks_inventoryadjustment_add_response($requestID, $user, $action, $ID, $extra, &$err, $last_action_time, $last_actionident_time, $xml, $idents, $callback_config)
{   
    $Driver = QuickBooks_Driver_Singleton::getInstance();

    $datetime = date('Y-m-d H:i:s');

    $errnum = null;
    $errmsg = null;
    $data = $Driver->fetch($Driver->query("SELECT * FROM qb_inventoryadjustment WHERE qbsql_id = %d", $errnum, $errmsg, 0, 1, array( $ID )));

    if ($data)
    {
        // Get the existing lines 
        $res_lines = $Driver->query("SELECT * FROM qb_inventoryadjustment_inventoryadjustmentline WHERE InventoryAdjustment_TxnID = '%s' ORDER BY qbsql_id ASC ", $errnum, $errmsg, null, null, array( $data['TxnID'] ));

        // Update ListID/EditSequence
        $errnum = null;
        $errmsg = null;
        $Driver->query("
            UPDATE 
                qb_inventoryadjustment
            SET 
                TxnID = '%s', 
                EditSequence = '%s', 
                TimeCreated = '%s', 
                TimeModified = '%s', 
                RefNumber = '%s', 
                qbsql_discov_datetime = '%s', 
                qbsql_resync_datetime = '%s', 
                qbsql_modify_timestamp = '%s'
            WHERE 
                qbsql_id = %d ", $errnum, $errmsg, 0, 1, 
            array( 
                $idents['TxnID'], 
                $idents['EditSequence'], 
                date('Y-m-d H:i:s'), 
                date('Y-m-d H:i:s'), 
                $idents['RefNumber'],
                $datetime, 
                $datetime, 
                $datetime,
                $ID ));

        // Parse the XML we got back 
        // 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/InventoryAdjustmentAddRs');

            $TxnLineIDs = array();

            foreach ($List->children() as $InventoryAdjustment)
            {
                // Process the line items
                foreach ($InventoryAdjustment->children() as $Child)
                {
                    if ($Child->name() == 'InventoryAdjustmentLineRet')
                    {
                        // Store the TxnLineID
                        $TxnLineIDs[] = $Child->getChildDataAt('InventoryAdjustmentLineRet TxnLineID');
                    }
                }
            }

            reset($TxnLineIDs);
            while ($line = $Driver->fetch($res_lines))
            {
                $TxnLineID = current($TxnLineIDs);
                next($TxnLineIDs);

                // Update each line item with the TxnID and the TxnLineID 
                $Driver->query("
                    UPDATE 
                        qb_inventoryadjustment_inventoryadjustmentline
                    SET 
                        InventoryAdjustment_TxnID = '%s', 
                        TxnLineID = '%s', 
                        qbsql_discov_datetime = '%s', 
                        qbsql_resync_datetime = '%s', 
                        qbsql_modify_timestamp = '%s'
                    WHERE 
                        qbsql_id = %d ", $errnum, $errmsg, null, null,  
                    array( 
                        $idents['TxnID'], 
                        $TxnLineID, 
                        $datetime, 
                        $datetime, 
                        $datetime,
                        $line['qbsql_id'] ));
            }
        }

    }
}

这些函数^^^基本上与SQL镜像尝试做的事情相同,但是它们比SQL镜像更加可控和可调整,因为你可以完全控制那里的qbXML。

请记住,如果你走这条路线,只要你想要触发它发送给QuickBooks,就需要排队请求:

$Queue->enqueue(QUICKBOOKS_ADD_INVENTORYADJUSTMENT, $ID);

<强>最后:

高度建议您尽可能使用QuickBooks UI熟悉自己。 SDK请求与UI非常匹配,因此了解如何在UI中执行操作非常有用。