/** * Guess the appropriate priority for an action * * @param string $action The action to guess for * @param string $dependency A dependency (i.e. a DataExtAdd that depends on a CustomerAdd) * @return integer The best guess at the proper priority */ public static function priority($action, $dependency = null) { return QuickBooks_Utilities::priorityForAction($action, $dependency); }
/** * * */ protected static function _triggerActions($user, $table, $Object, $action = null) { $Driver = QuickBooks_Driver_Singleton::getInstance(); // Be *CAREFUL* here, you don't want to trigger an infinite loop of // high-priority Query requests! i.e.: // ReceivePayment_AppliedToTxn requests an InvoiceQuery // Invoice_LinkedTxn requests a ReceivePaymentQuery // ReceivePayment_AppliedToTxn requests an InvoiceQuery // ... wash rinse repeat $priority = 9999; if ($action) { $priority = QuickBooks_Utilities::priorityForAction($action) - 1; } // Account. Balance, TotalBalance // Bill. IsPaid, OpenAmount, AmountDue // Charge. BalanceRemaining // CreditMemo. IsPending, CreditRemaining // Customer. Balance, TotalBalance, // Invoice. IsPending, AppliedAmount, BalanceRemaining, IsPaid //$Driver->log('Running triggered actions for: [' . $table . ']', null, QUICKBOOKS_LOG_DEBUG); switch (strtolower($table)) { case 'receivepayment_appliedtotxn': // Fetch the linked invoice $where = array('TxnID' => $Object->get('ToTxnID')); // @todo WARNING WARNING WARNING THIS DOES NOT WORK, I DONT KNOW WHY! /* if ($Object->get('TxnType') == 'Invoice' and $arr = $Driver->get(QUICKBOOKS_DRIVER_SQL_PREFIX_SQL . 'invoice', $where)) { $Driver->log('Running triggered actions: derive invoice, derive customer', null, QUICKBOOKS_LOG_DEBUG); // Fetch the derived fields from the invoice, because the invoice needs it's balance updated $Driver->queueEnqueue($user, QUICKBOOKS_DERIVE_INVOICE, null, true, $priority, array( 'TxnID' => $arr['TxnID'] ) ); // Fetch the derived fields from the customer, balance updated $Driver->queueEnqueue($user, QUICKBOOKS_DERIVE_CUSTOMER, null, true, $priority, array( 'ListID' => $arr['Customer_ListID'] ) ); } */ break; case 'receivepayment': case 'invoice': // A customer has an updated invoice or payment, so the Customer Balance changed /* $Driver->log('Running triggered actions: derive customer', null, QUICKBOOKS_LOG_DEBUG); $Driver->queueEnqueue($user, QUICKBOOKS_DERIVE_CUSTOMER, null, true, $priority, array( 'ListID' => $Object->get('Customer_ListID') ) ); */ break; case 'bill': case 'billpaymentcheck': case 'billpaymentcreditcard': // We paid a bill, so the Vendor Balance has changed break; } }
/** * * * */ public static function ItemInventoryAddResponse($requestID, $user, $action, $ID, $extra, &$err, $last_action_time, $last_actionident_time, $xml, $idents, $config = array()) { $Driver = QuickBooks_Driver_Singleton::getInstance(); $Parser = new QuickBooks_XML_Parser($xml); $errnum = 0; $errmsg = ''; $Doc = $Parser->parse($errnum, $errmsg); $Root = $Doc->getRoot(); $List = $Root->getChildAt('QBXML QBXMLMsgsRs ItemInventoryAddRs'); $extra['IsAddResponse'] = true; QuickBooks_Server_SQL_Callbacks::_QueryResponse(QUICKBOOKS_OBJECT_INVENTORYITEM, $List, $requestID, $user, $action, $ID, $extra, $err, $last_action_time, $last_actionident_time, $xml, $idents, $config); $Driver->queueEnqueue($user, QUICKBOOKS_QUERY_INVENTORYADJUSTMENT, md5(__FILE__), true, QuickBooks_Utilities::priorityForAction(QUICKBOOKS_QUERY_INVENTORYADJUSTMENT)); }
/** * Try to guess the queueing priority for this action * * @param string $action The action you're trying to guess for * @param string $dependency If the action depends on another action (i.e. a DataExtAdd for a CustomerAdd) you can pass the dependency here * @return integer A best guess at the proper priority */ public static function priorityForAction($action, $dependency = null) { // low priorities up here (*lots* of dependencies) static $priorities = array(QUICKBOOKS_DELETE_TRANSACTION, QUICKBOOKS_VOID_TRANSACTION, QUICKBOOKS_DEL_DATAEXT, QUICKBOOKS_MOD_DATAEXT, QUICKBOOKS_ADD_DATAEXT, QUICKBOOKS_MOD_JOURNALENTRY, QUICKBOOKS_ADD_JOURNALENTRY, QUICKBOOKS_MOD_RECEIVEPAYMENT, QUICKBOOKS_ADD_RECEIVEPAYMENT, QUICKBOOKS_MOD_BILLPAYMENTCHECK, QUICKBOOKS_ADD_BILLPAYMENTCHECK, QUICKBOOKS_ADD_BILLPAYMENTCREDITCARD, QUICKBOOKS_MOD_BILL, QUICKBOOKS_ADD_BILL, QUICKBOOKS_MOD_PURCHASEORDER, QUICKBOOKS_ADD_PURCHASEORDER, QUICKBOOKS_MOD_INVOICE, QUICKBOOKS_ADD_INVOICE, QUICKBOOKS_MOD_SALESORDER, QUICKBOOKS_ADD_SALESORDER, QUICKBOOKS_MOD_ESTIMATE, QUICKBOOKS_ADD_ESTIMATE, QUICKBOOKS_ADD_INVENTORYADJUSTMENT, QUICKBOOKS_ADD_CREDITMEMO, QUICKBOOKS_MOD_CREDITMEMO, QUICKBOOKS_ADD_ITEMRECEIPT, QUICKBOOKS_MOD_ITEMRECEIPT, QUICKBOOKS_MOD_SALESRECEIPT, QUICKBOOKS_ADD_SALESRECEIPT, QUICKBOOKS_ADD_SALESTAXITEM, QUICKBOOKS_MOD_SALESTAXITEM, QUICKBOOKS_ADD_DISCOUNTITEM, QUICKBOOKS_MOD_DISCOUNTITEM, QUICKBOOKS_ADD_OTHERCHARGEITEM, QUICKBOOKS_MOD_OTHERCHARGEITEM, QUICKBOOKS_MOD_NONINVENTORYITEM, QUICKBOOKS_ADD_NONINVENTORYITEM, QUICKBOOKS_MOD_INVENTORYITEM, QUICKBOOKS_ADD_INVENTORYITEM, QUICKBOOKS_MOD_INVENTORYASSEMBLYITEM, QUICKBOOKS_ADD_INVENTORYASSEMBLYITEM, QUICKBOOKS_MOD_SERVICEITEM, QUICKBOOKS_ADD_SERVICEITEM, QUICKBOOKS_MOD_PAYMENTITEM, QUICKBOOKS_ADD_PAYMENTITEM, QUICKBOOKS_MOD_SALESREP, QUICKBOOKS_ADD_SALESREP, QUICKBOOKS_MOD_EMPLOYEE, QUICKBOOKS_ADD_EMPLOYEE, QUICKBOOKS_ADD_SALESTAXCODE, QUICKBOOKS_MOD_VENDOR, QUICKBOOKS_ADD_VENDOR, QUICKBOOKS_MOD_CUSTOMER, QUICKBOOKS_ADD_CUSTOMER, QUICKBOOKS_MOD_ACCOUNT, QUICKBOOKS_ADD_ACCOUNT, QUICKBOOKS_ADD_CLASS, QUICKBOOKS_ADD_PAYMENTMETHOD, QUICKBOOKS_ADD_SHIPMETHOD, QUICKBOOKS_QUERY_PURCHASEORDER, QUICKBOOKS_QUERY_ITEMRECEIPT, QUICKBOOKS_QUERY_SALESORDER, QUICKBOOKS_QUERY_SALESRECEIPT, QUICKBOOKS_QUERY_INVOICE, QUICKBOOKS_QUERY_ESTIMATE, QUICKBOOKS_QUERY_RECEIVEPAYMENT, QUICKBOOKS_QUERY_CREDITMEMO, QUICKBOOKS_QUERY_BILLPAYMENTCHECK, QUICKBOOKS_QUERY_BILLPAYMENTCREDITCARD, QUICKBOOKS_QUERY_BILLTOPAY, QUICKBOOKS_QUERY_BILL, QUICKBOOKS_QUERY_CREDITCARDCHARGE, QUICKBOOKS_QUERY_CREDITCARDCREDIT, QUICKBOOKS_QUERY_CHECK, QUICKBOOKS_QUERY_CHARGE, QUICKBOOKS_QUERY_DELETEDLISTS, QUICKBOOKS_QUERY_DELETEDTXNS, QUICKBOOKS_QUERY_TIMETRACKING, QUICKBOOKS_QUERY_VENDORCREDIT, QUICKBOOKS_QUERY_INVENTORYADJUSTMENT, QUICKBOOKS_QUERY_ITEM, QUICKBOOKS_QUERY_DISCOUNTITEM, QUICKBOOKS_QUERY_SALESTAXITEM, QUICKBOOKS_QUERY_SERVICEITEM, QUICKBOOKS_QUERY_NONINVENTORYITEM, QUICKBOOKS_QUERY_INVENTORYITEM, QUICKBOOKS_QUERY_SALESREP, QUICKBOOKS_QUERY_VEHICLEMILEAGE, QUICKBOOKS_QUERY_VEHICLE, QUICKBOOKS_QUERY_CUSTOMER, QUICKBOOKS_QUERY_VENDOR, QUICKBOOKS_QUERY_EMPLOYEE, QUICKBOOKS_QUERY_WORKERSCOMPCODE, QUICKBOOKS_QUERY_UNITOFMEASURESET, QUICKBOOKS_QUERY_JOURNALENTRY, QUICKBOOKS_QUERY_DEPOSIT, QUICKBOOKS_QUERY_SHIPMETHOD, QUICKBOOKS_QUERY_PAYMENTMETHOD, QUICKBOOKS_QUERY_PRICELEVEL, QUICKBOOKS_QUERY_DATEDRIVENTERMS, QUICKBOOKS_QUERY_BILLINGRATE, QUICKBOOKS_QUERY_CUSTOMERTYPE, QUICKBOOKS_QUERY_CUSTOMERMSG, QUICKBOOKS_QUERY_TERMS, QUICKBOOKS_QUERY_SALESTAXCODE, QUICKBOOKS_QUERY_ACCOUNT, QUICKBOOKS_QUERY_CLASS, QUICKBOOKS_QUERY_JOBTYPE, QUICKBOOKS_QUERY_VENDORTYPE, QUICKBOOKS_QUERY_COMPANY, QUICKBOOKS_IMPORT_RECEIVEPAYMENT, QUICKBOOKS_IMPORT_PURCHASEORDER, QUICKBOOKS_IMPORT_ITEMRECEIPT, QUICKBOOKS_IMPORT_SALESRECEIPT, QUICKBOOKS_IMPORT_INVOICE, QUICKBOOKS_IMPORT_SALESORDER, QUICKBOOKS_IMPORT_ESTIMATE, QUICKBOOKS_IMPORT_BILLPAYMENTCHECK, QUICKBOOKS_IMPORT_BILLPAYMENTCREDITCARD, QUICKBOOKS_IMPORT_BILLTOPAY, QUICKBOOKS_IMPORT_BILL, QUICKBOOKS_IMPORT_CREDITCARDCHARGE, QUICKBOOKS_IMPORT_CREDITCARDCREDIT, QUICKBOOKS_IMPORT_CHECK, QUICKBOOKS_IMPORT_CHARGE, QUICKBOOKS_IMPORT_DELETEDLISTS, QUICKBOOKS_IMPORT_DELETEDTXNS, QUICKBOOKS_IMPORT_TIMETRACKING, QUICKBOOKS_IMPORT_VENDORCREDIT, QUICKBOOKS_IMPORT_INVENTORYADJUSTMENT, QUICKBOOKS_IMPORT_ITEM, QUICKBOOKS_IMPORT_DISCOUNTITEM, QUICKBOOKS_IMPORT_SALESTAXITEM, QUICKBOOKS_IMPORT_SERVICEITEM, QUICKBOOKS_IMPORT_NONINVENTORYITEM, QUICKBOOKS_IMPORT_INVENTORYITEM, QUICKBOOKS_IMPORT_INVENTORYASSEMBLYITEM, QUICKBOOKS_IMPORT_SALESREP, QUICKBOOKS_IMPORT_VEHICLEMILEAGE, QUICKBOOKS_IMPORT_VEHICLE, QUICKBOOKS_IMPORT_CUSTOMER, QUICKBOOKS_IMPORT_VENDOR, QUICKBOOKS_IMPORT_EMPLOYEE, QUICKBOOKS_IMPORT_WORKERSCOMPCODE, QUICKBOOKS_IMPORT_UNITOFMEASURESET, QUICKBOOKS_IMPORT_JOURNALENTRY, QUICKBOOKS_IMPORT_DEPOSIT, QUICKBOOKS_IMPORT_SHIPMETHOD, QUICKBOOKS_IMPORT_PAYMENTMETHOD, QUICKBOOKS_IMPORT_PRICELEVEL, QUICKBOOKS_IMPORT_DATEDRIVENTERMS, QUICKBOOKS_IMPORT_BILLINGRATE, QUICKBOOKS_IMPORT_CUSTOMERTYPE, QUICKBOOKS_IMPORT_CUSTOMERMSG, QUICKBOOKS_IMPORT_TERMS, QUICKBOOKS_IMPORT_SALESTAXCODE, QUICKBOOKS_IMPORT_ACCOUNT, QUICKBOOKS_IMPORT_CLASS, QUICKBOOKS_IMPORT_JOBTYPE, QUICKBOOKS_IMPORT_VENDORTYPE, QUICKBOOKS_IMPORT_COMPANY); // high priorities down here (no dependencies OR queries) // Now, let's space those priorities out a little bit, it gives us some // wiggle room in case we need to add things inbetween the default // priority values static $wiggled = false; $wiggle = 6; if (!$wiggled) { $count = count($priorities); for ($i = $count - 1; $i >= 0; $i--) { $priorities[$i * $wiggle] = $priorities[$i]; unset($priorities[$i]); // with a wiggle multiplier of 2... // priority 25 goes to 50 // priority 24 goes to 48 // priority 23 goes to 46 // etc. etc. etc. } $wiggled = true; //print_r($priorities); } if ($dependency) { // // This is a list of dependency modifications // For instance, normally, you'd want to send just any old DataExtAdd // with a really low priority, because whatever record it applies to // must be in QuickBooks before you send the DataExtAdd/Mod request. // // However, if we pass in the $dependency of QUICKBOOKS_ADD_CUSTOMER, // then we know that this DataExt applies to a CustomerAdd, and can // therefore be sent with a priority *just barely lower than* than a // CustomerAdd request, which will ensure this gets run as soon as // possible, but not sooner than the CustomerAdd. // // This is important because in some cases, this data will be // automatically used by QuickBooks. For instance, a custom field that // is placed on an Invoice *must already be populated for the // Customer* before the invoice is created. // // This is an example of a priority list without dependencies, and it's bad: // CustomerAdd, InvoiceAdd, DataExtAdd // (the custom field for the customer doesn't get populated in the invoice) // // This is an example of a priority list with dependencies, and it's good: // CustomerAdd, DataExtAdd, InvoiceAdd // $dependencies = array(QUICKBOOKS_ADD_DATAEXT => array(QUICKBOOKS_ADD_CUSTOMER => QuickBooks_Utilities::priorityForAction(QUICKBOOKS_ADD_CUSTOMER) - 1, QUICKBOOKS_MOD_CUSTOMER => QuickBooks_Utilities::priorityForAction(QUICKBOOKS_MOD_CUSTOMER) - 1), QUICKBOOKS_MOD_DATAEXT => array(QUICKBOOKS_ADD_CUSTOMER => QuickBooks_Utilities::priorityForAction(QUICKBOOKS_ADD_CUSTOMER) - 1, QUICKBOOKS_MOD_CUSTOMER => QuickBooks_Utilities::priorityForAction(QUICKBOOKS_MOD_CUSTOMER) - 1), QUICKBOOKS_VOID_TRANSACTION => array(QUICKBOOKS_MOD_PURCHASEORDER => QuickBooks_Utilities::priorityForAction(QUICKBOOKS_MOD_PURCHASEORDER) + 1)); } // Check for dependency priorities if ($dependency and isset($dependencies[$action]) and isset($dependencies[$action][$dependency])) { // Dependency modified priority return $dependencies[$action][$dependency]; } else { if ($key = array_search($action, $priorities)) { // Regular priority return $key; } } // Default priority return 999; }
/** * @TODO Change this to return false by default, and only catch the specific errors we're concerned with. * */ public static function catchall($requestID, $user, $action, $ident, $extra, &$err, $xml, $errnum, $errmsg, $config) { $Driver = QuickBooks_Driver_Singleton::getInstance(); $ignore = array(QUICKBOOKS_IMPORT_DELETEDTXNS => true, QUICKBOOKS_QUERY_DELETEDTXNS => true, QUICKBOOKS_IMPORT_DELETEDLISTS => true, QUICKBOOKS_QUERY_DELETEDLISTS => true, QUICKBOOKS_VOID_TRANSACTION => true, QUICKBOOKS_DELETE_TRANSACTION => true, QUICKBOOKS_DELETE_LIST => true); if (isset($ignore[$action])) { // Ignore errors for these requests return true; } /* $Parser = new QuickBooks_XML($xml); $errnumTemp = 0; $errmsgTemp = ''; $Doc = $Parser->parse($errnumTemp, $errmsgTemp); $Root = $Doc->getRoot(); $emailStr = var_export($Root->children(), true); $List = $Root->getChildAt('QBXML QBXMLMsgsRs '.QuickBooks_Utilities::actionToResponse($action)); $Node = current($List->children()); */ $map = array(); $others = array(); QuickBooks_SQL_Schema::mapToSchema(trim(QuickBooks_Utilities::actionToXMLElement($action)), QUICKBOOKS_SQL_SCHEMA_MAP_TO_SQL, $map, $others); $object = new QuickBooks_SQL_Object($map[0], trim(QuickBooks_Utilities::actionToXMLElement($action))); $table = $object->table(); $existing = null; if ($table and is_numeric($ident)) { $multipart = array(QUICKBOOKS_DRIVER_SQL_FIELD_ID => $ident); $existing = $Driver->get(QUICKBOOKS_DRIVER_SQL_PREFIX_SQL . $table, $multipart); } switch ($errnum) { case 1: // These errors occur when we search for something and it doesn't exist // These errors occur when we search for something and it doesn't exist case 500: // i.e. we query for invoices modified since xyz, but there are none that have been modified since then // This isn't really an error, just ignore it if ($action == QUICKBOOKS_DERIVE_CUSTOMER) { // Tried to derive, doesn't exist, add it $Driver->queueEnqueue($user, QUICKBOOKS_ADD_CUSTOMER, $ident, true, QuickBooks_Utilities::priorityForAction(QUICKBOOKS_ADD_CUSTOMER)); } else { if ($action == QUICKBOOKS_DERIVE_INVOICE) { // Tried to derive, doesn't exist, add it $Driver->queueEnqueue($user, QUICKBOOKS_ADD_INVOICE, $ident, true, QuickBooks_Utilities::priorityForAction(QUICKBOOKS_ADD_INVOICE)); } else { if ($action == QUICKBOOKS_DERIVE_RECEIVEPAYMENT) { // Tried to derive, doesn't exist, add it $Driver->queueEnqueue($user, QUICKBOOKS_ADD_RECEIVEPAYMENT, $ident, true, QuickBooks_Utilities::priorityForAction(QUICKBOOKS_ADD_RECEIVEPAYMENT)); } } } return true; case 1000: // An internal error occured // @todo Hopefully at some point we'll have a better idea of how to handle this error... return true; //case 3120: // 3120 errors are handled in the 3210 error handler section // break; //case 3120: // 3120 errors are handled in the 3210 error handler section // break; case 3170: // This list has been modified by another user. // This list has been modified by another user. case 3175: case 3176: case 3180: // This error can occur in several different situations, so we test per situation if (false !== strpos($errmsg, 'list has been modified by another user') or false !== strpos($errmsg, 'internals could not be locked') or false !== strpos($errmsg, 'failed to acquire the lock') or false !== strpos($errmsg, 'list element is in use')) { // This is *not* an error, we can just send the request again, and it'll go through just fine return true; } break; case 3200: // Ignore EditSequence errors (the record will be picked up and a conflict reported next time it runs... maybe?) if ($action == QUICKBOOKS_MOD_CUSTOMER and $existing) { // Queue up a derive customer request // Tried to derive, doesn't exist, add it $Driver->queueEnqueue($user, QUICKBOOKS_DERIVE_CUSTOMER, $ident, true, 9999, array('ListID' => $existing['ListID'])); } else { if ($action == QUICKBOOKS_MOD_INVOICE and $existing) { // Queue up a derive customer request // Tried to derive, doesn't exist, add it $Driver->queueEnqueue($user, QUICKBOOKS_DERIVE_INVOICE, $ident, true, 9999, array('TxnID' => $existing['TxnID'])); } } return true; case 3120: case 3210: //print_r($existing); //print('TXNID: [' . $existing['TxnID'] . ']'); // 3210: The "AppliedToTxnAdd payment amount" field has an invalid value "129.43". QuickBooks error message: You cannot pay more than the amount due. if ($action == QUICKBOOKS_ADD_RECEIVEPAYMENT and (false !== strpos($errmsg, 'pay more than the amount due') or false !== strpos($errmsg, 'cannot be found')) and $existing) { // If this happens, we're going to try to re-submit the payment, *without* the AppliedToTxn element $db_errnum = null; $db_errmsg = null; $Driver->query("\n\t\t\t\t\t\tUPDATE \n\t\t\t\t\t\t\t" . QUICKBOOKS_DRIVER_SQL_PREFIX_SQL . "receivepayment_appliedtotxn \n\t\t\t\t\t\tSET \n\t\t\t\t\t\t\tqbsql_to_skip = 1 \n\t\t\t\t\t\tWHERE \n\t\t\t\t\t\t\tReceivePayment_TxnID = '%s' ", $db_errnum, $db_errmsg, null, null, array($existing['TxnID'])); return true; } break; case 3250: // This feature is not enabled or not available in this version of QuickBooks. // Do nothing (this can be safely ignored) return true; case 3260: // Insufficient permission level to perform this action. // Insufficient permission level to perform this action. case 3261: // The integrated application has no permission to ac... // There's nothing we can do about this, if they don't grant the user permission, just skip it return true; case 3100: // Name of List Element is already in use. break; case '0x8004040D': // The ticket parameter is invalid (how does this happen!?!) return true; } // This is our catch-all which marks the item as errored out if (strstr($xml, 'statusSeverity="Info"') === false) { $multipart = array(QUICKBOOKS_DRIVER_SQL_FIELD_ID => $ident); $object->set(QUICKBOOKS_DRIVER_SQL_FIELD_ERROR_NUMBER, $errnum); $object->set(QUICKBOOKS_DRIVER_SQL_FIELD_ERROR_MESSAGE, $errmsg); // Do not set the resync field, we want resync and modified timestamps to be different $update_resync_field = false; $update_discov_field = false; $update_derive_field = false; if ($table and is_numeric($ident)) { // Set the error message $Driver->update(QUICKBOOKS_DRIVER_SQL_PREFIX_SQL . $table, $object, array($multipart), $update_resync_field, $update_discov_field, $update_derive_field); } } // Please don't change this, it stops us from knowing what's actually // going wrong. If an error occurs, we should either catch it if it's // recoverable, or treated as a fatal error so we know about it and // can address it later. //return false; // I'm changing it because otherwise the sync never completes if a // single error occurs... we need a way to skip errored-out records return true; }