Quickbooks - Select all payments for an invoice - php

I am trying to query for all payments assigned to an invoice using the consolibyte quickbooks php toolkit. I was hoping I could pull these back using a query but so far the only way I can see to do it is by grabbing all payments and then looping through and checking what is within the LinkedTxn array and checking if it has a type of 'Invoice' and that the 'TxnId' value matches my stored invoice id. What I have so far is this which gets the payments but is obviously not an option going forward as there may be tens of thousands of payments in the system:
public function getAllPaymentsForInvoice(Invoice $invoice)
{
$query = "SELECT * FROM Payment";
$payments = $this->qbPaymentService->query($this->context, $this->realm, $query);
$lines = [];
foreach ($payments as $payment) {
$num_lines = $payment->countLine();
for ($i = 0; $i < $num_lines; $i++) {
$line = $payment->getLine($i);
$txnId = $this->parseResponse($line->getLinkedTxn()->getTxnId());// convert {-1} to 1
$txnType = $line->getLinkedTxn()->getTxnType();
if ($txnType == 'Invoice' && $txnId == $invoice->qb_ref) {
$lines[] = $line;
}
}
}
return $lines;
}
Can anyone push me in the direction of a better way?

There's no easy, one-liner way to do this using the QuickBooks Online API. This just isn't something that QuickBooks Online itself supports.
There are two separate approaches you could take to optimize what you're doing.
The payments applied should always be for the same customer as the invoices, so you can change your query to SELECT * FROM Payment WHERE CustomerRef = 'xyz' where xyz is the CustomerRef from the invoice. Then, use your existing code to check exactly which invoices payments are applied to.
Use the CDC functionality to keep a cached copy of all payments in your own database, and query your own database for the information instead (a normal SQL database is considerably more flexible and query-able than what QuickBooks Online offers).
The CDC functionality is specifically designed to allow you to keep an accurate, cached copy of the QuickBooks data in your own application, specifically to address situations like you're running into. You pass it a timestamp, and it gives you everything that has changed since that timestamp. By remembering the date/time you last ran the ->cdc(...) method, you can continually get lightweight updates to any objects that have changed since you last queried them.
<?php
$CDCService = new QuickBooks_IPP_Service_ChangeDataCapture();
// What types of objects do you want to get?
$objects = array(
'Payment',
'Invoice',
);
// The date they should have been updated after
$timestamp = QuickBooks_Utilities::datetime($datetime_you_last_called_cdc_method);
$cdc = $CDCService->cdc($Context, $realm,
$objects,
$timestamp);
// You would cache all the stuff you get back here
print_r($cdc);
Docs links:
https://github.com/consolibyte/quickbooks-php/blob/master/docs/partner_platform/example_app_ipp_v3/example_cdc.php
https://developer.intuit.com/docs/0100_accounting/0300_developer_guides/change_data_capture

The question is quite old, anyway, in case someone else is looking for answer:
$InvoiceService = new QuickBooks_IPP_Service_Invoice();
// $invoices = $InvoiceService->query($Context, $realm, "SELECT * FROM Invoice STARTPOSITION 1 MAXRESULTS 10");
// If, we have QB Invoice # i.e.DocNumber
$invoice_no = 1001;
$invoices = $InvoiceService->query($Context, $realm, "SELECT * FROM Invoice WHERE DocNumber = '$invoice_no' ");
foreach ($invoices as $Invoice)
{
$txnId = 0;
if (is_object($Invoice->getLinkedTxn()) && $Invoice->getLinkedTxn()->getTxnType() == 'Payment') {
$txnId = $Invoice->getLinkedTxn()->getTxnId();
}
print('Invoice # '.$Invoice->getDocNumber().' has a total of $'.$Invoice->getTotalAmt().' and Linked Payment ID: ' . $txnId . "\n");
}

Related

Php-Reports Stripe Building

First question on here as an enthusiast trying to get this to work.
Using the open source project by jdorn PHP-Reports and following the stripe custom reporting tutorials.
The report never returns, what I am trying to achieve is custom Stripe payments reporting using the Stripe API to return all charges, to then display in a nice table 😀
Here is my paymentreport.php code
<?php
//My Payments Report
//This connects to the Stripe Payments api and shows a list of charges
//VARIABLE: { name: "count", display: "Number of Charges" }
// Set your secret key: remember to change this to your live secret key in production
\Stripe\Stripe::setApiKey('StripeTestCodeAPIKEY');
if($count > 100 || $count < 1) throw new Exception("Number of Charges must be between 1 and 100");
// Retrieve a list of 50 most recent charges
$charges = \Stripe\Charge::all(array(
'limit' => 50
));
// Loop through each charge
foreach ($charges->data as $charge) {
// Get the required charge information and assign to variables
$id = $charge->id;
$description = $charge->description;
$created = gmdate('Y-m-d H:i', $charge->created); // Format the time
$amount = $charge->amount/100; // Convert amount from cents to dollars
$currency = $charge->currency;
$rows = array();
foreach($charges as $charge) {
$rows[] = array(
'Charge Id'=>$charge->id,
'Amount'=>number_format($charge->amount/100,2),
'Date'=>date('Y-m-d',$charge->created)
);
}
echo json_encode($rows);
?>
There are a few problems here-- you accept a $count-variable that's never used. I think what you're trying to do is this:
$charges = \Stripe\Charge::all(array(
'limit' => $count
));
Additionally, you've got a loop at the bottom that's broken. You've got to remove the second foreach and move the $rows = array(); outside of the first loop.
Finally, the last call $rows[] = array(...) should probably also include the variables you captured above. Does that make sense?

PHP NetSuite API - get Customer records by internalID range

I need to sync Customers from NetSuite to another system and would like to search Customers by internalId range, using the PHP library for the API. Given that the internalId's are autoincrement fields, I would only need Customers with internalId greater than the highest ID in the external system.
Is there a way to do this? If so, how?
I've currently only been able to iterate over ALL records, using the SearchRequest and SearchMoreWithIdRequest. A slight optimization I can do with this is to iterate over the pages in reverse order to avoid pulling too much duplicate data, but would prefer to filter by internalId (greater-than) if possible.
Thanks
here is existing code
/// initial search
$search = new CustomerSearchBasic();
$request = new SearchRequest();
$request->searchRecord = $search;
$searchResponse = $service->search($request);
// cache the search ID because we're gonna need to re-search, with pagination, using this id
$searchId = $searchResponse->searchResult->searchId;
$totalRecords = $searchResponse->searchResult->totalRecords;
// process the results
process_response($searchResponse);
// get subsequent pages
for ($page = 2; $page < ceil($totalRecords / $pageSize); $page++) {
echo "[page: $page] ... searchId: '$searchId'" . PHP_EOL;
if (!$searchId) continue;
$request = new SearchMoreWithIdRequest();
$request->searchId = $searchId;
$request->pageIndex = $page;
$searchResponse = $service->searchMoreWithId($request);
// process the results
process_response($searchResponse);
}
function process_response($searchResponse) { ... }

How to generate voucher code, check the DB if it's unique, generate new one if not

I'm having a loop issue in my script. I've spent a lot of time trying to fix it but I still don't know how to fix the problem. I need your help and suggestions regarding this.
My goal is to create a voucher code generator script where the user enters the number of voucher codes to be generated.
Then, the script will generate the required number of vouchers in the database table, and each voucher code will be checked if it is unique - if not, a new voucher code will be generated and the script will proceed until all vouchers are saved.
The problem is that if voucher already exists in the DB, a new one needs to be generated. This newly generated voucher code needs to be checked again if it's already in the DB, if it's unique it will be saved to the DB and if not, the process will go on again. This is where the loop problem lies. I hope you get what i mean.
By the way, the voucher code is in this format: XXXX-XXXX-XXXX (uppercase letters only)
Here's the current codes that I have:
include 'conn.php';
function WriteCSV($flname,$values) {
$Filename = "./vouchers/$flname.csv";
$fh = fopen($Filename, 'a') or die("can't open file");
$filecontent = $values;
$filecontent .= PHP_EOL;
fwrite($fh,$filecontent);
fclose($fh);
}
function generateCode(){
$chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
$res = "";
for ($i = 0; $i < 4; $i++) {
$res .= $chars[mt_rand(0, strlen($chars)-1)];
}
return $res;
}
function generateVCode(){
$c1 = generateCode();
$c2 = generateCode();
$c3 = generateCode();
$voucher = "$c1-$c2-$c3";
return $voucher;
}
function searchDB($con, $voucher){
$rs = mysqli_query($con,"SELECT count(*) AS cnt FROM vouchers WHERE vouchercode = '$voucher'");
$row = mysqli_fetch_assoc($rs);
$cnt = $row['cnt'];
if($cnt > 0){
return '1';
} else {
return '0';
}
}
function checkVoucher($con, $voucher, $vsource, $expiry, $today, $vnum, $vprice){
$dbres = searchDB($con, $voucher);
if($dbres == '1'){ //voucher found in db
$val = '0';
$voucher = generateVCode(); //generate a new voucher
checkVoucher($con, $voucher, $vsource, $expiry, $today, $vnum, $vprice); //repeat the process
} else { // voucher is unique
mysqli_query($con, "INSERT INTO vouchers (vouchercode, source, price, expires, generated) VALUES ('$voucher', '$vsource', '$vprice', '$expiry', '$today')");
$flname = "$vsource - ".date('d M Y')." ($vnum vouchers)";
WriteCSV($flname,$voucher);
$val = '1';
}
return $val;
}
$vnum = $_POST['vouchernum'];
$vsource = $_POST['source'];
$vprice = $_POST['amt'];
$expdate = $_POST['expdate'];
$expiry = $_POST['voucherexpiry'];
$today = date('Y-m-d');
$expconv = date('Y-m-d',strtotime("$expiry"));
$expfive = date('Y-m-d',strtotime("$expiry +5 years"));
for ($x = 1; $x <= $vnum; $x++) {
$vouchercode = generateVCode();
if($expdate == "no"){
$expiry = $expfive;
} else {
$expiry = $expconv;
}
do {
$result = checkVoucher($con, $vouchercode, $vsource, $expiry, $today, $vnum, $vprice);
} while ($result != '1');
header("location: index.php?s=1");
}
By the way, if you have suggestions on how to generate the voucher codes easier, please feel free to share.
I'm thinking the issue/problem here is on either the do-while statement or the checkVoucher() function.
I'd really appreciate you help and suggestions. Thanks.
I would go completely easier. Set the voucher column in your table to unique. Generate a code PHP side, do your insert, in the error callback function call to generate a new code.
Basically, this will self loop until inserted. Then in your success callback add it to your display. All of this is wrapped in a while loop. Once you get your 5, break the loop.
As far as generating a random string with minimal chance of a repeat, check this thread: PHP random string generator
I would generate the full length string and then just add your hyphens.
Using this approach to generate random unique data, the amount of processing required increases proportionally as more and more codes are generated.
What I would do instead is:
Generate a whole bunch of values (lets say a few thousand) values sequentially and store them in a redis/SQL database
Use a random number to index that record in the database, and remove the record from the table once it has been used
This reduces the processing required greatly, and also gives you a pre determined pool of voucher codes which could be useful for other purposes in your application
Mysql unique constraint may be the solution you are looking for.it ensures a value is always unique. It is like primary key. but unlike primary key a table can have multiple unique values.
Here is the link to w3school explaining this
www.w3schools.com/sql/sql_unique.asp
The best part is it will genrerate a Duplicate Entry error when adding a duplicate entry. so you can use it to add data to csv . add it only when you have no error.
But make sure the unique value is not null.

PDO 'too many connections' when I select all rows from DB

I am trying to write a function that verifies a bank transaction amount against all purchases linked to that transaction. It is possible to link a purchase to more than one transaction and for more than one purchase to link to the same transaction.
The function works by totalling the transactions in question, then running a DB query to select all purchases, then checking each purchase to see if it references any of the transactions in question. It then totals the amount of all purchases linked to any of the transactions and reconciles it by checking if the two totals add up.
My problem is that I always get a 'too many connections' error when I get all the purchases from the database. It's fine if I limit the search, but I need all of them in order for the function to work.
I either need to fix the connection overload issue somehow write the query to only search for purchases containing those transactions - but I'm not sure how to do that.
The transactions are comma separated in a column of the purchase entry.
Interestingly, if the list of purchases is called via AJAX it works fine. But if the page (including other prior connections) is loaded statically - it doesn't work. I'm assuming that because the AJAX is loading one thing, there are no prior connections in that instance.
It is quite a complicated system so this may not be very helpful on its own:
public static function verify($ids, $return = NULL) {
// Transaction total
$transactions = explode(",",$ids);
$transTotal = 0;
foreach($transactions as $transaction) {
$transTotal = $transTotal + self::get($transaction,"amount");
}
// Expense/item total
$accounts = AccItem::getAll("all");
$itemTotal = 0;
foreach($accounts as $item) {
$translink = explode(",",$item->transaction_ids);
if(array_intersect($transactions, $translink)) {
$itemTotal = $itemTotal + AccItem::calculate($item->id,"total") + AccItem::calculate($item->id,"tax");
}
}
unset($accounts);
if($transTotal == $itemTotal or $transTotal + $itemTotal == 0) {
if($return) return 'check';
if(!$return) echo '<abbr title="Transaction verified."><i class="fa fa-check-circle"></i></abbr>';
} else {
if(!$return) echo '<abbr title="Transaction amount mismatch!"><i class="fa fa-exclamation-circle"></i></abbr>';
}
}
And here is the getAll function:
public static function getAll($chart_id, $date_from = 0, $date_to = 9999999999999) {
$db = new Data;
if($chart_id == "all") {
$sql = $db->query("SELECT * FROM mc_account_items WHERE date_incurred >= :date_from AND date_incurred <= :date_to ORDER BY date_incurred DESC");
} else {
$sql = $db->query("SELECT * FROM mc_account_items WHERE chart_id = :chart_id AND date_incurred >= :date_from AND date_incurred <= :date_to ORDER BY date_incurred DESC");
$sql->bindParam(":chart_id", $chart_id);
}
$sql->bindParam(":date_from", $date_from);
$sql->bindParam(":date_to", $date_to);
$sql->setFetchMode(PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE,'AccItem');
$sql->execute();
return $sql->fetchAll();
//unset($db);
}
So I read into things a bit more, turns out I wanted a persistent connection as it was the same connection every time. Instead of opening a new one it will just use the cached connection.
PDO::ATTR_PERSISTENT => true

MySQL queries optimization

So I've been working on an IRC game for one year now, written in PHP and using a PHP to IRC framework.
Recently, I've added the ability to archive scores (they're being reseted every couple hundreds of games) which forced me to update various admin functions.
I've just updated a function that allows me to merge two players (some users don't bother looking for their old password etc...) in order to merge archived scores too (in case a reset has occurred before I find the duplicated accounts).
The score-merging part (below) works has intended, but I'm wondering if I can optimize the process because I find it rather heavy (but can't think of something better) :
$from_stats = $this->db->query("SELECT `games`, `wins`, `points`, `date_archive` FROM ".$this->dbprefix."score WHERE `id`=".$id1." AND `channel`='".$gamechan."' GROUP BY `date_archive`"); // get scores for the original account
$to_stats = $this->db->query("SELECT `games`, `wins`, `points`, `date_archive` FROM ".$this->dbprefix."score WHERE `id`=".$id2." AND `channel`='".$gamechan."' GROUP BY `date_archive`"); // get scores for the duplicated account
$from_games = array();
$from_wins = array();
$from_points = array();
$from_date = array();
while (list($fromstats_games,$fromstats_wins,$fromstats_points,$fromstats_date) = $this->db->fetchRow($from_stats)) { // build score arrays for the original account
$from_games[count($from_games)] = $fromstats_games;
$from_wins[count($from_wins)] = $fromstats_wins;
$from_points[count($from_points)] = $fromstats_points;
$from_date[count($from_date)] = $fromstats_date;
}
$to_games = array();
$to_wins = array();
$to_points = array();
$to_date = array();
while (list($tostats_games,$tostats_wins,$tostats_points,$tostats_date) = $this->db->fetchRow($to_stats)) { // build score arrays for the duplicated account
$to_games[count($to_games)] = $tostats_games;
$to_wins[count($to_wins)] = $tostats_wins;
$to_points[count($to_points)] = $tostats_points;
$to_date[count($to_date)] = $tostats_date;
}
foreach ($from_date as $key1 => $id1_date) {
foreach ($to_date as $key2 => $id2_date) {
if ($id1_date == $id2_date) { // merge scores if dates match
$from_games[$key1] += $to_games[$key2];
$from_wins[$key1] += $to_wins[$key2];
$from_points[$key1] += $to_points[$key2];
$this->db->query("UPDATE ".$this->dbprefix."score SET `games`=".$from_games[$key1].", `wins`=".$from_wins[$key1].", `points`=".$from_points[$key1]." WHERE `id`=".$id1." AND `channel`='".$gamechan."' AND `date_archive`='".$id1_date."'");
break;
}
}
}
$this->db->query("DELETE FROM ".$this->dbprefix."score WHERE `id`=".$id2); // delete all entries for the duplicated account
Just one tip: after all use this query (if You have appropriate privilages)
$this->db->query("OPTIMIZE TABLE ".$this->dbprefix."score");
This should cause all indexes in this table to be recalculated. You'll notice the index file size has changed to 1kb (or few bytes)

Categories