Odyssey
homecu_billing_qbwc.prg
1 <?php
2 /* File: homecu_billing_qbwc.prg
3  * Purpose: This script is used by the QuickBooks Web Connector to extract billing information and input it into QuickBooks.
4  * It will read the cubillpreinvoice table to get the data
5  * to create the XML that is fed to QuickBooks.
6  */
7 
8 $sharedLibrary = dirname(__FILE__) . "/../../shared/library";
9 $monLibrary = dirname(__FILE__) . "/../../monitor/library";
10 $billingLibrary = dirname(__FILE__) . "/../library";
11 
12 require_once("$monLibrary/cu_top.i");
13 require_once("$billingLibrary/globals.i");
14 
15 $MY_TICKET = "15c9ce293bd3f41b761c21635b14fa06";
16 
17 header( "Content-Type: text/xml" );
18 
19 // figure out what is called
20 $xmlString = file_get_contents("php://input");
21 
22 ini_set("soap.wsdl_cache_enabled", "0");
23 
24 /* this could also work: http://developer.intuit.com/uploadedFiles/Support/QBWebConnectorSvc.wsdl
25  * this is the wsdl: https://test.developer.intuit.com/QBWC/TroubleshootWebServiceFS/Service.asmx?wsdl
26  */
27 $mTime = file_exists("/tmp/wsdl.xml") ? filemtime("/tmp/wsdl.xml") : false;
28 $yesterday = strtotime("-1 day");
29 if ($mTime === false || $mTime - $yesterday > 86400) {
30 
31  $wfpw = fopen("/tmp/wsdl.xml", "w");
32  $ch = curl_init();
33  curl_setopt($ch, CURLOPT_URL, "https://test.developer.intuit.com/QBWC/TroubleshootWebServiceFS/Service.asmx?wsdl");
34  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
35 
36  curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);
37  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
38  curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
39  curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
40 
41  $response = curl_exec($ch);
42 
43  fwrite($wfpw, $response);
44  curl_close($ch);
45 
46  fclose($wfpw);
47  sleep(1);
48 }
49 
50 $WSDL = "http://developer.intuit.com/uploadedFiles/Support/QBWebConnectorSvc.wsdl";
51 $WSDL = "https://test.developer.intuit.com/QBWC/TroubleshootWebServiceFS/Service.asmx?wsdl";
52 $WSDL = "file:///tmp/wsdl.xml";
53 $soapServer = new SoapServer($WSDL);
54 
55 /* Assumably QBWC has a 2 minute requirement between its response and the web service response.
56  * So keep the information transfer packets smaller.
57  * Here is good information: http://www.consolibyte.com/docs/index.php/QuickBooks_Web_Connector_Overview
58  */
59 
60 $soapServer->addFunction( "serverVersion" );
61 $soapServer->addFunction( "clientVersion" );
62 $soapServer->addFunction( "authenticate" );
63 $soapServer->addFunction( "sendRequestXML" );
64 $soapServer->addFunction( "receiveResponseXML" );
65 $soapServer->addFunction( "connectionError" );
66 $soapServer->addFunction( "getLastError" );
67 $soapServer->addFunction( "closeConnection" );
68 
69 /**
70  * To install the webconnect "application" on quickbooks (.qwc file), comment out the
71  * following line: $soapServer->handle ($xmlString);
72  * Then using QB WebConnect, add the application. You should see the SSL certificate installation message.
73  * Before hitting "OK" to continue, uncomment the line you just commented out. Now click "OK" to
74  * continue the installation.
75  * If the webconnect "application" was already installed and the same QB company file is being used,
76  * there may be some confusion in QB that the OwnerID/FileID is already installed. If this is the case
77  * (you will see the error message) you will need to edit the .QWC file to change either the OwnerID or
78  * FileID GUID (just change at least one character). Then re-do the above steps.
79  */
80 
81 if (isset($xmlString)) {
82  $soapServer->handle ($xmlString);
83 }
84 
85 
86 /* Required function by QBWC. Good status is empty, otherwise starts with okay "O:", warnings "W:", and errors "E:"
87  * Parameters: $params-- object that contains a string version variable
88  * Returns: $reply.clientVersionResult-- ""
89  */
90 function clientVersion( $params ) {
91  $reply = array( "clientVersionResult" => "" );
92  return $reply;
93 }
94 
95 /* Required function by QBWC.
96  * Parameters: $params-- apparently an empty array
97  * Returns: $reply.serverVersionResult-- "0.9"
98  */
99 function serverVersion( $params ) {
100  $xsi = "http://www.w3.org/2001/XMLSchema-instance";
101  $soap = "http://schemas.xmlsoap.org/soap/envelope/";
102  $outputXML = '<soap:Envelope xmlns:xsi="' . $xsi . '" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="' . $soap . '">
103  <soap:Body>
104  <serverVersionResponse xmlns="http://developer.intuit.com/">
105  <serverVersionResult>Mike Is Grate</serverVersionResult>
106  </serverVersionResponse>
107  </soap:Body>
108  </soap:Envelope>';
109 
110  $reply = array( "serverVersionResult" => "0.9" );
111  return $reply;
112 }
113 
114 function authenticate($params) {
115  try {
116  global $MY_TICKET, $dbh, $cookieName;
117  $user = $params->strUserName;
118  $pass = $params->strPassword;
119  $defaultRetries = 5;
120 
121  // set up the result to return
122  $returnResult = array();
123  $returnResult["string"][0] = ""; // need a value here for success
124  $returnResult["string"][1] = "none"; // this is blank for success
125 
126  $results = GetCurrentRun($dbh);
127  if ($results["status"] !== "000") {
128  throw new exception ($results["error"], 1);
129  }
130  $txnDate = $results["date"];
131 
132  $returnResult["string"][1] = "nvu"; // this is blank for success
133 
134  if (!CheckPerm($dbh, $user, "billing_qbwc", $_SERVER['REMOTE_ADDR'])) {
135  throw new exception ("Permissions failure", 2);
136  }
137 
138  // now check the user/pass
139  $lowerUser = strtolower($user);
140  $sql = "select user_name, passwd, failedremain from dmsmonitorusers where lower(user_name) = '" . prep_save($lowerUser, 12) . "'";
141  $sth = db_query($sql, $dbh);
142  if (!$sth) {
143  throw new exception ("Select query failed.", 3);
144  }
145  $record = db_num_rows($sth) > 0 ? db_fetch_assoc($sth, 0) : false;
146  if ($record === false) {
147  throw new exception ("Invalid Username or Password", 4);
148  }
149 
150  $user = $record["user_name"];
151  $password = $record["passwd"];
152  $failedCount = $record["failedremain"];
153 
154  $password = preg_replace("/ +$/","", $password);
155 
156  // For development use the following check
157  if ($password != crypt($pass, $password)) {
158 
159  // ** Update the failed retry limit -- and the last failed login attempt date
160  $failedCount --;
161  if ( $failedCount < 0 ) {
162  $failedCount = 0;
163  }
164  $sql = "update dmsmonitorusers set failedremain = " . intval($failedCount) . ", failedlogin = to_char(now(),'YYYY/MM/DD HH24:MI:SS')
165  where lower(user_name) = '" . prep_save($lowerUser, 12) . "'";
166  $sth = db_query ($sql, $dbh);
167  if (!$sth) {
168  throw new exception ("Update query failed.", 7);
169  }
170 
171  if ( $failedCount <= 0 ) {
172  throw new exception ("Account is Locked", 5);
173  } else {
174  throw new exception ("Invalid Username or Password", 6);
175  }
176  }
177 
178  // ** Successful --- make sure to record a good login
179  $sql = "update dmsmonitorusers set failedremain = '" . intval($defaultRetries) . "', priorlogin = lastlogin, lastlogin = to_char(now(),'YYYY/MM/DD HH24:MI:SS')
180  where lower(user_name) = '" . prep_save($lowerUser, 12) . "'";
181  $sth = db_query($sql, $dbh);
182  if (!$sth) {
183  throw new exception ("Update query failed.", 8);
184  }
185 
186  // clear out error condition
187  $returnResult["string"][1] = "";
188 
189  // success!!!
190 
191  // generate a ticket id
192  $now = time();
193  $ip = $_SERVER["REMOTE_ADDR"];
194  $hash = MD5( $MY_TICKET . MD5( join( ":", array( $MY_TICKET, $now, $ip ) ) ) );
195  $ticketString = "t=$now&i=$ip&h=$hash";
196 
197  $returnResult["string"][0] = $ticketString;
198 
199  // set the step to show we are processing
200  $sql = "update cubillmonth set processing_flag = 110, last_step_date = now() where billing_date = '" . prep_save($txnDate) . "'";
201  $sth = db_query($sql, $dbh);
202  if (!$sth) {
203  throw new exception ("Update query failed.", 9);
204  }
205 
206  // clear out any errors that might have been left over from a prior run
207  WriteErrorMsg( $dbh, $txnDate, "", "");
208  } catch (exception $e) {
209  WriteErrorMsg( $dbh, $txnDate, $e->getCode(), $e->getMessage());
210  }
211 
212  return array( "authenticateResult" => $returnResult );
213 }
214 
215 
216 /* Consistently gets the current transaction run.
217  * Parameters: $dbh-- database connection, $errors (reference)-- array to append any additional errors to, $sqls (reference)-- array to add any additional SQL run to
218  * Returns: the date of the oldest month that is incomplete in the cubillmonth table.
219  */
220 function GetCurrentRun($dbh) {
221  try {
222  $sql = "select billing_date from cubillmonth where started_date is not null and processing_flag in (100,110,120) order by billing_date limit 1";
223  $sth = db_query($sql, $dbh);
224  if (!$sth) {
225  throw new exception ("Select query failed.", 1);
226  }
227  $date = db_num_rows($sth) > 0 ? db_fetch_row($sth, 0)[0] : null;
228  $returnArray = array("status" => "000", "error" => "", "date" => $date);
229  } catch (exception $e) {
230  $returnArray = array("status" => $e->getCode(), "error" => $e->getMessage());
231  }
232  return $returnArray;
233 } // end getCurrentRun
234 
235 /* Writes an error to the cubillmonth table, being aware of column size limits.
236  * Parameters: $dbh-- database connection, $runDate-- date that processing is running for, $errorCode-- error code to write, $errorMsg-- error message to write,
237  * $errors (reference)-- array to append any additional errors to, $sqls (reference)-- array to add any additional SQL run to
238  */
239 function WriteErrorMsg( $dbh, $runDate, $errorCode, $errorMsg) {
240 
241  try {
242  // get the current transaction date
243  $sql = "update cubillmonth set last_error_code = '" . prep_save($errorCode, 20) . "', last_error_msg = '" . prep_save($errorMsg, 250)
244  . "' where billing_date = '" . prep_save($runDate) . "'";
245  $sth = db_query($sql, $dbh);
246  if (!$sth) {
247  throw new exception("Update query failed.", 1);
248  }
249 
250  $returnArray = array("status" => "000", "error" => "");
251  } catch (exception $e) {
252  $returnArray = array("status" => $e->getCode(), "error" => $e->getMessage());
253  }
254  return $returnArray;
255 } // end writeErrorMsg
256 
257 function sendRequestXML($params) {
258  try {
259  global $dbh, $cookieName;
260 
261  $billingEmailFlag = GetBillingEmailFlag();
262 
263  $sql = "select upper(cu_id) \"cu\", coalesce(flags, 0) & " . intval($billingEmailFlag) . " \"flag\" from cubillsalesorder";
264  $sth = db_query($sql, $dbh);
265  if (!$sth) {
266  throw new exception ("Select query failed.", 1);
267  }
268  $results = db_fetch_all($sth);
269  $results = $results === false ? array() : $results;
270  $cuPreferredDeliveryMap = array();
271  foreach($results as $result) {
272  $cuPreferredDeliveryMap[$result["cu"]] = $result["flag"];
273  }
274 
275  $results = GetCurrentRun($dbh);
276  if ($results["status"] !== "000") {
277  throw new exception ($results["error"], 2);
278  }
279 
280  $txnDate = $results["date"];
281 
282  // see if we have more information to transfer to QuickBooks
283  // flag = 0 means hasn't been processed; flag = 10 means we never got a successful reply (so do it again)
284  $sql = "select pi.cu_id \"cu\", pi.description \"description\", pi.billing_system_id \"billingItem\", pi.quantity \"quantity\", pi.amount \"amount\"
285  from cubillpreinvoice pi
286  where pi.billing_date = '" . prep_save($txnDate) . "' and pi.processing_flag in (0, 10)
287  order by pi.cu_id, pi.billing_system_id";
288  $sth = db_query($sql, $dbh);
289  if (!$sth) {
290  throw new exception ("Select query failed.", 3);
291  }
292  $recordsToProcess = db_fetch_all($sth);
293  $recordsToProcess = $recordsToProcess === false ? array() : $recordsToProcess;
294 
295  if (trim($params->ticket) == "") {
296  throw new exception ("Missing access ticket", 4);
297  }
298 
299  if (!CheckValidAccess($params->ticket)) {
300  throw new exception ("Access failure.", 5);
301  }
302 
303  $first = true;
304  $xmlRequest = "<?xml version=\"1.0\" encoding=\"utf-8\"?>
305  <?qbxml version=\"11.0\"?>
306  <QBXML>
307  <QBXMLMsgsRq onError=\"stopOnError\">
308  #
309  </QBXMLMsgsRq>
310  </QBXML>";
311  $invoiceXML = "";
312  $invoiceXMLInnards = "";
313  $index = 0;
314 
315  foreach($recordsToProcess as $record) {
316  if ($first) {
317  $cu = trim(strtolower($record["cu"]));
318  $emailCu = isset($cuPreferredDeliveryMap[strtoupper($cu)]) && $cuPreferredDeliveryMap[strtoupper($cu)] != 0;
319  $printCu = $emailCu ? "false" : "true";
320  $emailCu = $emailCu ? "true" : "false";
321  $emailCuXML = "<IsToBePrinted>$printCu</IsToBePrinted>
322  <IsToBeEmailed>$emailCu</IsToBeEmailed>";
323  $first = false;
324  $invoiceXML = "<InvoiceAddRq>
325  <InvoiceAdd>
326  <CustomerRef>
327  <FullName>$cu</FullName>
328  </CustomerRef>
329  <TxnDate>$txnDate</TxnDate>
330  $emailCuXML
331  #
332  </InvoiceAdd>
333  <IncludeRetElement>CustomerRef</IncludeRetElement>
334  </InvoiceAddRq>";
335 
336  }
337  else if (trim(strtolower($record["cu"])) != $cu) {
338  // can only submit one invoice at a time
339  break;
340  }
341  $billingItem = $record["billingItem"];
342  $quantity = floatval($record["quantity"]); // float in the case of timetrack hours but most of the time will be integer.
343  $amount = sprintf('%0.2f', $record["amount"]);
344  $descLine = $record["description"] == "" ? "" : "<Desc>" . $record["description"] . "</Desc>";
345  $invoiceXMLInnards .= "<InvoiceLineAdd>
346  <ItemRef>
347  <FullName>$billingItem</FullName>
348  </ItemRef>
349  $descLine
350  <Quantity>$quantity</Quantity>
351  <Amount>$amount</Amount>
352  </InvoiceLineAdd>";
353  $index++;
354  }
355 
356  if ($invoiceXMLInnards != "") {
357  $invoiceXML = str_replace("#", $invoiceXMLInnards, $invoiceXML);
358  $xmlRequest = str_replace("#", $invoiceXML, $xmlRequest);
359  } else {
360  $xmlRequest = "";
361  }
362 
363  if ($xmlRequest == "") { // done with the month
364  // didn't find any CUs so we are done with this month
365  // NOTE: this means receiveResponseXML won't be called
366  $sql = "update cubillmonth set processing_flag = 120, last_step_date = now() where billing_date = '" . prep_save($txnDate) . "'";
367  $sth = db_query($sql, $dbh);
368  if (!$sth) {
369  throw new exception ("Update query failed.", 6);
370  }
371  } else {
372  $upperCu = strtoupper($cu);
373  // mark the items as being handled to this point
374  $sql = "update cubillpreinvoice set processing_flag = 10
375  where upper(cu_id) = '" . prep_save($upperCu, 10) . "' and billing_date = '" . prep_save($txnDate) . "'";
376  $sth = db_query($sql, $dbh);
377  if (!$sth) {
378  throw new exception ("Update query failed.", 7);
379  }
380  }
381 
382  } catch (exception $e) {
383  $xmlRequest = "";
384  WriteErrorMsg( $dbh, $txnDate, $e->getCode(), $e->getMessage());
385  }
386  return array( "sendRequestXMLResult" => $xmlRequest );
387 }
388 
389 /* Checks the given input for correctness against the hash algorithm.
390  * Parameters: $string-- the string representation of the ticket created from the "Authenticate" function earlier.
391  * Returns: $retVal-- Boolean true if the ticket returned from QB is the same as the global ticket variable, false otherwise.
392  */
393 function CheckValidAccess( $string ) {
394  global $MY_TICKET;
395 
396  // check the parts
397  $parts = array();
398 
399  parse_str( $string, $parts );
400 
401  $now = time();
402  $ip = $_SERVER["REMOTE_ADDR"];
403 
404  $hash = MD5( $MY_TICKET . MD5( join( ":", array( $MY_TICKET, $parts["t"], $parts["i"] ) ) ) );
405 
406  if ( $hash == $parts["h"] ) {
407  // passed the hash, check the ip of the caller
408  if ( $ip != $parts["i"] ) {
409  // not from where we expected!
410  $retVal = false;
411  } else {
412  // passed the hash, now check time - only get twenty minutes
413  if ( ($now - $parts["t"]) > ( 20 * 60 ) ) {
414  // too long!
415  $retVal = false;
416  } else {
417  // passed!
418  $retVal = true;
419  }
420  }
421  } else {
422  $retVal = false;
423  }
424 
425  return $retVal;
426 } // end checkValidAccess
427 
428 /* Could get something back like this:
429  <?xml version="1.0" ?>
430  <QBXML>
431  <QBXMLMsgsRs>
432  <InvoiceAddRs statusCode="0" statusSeverity="Info" statusMessage="Status OK">
433  <InvoiceRet>
434  <CustomerRef>
435  <ListID>2B20000-1131404885</ListID>
436  <FullName>southern</FullName>
437  </CustomerRef>
438  </InvoiceRet>
439  </InvoiceAddRs>
440  </QBXMLMsgsRs>
441  </QBXML>
442 */
443 function receiveResponseXML( $params ) {
444  try {
445  global $dbh;
446 
447  // response is a percent complete (1 - 99), or 100 if done; negative value is an error
448  $responseValue = 100;
449 
450  $results = GetCurrentRun($dbh);
451  if ($results["status"] !== "000") {
452  throw new exception ($results["error"], 1);
453  }
454  $txnDate = $results["date"];
455 
456  if (trim($params->ticket) == "") {
457  throw new exception ("Missing access ticket", 2);
458  }
459 
460  if (!CheckValidAccess($params->ticket)) {
461  throw new exception ("Access failure.", 3);
462  }
463 
464  // if got a result or message then there is a problem
465  $hResult = $params->hresult;
466  $message = $params->message;
467 
468  if ( strlen( $hResult ) > 0 || strlen( $message ) > 0 ) {
469  throw new exception ("QB Error: $message", 4);
470  }
471 
472  // get the response from the last request
473  $xmlString = $params->response;
474 
475  $xmlObject = simplexml_load_string( $xmlString );
476 
477  if ( strlen( $txnDate ) > 0 ) {
478  // get the CU just processed
479  $cuProcessed = strtoupper( trim( $xmlObject->QBXMLMsgsRs->InvoiceAddRs->InvoiceRet->CustomerRef->FullName ) );
480 
481  if ( strlen( $cuProcessed ) ) {
482  // mark the items just processed
483  $sql = "update cubillpreinvoice set processing_flag = 20 where processing_flag = 10 and upper(cu_id) = '" . prep_save($cuProcessed, 10) . "'
484  and billing_date = '" . prep_save($txnDate) . "'";
485  $sth = db_query($sql, $dbh);
486  if (!$sth) {
487  throw new exception ("Update query failed.", 5);
488  }
489  } else {
490  // check for a result error
491  $statusCode = trim( $xmlObject->QBXMLMsgsRs->InvoiceAddRs["statusCode"] );
492 
493  if ( strlen( $statusCode ) ) {
494  $statusMsg = trim( $xmlObject->QBXMLMsgsRs->InvoiceAddRs["statusMessage"] );
495  }
496 
497  throw new exception ("QB returned error: $statusCode - $statusMsg", 6);
498  $responseValue = 100;
499  }
500 
501  // get the remaining count
502  $sql = "select distinct cu_id \"cu\", processing_flag \"flag\" from cubillpreinvoice where billing_date = '" . prep_save($txnDate) . "'";
503  $sth = db_query($sql, $dbh);
504  if (!$sth) {
505  throw new exception ("Select query failed.", 7);
506  }
507  $queryResults = db_fetch_all($sth);
508  $queryResults = $queryResults === false ? array() : $queryResults;
509 
510  $pending = 0;
511  $complete = 0;
512 
513  foreach($queryResults as $record) {
514  $flag = $record["flag"];
515  $cu = $record["cu"];
516  if (in_array($flag, array(0, 10))) {
517  $pending++;
518  } else {
519  $complete++;
520  }
521  }
522 
523  // return a response value
524  $total = $pending + $complete;
525  if ( $total > 0 ) {
526  $responseValue = floor( 100 * $complete / $total );
527  if ( $responseValue == 100 && $pending > 0 ) {
528  // make sure we are not saying we are done due to rounding
529  $responseValue = 99;
530  } else {
531 
532  if (!db_work ($dbh, HOMECU_WORK_BEGIN)) {
533  throw new exception("begin query failed.", 8);
534  }
535 
536  $sql = "update cubillpreinvoice set processing_flag = 25 where processing_flag = 20 and billing_date = '" . prep_save($txnDate) . "'";
537  $sth = db_query($sql, $dbh);
538  if (!$sth) {
539  throw new exception ("Update query failed.", 109);
540  }
541 
542  $sql = "update cubillmonth set processing_flag = 120, last_step_date = now() where billing_date = '" . prep_save($txnDate) . "'";
543  $sth = db_query($sql, $dbh);
544  if (!$sth) {
545  throw new exception ("Update query failed.", 110);
546  }
547 
548  if (!db_work ($dbh, HOMECU_WORK_COMMIT)) {
549  throw new exception("commit query failed.", 111);
550  }
551  }
552 
553  } else {
554  // didn't find anything, so I guess we are done
555  $responseValue = 100;
556  }
557  } else {
558  // nothing to process
559  $responseValue = 100;
560  }
561 
562 
563  } catch (exception $e) {
564 
565  if ($e->getCode() > 100) {
566  db_work($dbh, HOMECU_WORK_ROLLBACK);
567  }
568 
569  WriteErrorMsg( $dbh, $txnDate, $e->getCode(), $e->getMessage());
570  }
571 
572  return array( "receiveResponseXMLResult" => $responseValue );
573 }
574 
575 function connectionError($params) {
576  try {
577  $lastErrorCode = "";
578  global $dbh;
579 
580  $xsi = "http://www.w3.org/2001/XMLSchema-instance";
581  $soap = "http://schemas.xmlsoap.org/soap/envelope/";
582  $outputXML = '<soap:Envelope xmlns:xsi="' . $xsi . '" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="' . $soap . '">
583  <soap:Body>
584  <connectionErrorResponse xmlns="http://developer.intuit.com/">
585  <connectionErrorResult>This would be an error string.</connectionErrorResult>
586  </connectionErrorResponse>
587  </soap:Body>
588  </soap:Envelope>';
589 
590  // get the last error and error code
591  $sql = "select billing_date, last_error_code from cubillmonth where started_date is not null and processing_flag IN (110, 120) order by billing_date limit 1";
592  $sth = db_query($sql, $dbh);
593  if (!$sth) {
594  throw new exception ("Select query failed.", 1);
595  }
596  $lastErrorCode = db_num_rows($sth) > 0 ? db_fetch_row($sth, 0)[1] : "";
597 
598  } catch (exception $e) {
599  $lastErrorCode = $e->getMessage();
600  }
601 
602  return array( "connectionErrorResult" => $lastErrorCode );
603 }
604 
605 function getLastError($params) {
606  try {
607  global $dbh;
608 
609  $xsi = "http://www.w3.org/2001/XMLSchema-instance";
610  $soap = "http://schemas.xmlsoap.org/soap/envelope/";
611  $outputXML = '<soap:Envelope xmlns:xsi="' . $xsi . '" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="' . $soap . '">
612  <soap:Body>
613  <getLastErrorResponse xmlns="http://developer.intuit.com/">
614  <getLastErrorResult>$lastErrorCode</getLastErrorResult>
615  </getLastErrorResponse>
616  </soap:Body>
617  </soap:Envelope>';
618 
619  // get the last error and error code
620  $sql = "select billing_date, last_error_code, last_error_msg from cubillmonth where started_date is not null and processing_flag in (110, 120)
621  order by billing_date limit 1";
622  $sth = db_query($sql, $dbh);
623  if (!$sth) {
624  throw new exception ("Select query failed.", 1);
625  }
626  $record = db_num_rows($sth) > 0 ? db_fetch_assoc($sth, 0) : false;
627 
628  $date = $record === false ? "" : trim($record["billing_date"]);
629  $code = $record === false ? "" : trim($record["last_error_code"]);
630  $msg = $record === false ? "" : trim($record["last_error_msg"]);
631 
632  $errorCode = $code == "" ? "" : "Code: $code";
633  $errorMsg = $msg == "" ? "" : "- $msg";
634  $errorString = $code == "" ? "" : $errorCode . $errorMsg;
635  } catch (exception $e) {
636  $errorString = "Code: {$e->getCode()}- {$e->getMessage()}";
637  }
638 
639  return array( "getLastErrorResult" => $errorString );
640 }
641 
642 function closeConnection($params) {
643  try {
644  global $dbh;
645 
646  $xsi = "http://www.w3.org/2001/XMLSchema-instance";
647  $soap = "http://schemas.xmlsoap.org/soap/envelope/";
648  $outputXML = '<soap:Envelope xmlns:xsi="' . $xsi . '" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="' . $soap . '">
649  <soap:Body>
650  <closeConnectionResponse xmlns="http://developer.intuit.com/">
651  <closeConnectionResult>SUCCESS</closeConnectionResult>
652  </closeConnectionResponse>
653  </soap:Body>
654  </soap:Envelope>';
655 
656  // if all successful, mark the month entry as completed
657  $sql = "select billing_date, last_error_code from cubillmonth where started_date is not null and processing_flag in (120) order by billing_date limit 1";
658  $sth = db_query($sql, $dbh);
659  if (!$sth) {
660  throw new exception ("Select query failed.", 1);
661  }
662  $record = db_num_rows($sth) > 0 ? db_fetch_assoc($sth, 0) : false;
663  $txnDate = $record === false ? "" : trim($record["billing_date"]);
664  $errorCode = $record === false ? "" : trim($record["last_error_code"]);
665 
666  if (trim($params->ticket) == "") {
667  throw new exception ("Missing access ticket", 2);
668  }
669 
670  if (!CheckValidAccess($params->ticket)) {
671  throw new exception ("Access failure.", 3);
672  }
673 
674  if ($txnDate != "") {
675  // make sure there are no invoice items left (in case an error occurred that wasn't recorded)
676  $sql = "select count(*) as count from cubillpreinvoice where billing_date = '" . prep_save($txnDate) . "' and processing_flag < 25";
677  $sth = db_query($sql, $dbh);
678  if (!$sth) {
679  throw new exception ("Select query failed.", 4);
680  }
681  $count = db_num_rows($sth) > 0 ? intval(db_fetch_row($sth, 0)[0]) : 0;
682 
683  if ($count == 0 ) {
684  // mark the billing processing and the process as completed
685 
686  if (!db_work ($dbh, HOMECU_WORK_BEGIN)) {
687  throw new exception("begin query failed.", 5);
688  }
689 
690  $sql = "update cubillpreinvoice set processing_flag = 99 where processing_flag = 25";
691  $sth = db_query($sql, $dbh);
692  if (!$sth) {
693  throw new exception ("Update query failed.", 106);
694  }
695 
696  $sql = "update cubillmonth set processing_flag = 200, last_step_date = now() where billing_date = '" . prep_save($txnDate) . "'";
697  $sth = db_query($sql, $dbh);
698  if (!$sth) {
699  throw new exception ("Update query failed.", 107);
700  }
701 
702  if (!db_work ($dbh, HOMECU_WORK_COMMIT)) {
703  throw new exception("commit query failed.", 108);
704  }
705 
706  $returnResult = "SUCCESS: $txnDate";
707  } else {
708  $returnResult = "Incomplete finish";
709  }
710  } else {
711  $returnResult = "Finished with error(s)";
712  }
713 
714  } catch (exception $e) {
715 
716  if ($e->getCode() > 100) {
717  db_work($dbh, HOMECU_WORK_ROLLBACK);
718  }
719  $returnResult = "Finished with error(s)";
720 
721  WriteErrorMsg( $dbh, $txnDate, $e->getCode(), $e->getMessage());
722  }
723 
724  return array( "closeConnectionResult" => $returnResult );
725 }
726 
727 return;
728