Overhauled export, and other minor updates

This commit is contained in:
Deon George
2013-05-14 23:53:04 +10:00
parent e81eb7a446
commit 684b46f585
33 changed files with 690 additions and 652 deletions

View File

@@ -0,0 +1,19 @@
<?php defined('SYSPATH') or die('No direct access allowed.');
/**
* This class enables common Export functions for Plugins
*
* @package Export
* @category Plugins
* @author Deon George
* @copyright (c) 2009-2013 Open Source Billing
* @license http://dev.osbill.net/license.html
*/
abstract class Export_Plugin {
protected $emo; // Our Export Object
public function __construct(Model_Export_Module $emo) {
$this->emo = $emo;
}
}
?>

View File

@@ -0,0 +1,288 @@
<?php defined('SYSPATH') or die('No direct access allowed.');
/**
* This class provides OSB exporting capabilities for Quickbooks.
*
* @package Export
* @category Plugins
* @author Deon George
* @copyright (c) 2009-2013 Open Source Billing
* @license http://dev.osbill.net/license.html
*/
class Export_Plugin_Quicken extends Export_Plugin {
public function export(Response $response) {
$output = '';
if (! isset($_POST['id']) OR ! $_POST['id'] OR ! $this->emo->loaded())
HTTP::redirect(URL::link('reseller','export/index'));
$o = ORM::factory('Module',$this->emo->module_id);
if (! $o->loaded())
HTTP::redirect(URL::link('reseller','export/index'));
switch ($o->name) {
case 'invoice': $output .= $this->invoices($_POST['id']);
break;
case 'payment': $output .= $this->payments($_POST['id']);
break;
default:
throw HTTP_Exception::factory(501,'Dont know how to export :name',array(':name'=>$o->name));
}
$response->body($output);
$response->send_file(TRUE,'quicken-import.iif',array('mime_type'=>'text/plain'));
}
/**
* Export an invoice
*/
private function invoice(Model_Invoice $io) {
$defaults = array(
'ACCNT'=>'Accounts Receivable',
'TRNSTYPE'=>'TAX_INVOICE',
'INVMEMO'=>'Thank you for using '.Company::instance()->name(),
'CLEAR'=>'N',
'TOPRINT'=>'N',
'PAID'=>'N',
'MEMO'=>'Import from OSB',
'INVTITLE'=>Company::instance()->name().' Invoice',
);
$invoice = $items = array();
$invoice['TRNSID'] = sprintf('%06s',$io->id);
$invoice['DATE'] = date('m/d/Y',$io->date_orig);
$invoice['ADDR1'] = $io->account->address1;
$invoice['ADDR2'] = $io->account->address2;
$invoice['ADDR3'] = sprintf('%s, %s %s',$io->account->city,$io->account->state,$io->account->zip);
// @todo - should be configurable
# $invoice['TERMS'] = '7 Days';
$invoice['DOCNUM'] = sprintf('%06s',$io->id);
$invoice['DUEDATE'] = date('m/d/Y',$io->due_date);
$invoice['AMOUNT'] = sprintf('%3.2f',$io->total());
$invoice['NAME'] = $io->account->company ? $io->account->company : sprintf('%s %s',$io->account->last_name,$io->account->first_name);
// Other Quicken fields not used.
#$invoice['CLASS'] = '';
#$invoice['SHIPVIA'] = '';
#$invoice['SHIPDATE'] = '';
#$invoice['OTHER1'] = '';
#$invoice['REP'] = '';
#$invoice['FOB'] = '';
#$invoice['PONUM'] = '';
#$invoice['SADDR1'] = '';
#$invoice['SADDR2'] = '';
#$invoice['SADDR3'] = '';
#$invoice['SADDR4'] = '';
#$invoice['SADDR5'] = '';
$c = 0;
// Add the items to the invoice
foreach ($io->invoice_item->find_all() as $iio) {
// Skip any zero amount items not relating to a service
if ($iio->total() == 0 and ! $iio->service_id)
continue;
// Get the mapping item for account purposes
if ($iio->product_id) {
$edo = ORM::factory('Export_DataMap')
->where('module_id','=',$iio->product->mid())
->and_where('item_id','=',$iio->product_id)
->find();
if ($edo->loaded()) {
$items[$c]['ACCNT'] = $edo->map_data['account'];
$items[$c]['INVITEM'] = $edo->map_data['item'];
} else {
throw HTTP_Exception::factory(501,'Missing product map data for :product (:id)',array(':product'=>$iio->product->title(),':id'=>$iio->product_id));
}
$items[$c]['MEMO'] = sprintf('%s (%s)',$iio->product->title(),$iio->period());
// Non product item
} else {
$items[$c]['ACCNT'] = 'Other Income';
$items[$c]['INVITEM'] = 'Unknown';
$items[$c]['MEMO'] = $iio->period();
}
$items[$c]['CLEAR'] = 'N';
$items[$c]['QNTY'] = -1;
if ($iio->tax_items()) {
// @todo, need to figure out how multiple tax items are handled
if (count($iio->tax_items()) > 1)
throw HTTP_Exception(501,'Export cant handle multiple tax items yet');
foreach ($iio->tax_items() as $tid => $amount) {
$to = ORM::factory('Tax',$tid);
$items[$c]['TAXABLE'] = 'Y';
$items[$c]['TAXCODE'] = $to->description;
$items[$c]['TAXRATE'] = sprintf('%3.2f%%',$to->rate);
$items[$c]['TAXAMOUNT'] = sprintf('%3.2f',$amount*-1);
}
} else {
$items[$c]['TAXAMOUNT'] = 0;
}
// @todo This rounding should be a system config.
$items[$c]['PRICE'] = sprintf('%3.2f',round($iio->subtotal()-$iio->discount(),2));
$items[$c]['AMOUNT'] = sprintf('%3.2f',round($iio->subtotal()-$iio->discount(),2)*-1);
$c++;
}
// Add credits as a other item
if ($io->total_credits()) {
$items[$c]['ACCNT'] = 'Other Income';
$items[$c]['INVITEM'] = 'Product:Unknown';
$items[$c]['CLEAR'] = 'N';
$items[$c]['QNTY'] = 1;
$items[$c]['MEMO'] = 'Credit Item';
$items[$c]['TAXAMOUNT'] = 0;
$items[$c]['PRICE'] = sprintf('%3.2f',round(($io->total_credits()-$io->tax())*-1,2));
$items[$c]['AMOUNT'] = sprintf('%3.2f',round(($io->total_credits()-$io->tax()),2));
}
return $this->output(Arr::merge($defaults,$invoice),$items);
}
/**
* Export selected invoices
*/
private function invoices(array $ids) {
$output = '';
foreach ($ids as $id)
$output .= $this->invoice(ORM::factory('Invoice',$id));
// If all went OK, update our export status
$this->update($ids);
return $output;
}
/**
* Return exported data for download
*/
private function output(array $trans,array $items) {
$output = '';
$output .= "!TRNS\t";
$output .= implode("\t",array_keys($trans))."\n";
$output .= "TRNS\t";
$output .= implode("\t",array_values($trans))."\n";
$spl = 0;
foreach ($items as $detail) {
if (! $spl) {
$output .= "!SPL\tSPLID\t";
$output .= implode("\t",array_keys($detail))."\n";
}
$output .= sprintf("SPL\t%s\t%s\n",$spl++,implode("\t",array_values($detail)));
}
$output .= "ENDTRNS\n";
return $output;
}
/**
* Export a payment
*/
private function payment(Model_Payment $po) {
$defaults = array(
'CLEAR'=>'N',
'TRNSTYPE'=>'PAYMENT',
);
$payment = $items = array();
$payment['AMOUNT'] = sprintf('%3.2f',$po->total_amt);
$payment['TRNSID'] = sprintf('P%06s',$po->id);
$payment['DATE'] = date('m/d/Y',$po->date_payment);
$payment['NAME'] = $po->account->company ? $po->account->company : sprintf('%s %s',$po->account->last_name,$po->account->first_name);
$payment['MEMO'] = sprintf('Payment for invoice(s) %s (%s)',implode(':',$po->invoices()),$po->checkout->name);
// @todo Accounts/Payment should be configurable
switch ($po->checkout->plugin) {
// @todo this is direct debit
case 'DD_EZYPAY':
$payment['PAYMETH'] = 'DirectDebit';
$payment['ACCNT'] = 'Ezypay';
break;
case 'REMIT_CHEQUE':
$payment['PAYMETH'] = 'Cheque';
$payment['ACCNT'] = 'Undeposited Funds';
break;
case 'REMIT_BANK_WIRE':
$payment['PAYMETH'] = 'DirectCredit';
$payment['ACCNT'] = 'Bendigo Bank';
break;
case 'PAYPAL_CART':
$payment['PAYMETH'] = 'Paypal';
$payment['ACCNT'] = 'Paypal';
break;
default:
$payment['PAYMETH'] = 'TBA';
$payment['ACCNT'] = 'Undeposited Funds';
}
$items[0]['TRANSTYPE'] = 'PAYMENT';
$items[0]['CLEAR'] = 'N';
$items[0]['ACCNT'] = 'Accounts Receivable';
$items[0]['AMOUNT'] = $po->total();
return $this->output(Arr::merge($defaults,$payment),$items);
}
/**
* Export selected invoices
*/
private function payments(array $ids) {
$output = '';
foreach ($ids as $id)
$output .= $this->payment(ORM::factory('Payment',$id));
// If all went OK, update our export status
$this->update($ids);
return $output;
}
private function update(array $ids) {
foreach ($ids as $id) {
// Check if we have exported this item already
$eio = ORM::factory('Export_Item')
->where('item_id','=',$id)
->and_where('export_module_id','=',$this->emo->id)
->find();
if (! $eio->loaded()) {
$eio->item_id = $id;
$eio->export_module_id = $this->emo->id;
}
$eio->save();
}
}
}
?>

View File

@@ -1,217 +0,0 @@
<?php defined('SYSPATH') or die('No direct access allowed.');
/**
* This class provides OSB exporting capabilities for Quickbooks.
*
* @package Export
* @category Helpers
* @author Deon George
* @copyright (c) 2009-2013 Open Source Billing
* @license http://dev.osbill.net/license.html
*/
class Export_Quicken extends Export {
public function export() {
if (! empty($_POST['id']) AND count($_POST['id'])) {
$qo = new Quicken;
foreach ($_POST['id'] as $pid) {
$po = ORM::factory('Payment',$pid);
if ($po->loaded()) {
$invoice_ids = array();
foreach ($po->payment_item->find_all() as $pio) {
// If our invoice ID is not blank, then the payment was applied to an invoice
if ($pio->invoice->id) {
// Record our invoice IDs for the summary
array_push($invoice_ids,$pio->invoice->id);
$qio = new Quicken_Invoice;
$qio->TRNSID = sprintf('%06s',$pio->invoice->id);
$qio->DATE = date('m/d/Y',$pio->invoice->date_orig);
$qio->MEMO = 'Import from OSB';
$qio->CLEAR = 'N';
$qio->TOPRINT = 'N';
$qio->PAID = 'N';
$qio->ADDR1 = $po->account->address1;
$qio->ADDR2 = $po->account->address2;
$qio->ADDR3 = sprintf('%s, %s %s',$po->account->city,$po->account->state,$po->account->zip);
// @todo - should be configurable
$qio->TERMS = '7 Days';
// @todo - should be configurable
$qio->INVTITLE = Company::instance()->name().' Invoice';
// @todo - should be configurable
$qio->INVMEMO = 'Thank you for using '.Company::instance()->name();
$qio->DOCNUM = sprintf('%06s',$pio->invoice->id);
$qio->DUEDATE = date('m/d/Y',$pio->invoice->due_date);
$qio->AMOUNT = sprintf('%3.2f',$pio->invoice->total());
if ($po->account->company)
$qio->NAME = $po->account->company;
else
$qio->NAME = sprintf('%s %s',$po->account->last_name,$po->account->first_name);
// Other Quicken fields not used.
#$qio->CLASS = '';
#$qio->SHIPVIA = '';
#$qio->SHIPDATE = '';
#$qio->OTHER1 = '';
#$qio->REP = '';
#$qio->FOB = '';
#$qio->PONUM = '';
#$qio->SADDR1 = '';
#$qio->SADDR2 = '';
#$qio->SADDR3 = '';
#$qio->SADDR4 = '';
#$qio->SADDR5 = '';
// Add the items to the invoice
foreach ($pio->invoice->invoice_item->find_all() as $iio) {
$qto = new Quicken_InvoiceItem;
if ($iio->period())
$daterange = $iio->period();
// @todo This should go.
elseif ($iio->product_attr && preg_match('/^a/',$iio->product_attr)) {
echo 'Uncaptured';die();
// @todo This should go.
} elseif ($iio->product_attr && preg_match('/^s/',$iio->product_attr))
$daterange = preg_replace("/\r?\n/",' ',unserialize($iio->product_attr));
else
$daterange = '';
if ($iio->product_id) {
$mo = ORM::factory('Module',array('name'=>'product'));
$eo = ORM::factory('Export')
->where('plugin_name','=',strtolower($this->plugin))
->and_where('module_id','=',$mo->id)
->and_where('item_id','=',$iio->product_id)
->find();
if ($eo->loaded()) {
$qto->ACCNT = $eo->map_data['account'];
$qto->INVITEM = $eo->map_data['item'];
} else {
throw new Kohana_Exception('Missing product map data for :product (:id)',
array(':product'=>$iio->product->title(),':id'=>$iio->product_id));
$qto->ACCNT = 'Other Income';
$qto->INVITEM = 'Product:Unknown';
}
$qto->MEMO = sprintf('%s (%s)',
$iio->product->product_translate->find()->name,$daterange);
} else {
$qto->ACCNT = 'Other Income';
$qto->INVITEM = 'Unknown';
$qto->MEMO = sprintf('%s (%s)',
$iio->product->product_translate->find()->name,$daterange);
}
$qto->CLEAR = 'N';
$qto->QNTY = -1;
if ($pio->invoice->tax()) {
$qto->TAXABLE = 'Y';
# @todo, get this from OSB
$qto->TAXCODE = 'GST';
$qto->TAXRATE = sprintf('%3.2f%%','0.10');
$qto->TAXAMOUNT = sprintf('%3.2f',$iio->tax()*-1);
} else {
$qto->TAXAMOUNT = 0;
}
// @todo This rounding should be a system config.
$qto->PRICE = sprintf('%3.2f',round($iio->subtotal()-$iio->discount(),2));
$qto->AMOUNT = sprintf('%3.2f',round($iio->subtotal()-$iio->discount(),2)*-1);
$qio->addInvoiceItem($qto);
}
// Add credits as a other item
$qto = new Quicken_InvoiceItem;
$qto->ACCNT = 'Other Income';
$qto->INVITEM = 'Product:Unknown';
$qto->CLEAR = 'N';
$qto->QNTY = 1;
$qto->MEMO = 'Credit Item';
if ($pio->invoice->tax()) {
$qto->TAXABLE = 'Y';
# @todo, get this from OSB
$qto->TAXCODE = 'GST';
$qto->TAXRATE = sprintf('%3.2f%%','0.10');
$tax = round($pio->invoice->total_credits()/11,2);
$qto->TAXAMOUNT = sprintf('%3.2f',$tax);
} else {
$qto->TAXAMOUNT = 0;
}
$qto->PRICE = sprintf('%3.2f',round(($pio->invoice->total_credits()-$tax)*-1,2));
$qto->AMOUNT = sprintf('%3.2f',round(($pio->invoice->total_credits()-$tax),2));
$qio->addInvoiceItem($qto);
$qo->addInvoice($qio);
}
}
$qpo = new Quicken_Payment;
$qpo->AMOUNT = sprintf('%3.2f',$po->total_amt);
$qpo->TRNSID = sprintf('P%06s',$po->id);
$qpo->DATE = date('m/d/Y',$po->date_payment);
// @todo this should be from a function - when no invoice is paid we cant use $qio
if ($po->account->company)
$qpo->NAME = $po->account->company;
else
$qpo->NAME = sprintf('%s %s',$po->account->last_name,$po->account->first_name);
$qpo->CLEAR = 'N';
$qpo->MEMO = sprintf('Payment for invoice(s) %s (%s)',implode(':',$invoice_ids),$po->checkout->name);
// @todo Accounts/Payment should be configurable
switch ($po->checkout->plugin) {
// @todo this is direct debit
case 'DD_EZYPAY':
$qpo->PAYMETH = 'DirectDebit';
$qpo->ACCNT = 'Ezypay';
break;
case 'REMIT_CHEQUE':
$qpo->PAYMETH = 'Cheque';
$qpo->ACCNT = 'Undeposited Funds';
break;
case 'REMIT_BANK_WIRE':
$qpo->PAYMETH = 'DirectCredit';
$qpo->ACCNT = 'Bendigo Bank';
break;
case 'PAYPAL_CART':
$qpo->PAYMETH = 'Paypal';
$qpo->ACCNT = 'Paypal';
break;
default:
$qpo->PAYMETH = 'TBA';
$qpo->ACCNT = 'Undeposited Funds';
}
if (isset($qio))
$qio->addPayment($qpo);
}
}
}
if (! empty($qo))
$this->response->body($qo->export());
$this->response->send_file(TRUE,'quicken-import.iif',array('mime_type'=>'text/plain'));
}
}
?>