From 718c42be65b80e18a905474cff76f874de913c0b Mon Sep 17 00:00:00 2001 From: Deon George Date: Tue, 11 Oct 2011 19:52:31 +1100 Subject: [PATCH] Work on invoices, products and other minor things --- .../classes/controller/admin/welcome.php | 10 +- application/classes/currency.php | 3 +- application/classes/model/account.php | 4 + application/classes/period.php | 2 + .../classes/model/service/plugin/adsl.php | 2 +- .../classes/controller/task/invoice.php | 127 ++++++++- modules/invoice/classes/model/invoice.php | 252 +++++++++--------- .../invoice/classes/model/invoice/item.php | 49 ++-- modules/invoice/views/invoice/user/view.php | 111 +++----- .../classes/controller/admin/product.php | 46 ++++ modules/service/classes/model/service.php | 31 +-- modules/tax/classes/tax.php | 13 +- 12 files changed, 393 insertions(+), 257 deletions(-) create mode 100644 modules/product/classes/controller/admin/product.php diff --git a/application/classes/controller/admin/welcome.php b/application/classes/controller/admin/welcome.php index 9966a52c..4d418292 100644 --- a/application/classes/controller/admin/welcome.php +++ b/application/classes/controller/admin/welcome.php @@ -33,7 +33,7 @@ class Controller_Admin_Welcome extends Controller_TemplateDefault { 'account->name()'=>array('label'=>'Account'), 'account->display("status")'=>array('label'=>'Active'), 'id'=>array('label'=>'ID','url'=>'user/invoice/view/'), - 'total_amt'=>array('label'=>'Total','class'=>'right'), + 'total(TRUE)'=>array('label'=>'Total','class'=>'right'), 'due(TRUE)'=>array('label'=>'Amount Due','class'=>'right'), ), array('page'=>TRUE)), @@ -51,7 +51,7 @@ class Controller_Admin_Welcome extends Controller_TemplateDefault { 'account->name()'=>array('label'=>'Account'), 'account->display("status")'=>array('label'=>'Active'), 'id'=>array('label'=>'ID','url'=>'user/invoice/view/'), - 'total_amt'=>array('label'=>'Total','class'=>'right'), + 'total(TRUE)'=>array('label'=>'Total','class'=>'right'), 'due(TRUE)'=>array('label'=>'Amount Due','class'=>'right'), ), array('page'=>TRUE)), @@ -69,7 +69,7 @@ class Controller_Admin_Welcome extends Controller_TemplateDefault { 'account->name()'=>array('label'), 'account->display("status")'=>array('label'=>'Active'), 'id'=>array('label'=>'ID','url'=>'user/invoice/view/'), - 'total_amt'=>array('label'=>'Total','class'=>'right'), + 'total(TRUE)'=>array('label'=>'Total','class'=>'right'), 'due(TRUE)'=>array('label'=>'Amount Due','class'=>'right'), ), array('show_other'=>'due()')), @@ -78,12 +78,10 @@ class Controller_Admin_Welcome extends Controller_TemplateDefault { )); // Show un-applied payments - $o = ORM::factory('payment'); - Block_Sub::add(array( 'title'=>'Unapplied Payments', 'body'=>Table::display( - $o->list_unapplied(), + ORM::factory('payment')->list_unapplied(), 25, array( 'date_payment'=>array('label'=>'Pay Date'), diff --git a/application/classes/currency.php b/application/classes/currency.php index b3a65ad5..0850b702 100644 --- a/application/classes/currency.php +++ b/application/classes/currency.php @@ -12,7 +12,8 @@ class Currency { public static function display($amount) { // @todo $cid and therefore precision should come from a global session value. - return Num::format($amount,2,TRUE); + // @todo This rounding needs to be system configurable. + return Num::format(round($amount,2),2,TRUE); } } ?> diff --git a/application/classes/model/account.php b/application/classes/model/account.php index c2694bea..93e6bf79 100644 --- a/application/classes/model/account.php +++ b/application/classes/model/account.php @@ -110,6 +110,10 @@ class Model_Account extends Model_Auth_UserDefault { foreach ($this->invoices_due($date) as $io) $result += $io->due(); + // @todo This shouldnt really be required + if ($result < 0) + $result = 0; + return $format ? Currency::display($result) : $result; } diff --git a/application/classes/period.php b/application/classes/period.php index c5042bb1..129011f0 100644 --- a/application/classes/period.php +++ b/application/classes/period.php @@ -105,8 +105,10 @@ class Period { $return = array( 'start'=>$period_start, + 'start_time'=>$period_start, 'date'=>$start, 'end'=>$period_end, + 'end_time'=>$period_end, 'weekday'=>$weekday, 'prorata'=>round($remain_time/$total_time,$precision), 'total_time'=>sprintf('%3.1f',$total_time/86400), diff --git a/modules/adsl/classes/model/service/plugin/adsl.php b/modules/adsl/classes/model/service/plugin/adsl.php index c8f300dc..c21074aa 100644 --- a/modules/adsl/classes/model/service/plugin/adsl.php +++ b/modules/adsl/classes/model/service/plugin/adsl.php @@ -371,7 +371,7 @@ class Model_Service_Plugin_ADSL extends Model_Service_Plugin { switch ($type) { case 'invoice_detail_items': return array( - _('Service Address')=>$this->display('service_address'), + _('Service Address')=>$this->service_address ? $this->display('service_address') : '>NotSet<', _('Contract Until')=>$this->contract_date_end(), ); break; diff --git a/modules/invoice/classes/controller/task/invoice.php b/modules/invoice/classes/controller/task/invoice.php index 84488a69..03138772 100644 --- a/modules/invoice/classes/controller/task/invoice.php +++ b/modules/invoice/classes/controller/task/invoice.php @@ -11,6 +11,12 @@ * @license http://dev.osbill.net/license.html */ class Controller_Task_Invoice extends Controller_Task { + /** + * Email a list of invoice balances + * + * This function is typically used to list the overdue invoices to the admins + * @param string mode The callback method to use as the data list eg: overdue + */ public function action_list() { $mode = $this->request->param('id'); @@ -47,7 +53,11 @@ class Controller_Task_Invoice extends Controller_Task { $this->response->body($output); } + /** + * Email a customers a reminder of their upcoming invoices that are due. + */ public function action_remind_due() { + $action = array(); // @todo This should go in a config somewhere $days = 5; $io = ORM::factory('invoice'); @@ -72,14 +82,20 @@ class Controller_Task_Invoice extends Controller_Task { ); // @todo Record email log id if possible. - if ($et->send()) + if ($et->send()) { $io->set_remind($key,time()); + array_push($action,(string)$io); + } } - $this->response->body(_('Due Reminders Sent.')); + $this->response->body(_('Due Reminders Sent: ').join('|',$action)); } + /** + * Email a customers when their invoices are now overdue. + */ public function action_remind_overdue() { + $action = array(); $io = ORM::factory('invoice'); $notice = $this->request->param('id'); $x = NULL; @@ -133,11 +149,114 @@ class Controller_Task_Invoice extends Controller_Task { ); // @todo Record email log id if possible. - if ($et->send()) + if ($et->send()) { $io->set_remind($key,time()); + array_push($action,(string)$io); + } } - $this->response->body(_('Overdue Reminders Sent: ').$notice); + $this->response->body(_('Overdue Reminders Sent: ').join('|',$action)); + } + + /** + * Generate our services invoices, based on the service next invoice date + * + * @param int ID Service ID to generate invoice for (optional) + */ + public function action_serviceinvoices() { + $action = array(); + $snd = array(); // Our service next billing dates that need to be updated if this is successful. + $sid = $this->request->param('id'); + + // Sort our service by account_id, then we can generate 1 invoice. + $svs = ORM::factory('service')->list_invoicesoon(); + Sort::MAsort($svs,'account_id,date_next_invoice'); + + $aid = $due = $io = NULL; + foreach ($svs as $so) { + if (! is_null($sid) AND $sid != $so->id) + continue; + + // Close off invoice, and start a new one. + if (is_null($io) OR (is_null($aid) AND $aid != $so->account_id) OR (is_null($due) AND $due != $io->min_due($so->date_next_invoice))) { + // Close this invoice. + if (! is_null($io)) { + // Save our invoice. + if (! $io->save()) + throw new Kohana_Exception('Failed to save invoice :invoice for service :service',array(':invoice'=>$io->id,':service'=>$so->id)); + } + + // Start a new invoice. + $io = ORM::factory('invoice'); + $io->due_date = $due = $io->min_due($so->date_next_invoice); + $io->account_id = $aid = $so->account_id; + $io->status = TRUE; + } + + $ppa = $so->product->get_price_array(); + // @todo Need to check our recurr_weekday configuration for items that need to be pro-rated and items that are billed on absolute dates. + $pdata = Period::details($so->recur_schedule,$so->product->price_recurr_weekday,$so->date_next_invoice,TRUE); + $iio = $io->add_item(); + + $iio->service_id = $so->id; + $iio->product_id = $so->product_id; + $iio->quantity = 1; + $iio->item_type = 0; + $iio->discount_amt = null; // @todo + $iio->price_type = $so->price_type; // @todo Do we need this? + // @todo Might be a better way to do this + $iio->price_base = isset($ppa[$so->recur_schedule]['price_base']) ? $ppa[$so->recur_schedule]['price_base'] : 0; + $iio->recurring_schedule = $so->recur_schedule; + $iio->date_start = $pdata['start_time']; // @todo + $iio->date_stop = $pdata['end_time']; // @todo + + // Our service next billing date, if this invoice generation is successful. + $snd[$so->id] = $pdata['end_time']+86400; + + array_push($action,(string)$so->id); + } + + // Save our invoice. + if (! $io->save()) + throw new Kohana_Exception('Failed to save invoice :invoice for service :service',array(':invoice'=>$io->id,':service'=>$so->id)); + + // Update our service next billing dates. + foreach ($snd as $sid=>$date) { + $so = ORM::factory('service',$sid); + $so->date_next_invoice = $date; + $so->save(); + } + + $this->response->body(_('Services Invoiced: ').join('|',$action)); + } + + /** END **/ + + public function action_audit_invoice_items() { + $output = ''; + + foreach (ORM::factory('invoice_item')->find_all() as $iio) { + if ($iio->product_name AND $iio->product_id) { + if (md5(strtoupper($iio->product_name)) == md5(strtoupper($iio->product->name()))) { + $iio->product_name = null; + $iio->save(); + } else { + print_r(array("DIFF",'id'=>$iio->id,'pn'=>serialize($iio->product_name),'ppn'=>serialize($iio->product->name()),'pid'=>$iio->product_id,'test'=>strcasecmp($iio->product_name,$iio->product->name()))); + } + } + + #if ($iio->product->prod_plugin_file == 'HOST') { + # if ($iio->service->name() == strtoupper($iio->domain_name)) + # $iio->domain_name=null; + #} + #if ($iio->product->prod_plugin_file == 'ADSL') { + # if ($iio->service->name() == strtoupper($iio->domain_name)) + # $iio->domain_name=null; + # #print_r(array('pid'=>$iio->domain_name,'iio-service-name'=>$iio->service->name(),'iii-domain_name'=>$iio->domain_name)); + #} + } + + $this->response->body($output); } } ?> diff --git a/modules/invoice/classes/model/invoice.php b/modules/invoice/classes/model/invoice.php index f588400f..b8d6c1e8 100644 --- a/modules/invoice/classes/model/invoice.php +++ b/modules/invoice/classes/model/invoice.php @@ -11,8 +11,6 @@ * @license http://dev.osbill.net/license.html */ class Model_Invoice extends ORMOSB { - private $invoice_items = array(); - protected $_belongs_to = array( 'account'=>array() ); @@ -28,20 +26,6 @@ class Model_Invoice extends ORMOSB { 'id'=>'DESC', ); - /** - * @var array Filters to render values properly - */ - protected $_filters = array( - // @todo This rounding should be a global configuration - 'total_amt'=>array('round'=>array('2')), - ); - - protected $_callbacks = array( - 'id'=>array('get_next_id'), - 'total_amt'=>array('calc_total'), - 'tax_amt'=>array('calc_tax'), - ); - protected $_display_filters = array( 'date_orig'=>array( array('Config::date',array(':value')), @@ -63,6 +47,9 @@ class Model_Invoice extends ORMOSB { ), ); + // Items belonging to an invoice + private $invoice_items = array(); + /** * Display the Invoice Number */ @@ -82,18 +69,20 @@ class Model_Invoice extends ORMOSB { */ public function due($format=FALSE) { // If the invoice is active calculate the due amount - $result = $this->status ? round($this->total_amt-$this->credit_amt-$this->billed_amt,Kohana::config('config.currency_format')) : 0; + $result = $this->status ? round($this->total()-$this->payments_total(),2) : 0; return $format ? Currency::display($result) : $result; } /** * Return a list of invoice items for this invoice. - * - * We only return the items, if the invoice hasnt been changed. */ public function items() { - return ($this->loaded() AND ! $this->_changed) ? $this->invoice_item->order_by('service_id,item_type,module_id')->find_all() : NULL; + // If we havent been changed, we'll load the records from the DB. + if ($this->loaded() AND ! $this->_changed) + return $this->invoice_item->order_by('service_id,item_type,module_id')->find_all()->as_array(); + else + return $this->invoice_items; } /** @@ -102,8 +91,19 @@ class Model_Invoice extends ORMOSB { public function subtotal($format=FALSE) { $result = 0; + // @todo This rounding should be a system config. foreach ($this->items() as $ito) - $result += $ito->subtotal(); + $result += round($ito->subtotal(),2); + + return $format ? Currency::display($result) : $result; + } + + public function discount($format=FALSE) { + $result = 0; + + // @todo This rounding should be a system config. + foreach ($this->items() as $ito) + $result += round($ito->discount_amt,2); return $format ? Currency::display($result) : $result; } @@ -111,12 +111,9 @@ class Model_Invoice extends ORMOSB { public function tax($format=FALSE) { $result = 0; + // @todo This rounding should be a system config. foreach ($this->items() as $ito) - $result += $ito->tax(); - - // @todo This should eventually be removed. - if (! $result AND $this->tax_amt) - $result = $this->tax_amt; + $result += round($ito->tax(),2); return $format ? Currency::display($result) : $result; } @@ -127,11 +124,12 @@ class Model_Invoice extends ORMOSB { public function total($format=FALSE) { $result = 0; + // @todo This rounding should be a system config. foreach ($this->items() as $ito) - $result += $ito->total(); + $result += round($ito->total(),2); // Reduce by any credits - $result -= $this->credit_amt; + $result -= round($this->credit_amt,2); return $format ? Currency::display($result) : $result; } @@ -150,31 +148,75 @@ class Model_Invoice extends ORMOSB { } /** - * Return a list of our main invoice items (item_id=0) + * Get a list of services on an invoice + * + * We use this to list details by service on an invoice. */ - public function items_main() { - return ($this->loaded() AND ! $this->_changed) ? $this->invoice_item->where('item_type','=',0)->find_all() : NULL; + public function items_services(array $items=array()) { + $result = array(); + if (! $items) + $items = $this->items(); + + foreach ($items as $ito) + if ($ito->service_id AND empty($result[$ito->service_id])) + $result[$ito->service_id] = $ito->service; + + return $result; } /** - * Return a list of our sub invoice items (item_id!=0) + * Return all invoice items for a service optionally by recurring schedule + */ + public function items_service($sid,$rs=NULL) { + $result = array(); + $items = $this->items(); + + Sort::MAsort($items,'item_type'); + foreach ($items as $ito) + if ($ito->service_id == $sid AND (is_null($rs) OR $ito->recurring_schedule == $rs)) + array_push($result,$ito); + + return $result; + } + + /** + * Return a list of periods and services * - * @param sid int Service ID + * This is so that we can list items summarised by billing period */ - public function items_sub($sid) { - return ($this->loaded() AND ! $this->_changed) ? $this->invoice_item->where('service_id','=',$sid)->and_where('item_type','<>',0)->find_all() : NULL; + public function items_service_periods() { + $result = array(); + + $c = array(); + foreach ($this->items() as $ito) + if ($ito->service_id) { + // If we have already covered a service with no recurring_schedule + if (! $ito->recurring_schedule AND in_array($ito->service_id,$c)) + continue; + + array_push($c,$ito->service_id); + + $result[$ito->recurring_schedule][] = $ito; + } + + return $result; } /** * Summarise the items on an invoice + * + * We summaries based on product. */ public function items_summary() { $result = array(); - foreach ($this->items_main() as $ito) { - $unique = TRUE; + foreach ($this->items() as $ito) { + // We conly summaries item_type=0 + if (! $ito->item_type == 0) + continue; + + $t = $ito->product_id; - $t = $ito->product->name(); if (! isset($result[$t])) { $result[$t]['quantity'] = 0; $result[$t]['subtotal'] = 0; @@ -187,22 +229,13 @@ class Model_Invoice extends ORMOSB { return $result; } - /** - * Find all the invoice items relating to a service - * - * @param int Service ID - */ - private function list_items_service($sid) { - return $this->invoice_item->where('service_id','=',$sid)->find_all(); - } - /** * Calculate the total for items for a service */ public function items_service_total($sid) { $total = 0; - foreach ($this->list_items_service($sid) as $ito) + foreach ($this->items_service($sid) as $ito) $total += $ito->total(); return $total; @@ -210,41 +243,26 @@ class Model_Invoice extends ORMOSB { /** * Calculate the tax of items for a service - * @todo This can be optimised */ public function items_service_tax($sid) { $total = 0; - foreach ($this->list_items_service($sid) as $ito) - $total += $ito->tax_amt; + foreach ($this->items_service($sid) as $ito) + $total += $ito->tax(); return $total; } - // @todo Add discounts - /** - * Return a list of items based on a sort criteria + * Calculate the discounts of items for a service */ - public function sorted_service_items($index) { - $summary = array(); + public function items_service_discount($sid) { + $total = 0; - foreach ($this->items() as $ito) { - $key = $ito->service->$index; + foreach ($this->items_service($sid) as $ito) + $total += $ito->discount(); - if (! isset($summary[$key]['items'])) { - $summary[$key]['items'] = array(); - $summary[$key]['total'] = 0; - } - - // Only record items with item_type=0 - if ($ito->item_type == 0) - array_push($summary[$key]['items'],$ito); - - $summary[$key]['total'] += $ito->total(); - } - - return $summary; + return $total; } /** @@ -264,8 +282,8 @@ class Model_Invoice extends ORMOSB { } // @todo This should be removed eventually - if (! $summary AND $this->tax_amt) - $summary[1] = $this->tax_amt; + if (! $summary) + $summary[1] = $this->tax(); return $summary; } @@ -274,6 +292,9 @@ class Model_Invoice extends ORMOSB { * Add an item to an invoice */ public function add_item() { + if ($this->loaded() and ! $this->invoice_items) + throw new Kohana_Exception('Need to load invoice_items?'); + $c = count($this->invoice_items); $this->invoice_items[$c] = ORM::factory('invoice_item'); @@ -281,26 +302,31 @@ class Model_Invoice extends ORMOSB { return $this->invoice_items[$c]; } + public function min_due($date) { + // @todo This should be configurable; + return ($date < time()) ? time() : $date; + } + public function save(Validation $validation = NULL) { + // Our items will be clobbered once we save the object, so we need to save it here. + $items = $this->items(); + // Save the invoice parent::save($validation); // Need to save the associated items and their taxes if ($this->saved()) { - $tax_amt = 0; - $discount_amt = 0; + foreach ($items as $iio) { + $iio->invoice_id = $this->id; - foreach ($this->items() as $invoice_item) { - $invoice_item->invoice_id = $this->id; - - if (! $invoice_item->check()) { + if (! $iio->check()) { // @todo Mark invoice as cancelled and write a memo, then... throw new Kohana_Exception('Problem saving invoice_item for invoice :invoice - Failed check()',array(':invoice'=>$invoice->id)); } - $invoice_item->save(); + $iio->save(); - if (! $invoice_item->saved()) { + if (! $iio->saved()) { // @todo Mark invoice as cancelled and write a memo, then... throw new Kohana_Exception('Problem saving invoice_item for invoice :invoice - Failed save()',array(':invoice'=>$invoice->id)); } @@ -313,28 +339,6 @@ class Model_Invoice extends ORMOSB { throw new Kohana_Exception('Couldnt save invoice for some reason?'); } - public function calc_total(Validate $array, $field) { - $array[$field] = 0; - - // @todo Rounding here should come from a global config - foreach ($this->invoice_items as $ito) - $array[$field] += round($ito->price_base+$ito->price_setup,2); - - $this->_changed[$field] = $field; - } - - public function calc_tax(Validate $array, $field) { - $array[$field] = 0; - - // @todo Rounding here should come from a global config - // @todo tax should be evaluated per item - // @todo tax parameters should come from user session - foreach ($this->invoice_items as $ito) - $array[$field] += round(Tax::total(61,NULL,$ito->price_base+$ito->price_setup),2); - - $this->_changed[$field] = $field; - } - /** * Check the reminder value */ @@ -386,23 +390,30 @@ class Model_Invoice extends ORMOSB { /** LIST FUNCTIONS **/ private function _list_due() { - // @todo This rounding should be a system configuration - return $this->where('round(total_amt-ifnull(credit_amt,0),2)','>','=billed_amt') - ->and_where('status','=',1) - ->order_by('due_date,account_id,id'); + static $result = array(); + + if (! $result) + foreach (ORM::factory('invoice')->where('status','=',1)->find_all() as $io) + if ($io->due()) + array_push($result,$io); + + return $result; } /** * Identify all the invoices that are due */ public function list_overdue($time=NULL) { + $result = array(); + if (is_null($time)) $time = time(); - // @todo This rounding should be a system configuration - return $this->_list_due() - ->and_where('due_date','<=',$time) - ->find_all(); + foreach ($this->_list_due() as $io) + if ($io->due_date <= $time) + array_push($result,$io); + + return $result; } /** @@ -428,15 +439,16 @@ class Model_Invoice extends ORMOSB { * Return a list of invoices that are due, excluding overdue. */ public function list_due($time=NULL) { - if (is_null($time)) - return $this->_list_due() - ->and_where('due_date','>',time()) - ->find_all(); - else - return $this->_list_due() - ->and_where('due_date','<=',$time) - ->and_where('due_date','>',time()) - ->find_all(); + $result = array(); + + foreach ($this->_list_due() as $io) + if ($io->due_date > time()) + if (is_null($time)) + array_push($result,$io); + elseif ($this->due_date <= $time) + array_push($result,$io); + + return $result; } } ?> diff --git a/modules/invoice/classes/model/invoice/item.php b/modules/invoice/classes/model/invoice/item.php index 55cd8a32..3b2b9c1d 100644 --- a/modules/invoice/classes/model/invoice/item.php +++ b/modules/invoice/classes/model/invoice/item.php @@ -32,7 +32,6 @@ class Model_Invoice_Item extends ORMOSB { ), ); - // Display a transaction number public function trannum() { return sprintf('%03s-%06s',$this->item_type,$this->id); @@ -41,7 +40,7 @@ class Model_Invoice_Item extends ORMOSB { // Display the period that a transaction applies public function period() { if ($this->date_start == $this->date_stop) - return sprintf('%s: %s',_('Date'),Config::date($this->date_start)); + return Config::date($this->date_start); else return sprintf('%s -> %s',Config::date($this->date_start),Config::date($this->date_stop)); @@ -49,17 +48,21 @@ class Model_Invoice_Item extends ORMOSB { // Sum up the tax that applies to this invoice item public function tax() { - $amount = 0; + $result = 0; foreach ($this->invoice_item_tax->find_all() as $iit) - $amount += $iit->amount; + $result += $iit->amount; - return $amount; + // @todo This shouldnt be required. + if (! $result) + $result += round($this->subtotal() *.1,2); + + return $result; } // This total of this item before discounts and taxes public function subtotal() { - return ($this->price_base)*$this->quantity; + return $this->price_base*$this->quantity; } // The total of all discounts @@ -72,6 +75,15 @@ class Model_Invoice_Item extends ORMOSB { return round($this->subtotal()+$this->tax()-$this->discount(),2); } + public function name() { + return $this->product_name ? $this->product_name : ($this->item_type == 0 ? _('Service') : _('Other')); + } + + public function detail() { + return ($this->item_type == 0 OR $this->quantity == 1) ? HTML::nbsp('') : sprintf('%s@%3.2f',$this->quantity,$this->price_base); + } + + // @todo This might not be required. public function invoice_detail_items() { if ($this->item_type != 0) return; @@ -81,36 +93,29 @@ class Model_Invoice_Item extends ORMOSB { public function save(Validation $validation = NULL) { // Save the invoice item - parent::save(); + parent::save($validation); // Need to save the taxes and discounts associated with the invoice_item if ($this->saved()) { - $invoice_item_tax = ORM::factory('invoice_item_tax'); - $tax_total = 0; + $iito = ORM::factory('invoice_item_tax'); // Save TAX details // @todo tax parameters should come from user session foreach (Tax::detail(61,NULL,$this->subtotal()) as $tax) { - $invoice_item_tax->clear(); - $invoice_item_tax->invoice_item_id = $this->id; + $iito->clear(); + $iito->invoice_item_id = $this->id; + $iito->tax_id = $tax['id']; // @todo Rounding here should come from a global config - $tax_total += ($invoice_item_tax->amount = round($tax['amount'],2)); - $invoice_item_tax->tax_id = $tax['id']; + $iito->amount = round($tax['amount'],2); - if (! $invoice_item_tax->check()) + if (! $iito->check()) throw new Kohana_Exception('Couldnt save tax for some reason - failed check()?'); - $invoice_item_tax->save(); + $iito->save(); - if (! $invoice_item_tax->saved()) + if (! $iito->saved()) throw new Kohana_Exception('Couldnt save tax for some reason - failed save()?'); } - - // Save DISCOUNT details - // @todo calculate discounts - - parent::save(); - } else throw new Kohana_Exception('Couldnt save invoice_item for some reason?'); } diff --git a/modules/invoice/views/invoice/user/view.php b/modules/invoice/views/invoice/user/view.php index b4126e3b..ac9eeff2 100644 --- a/modules/invoice/views/invoice/user/view.php +++ b/modules/invoice/views/invoice/user/view.php @@ -26,11 +26,11 @@ Current Charges Due - display('total_amt'); ?> + total(TRUE); ?> Payments Received to Date - display('billed_amt'); ?> + payments_total(TRUE); ?> Total Charges Due @@ -46,94 +46,61 @@ Charges Detail: - sorted_service_items('recur_schedule') as $cat => $catitems) { ?> - + items_service_periods() as $rs => $items) { ?> -
uri(array('file'=>'img/toggle-closed.png')),array('alt'=>'+')); ?>
- - - - +
uri(array('file'=>'img/toggle-closed.png')),array('alt'=>'+')); ?>
+ + + - - Other Items - - + Other Items +   +   -
+
- - + + items_services($items) as $sid) { ?> + - - - + + + + items_service($sid) as $ito) { ?> - - - + + + + + + - - items_sub($item->service_id) as $subitem) { - if (! is_null($subitem->module_id)) { - $m = StaticList_Module::record('module','name','id',$subitem->module_id); - // @todo Need to remove the explicit test for 'charge' and be more dynamic - $mi = ORM::factory($m,$m == 'charge' ? $subitem->charge_id : $subitem->id); - $display = $mi->details('invoice'); - } else { - $display = 'Other'; - } - ?> + discount_amt) { ?> - - - - + + + - - - + - + - + - - - items_sub(NULL) as $subitem) { - if (! is_null($subitem->module_id)) { - $m = StaticList_Module::record('module','name','id',$subitem->module_id); - // @todo Need to remove the explicit test for 'charge' and be more dynamic - $mi = ORM::factory($m,$m == 'charge' ? $subitem->charge_id : $subitem->id); - $display = $mi->details('invoice'); - } else { - $display = 'Other'; - } - ?> - - - - - - - -
service->id,$item->service->id()); ?>product_name ? $item->product_name : $item->product->product_translate->find()->name; ?> (product_id; ?>)items_service_total($item->service_id));?>id,$so->id()); ?>service_name(); ?> (product_id; ?>)items_service_total($so->id));?>
 trannum();?>period();?>subtotal());?>trannum();?>name();?>detail();?>period();?>subtotal());?> 
 trannum(); ?>subtotal());?> (items_service_discount($so->id));?>)
   tax(TRUE);?>items_service_tax($so->id));?> 
 trannum(); ?>subtotal());?>
@@ -141,9 +108,15 @@ - Sub Total: - subtotal(TRUE); ?> + Sub Total of Items: + subtotal(TRUE); ?>  + discount()) { ?> + + Discounts: + (discount(TRUE); ?>) + + Taxes Included: @@ -153,14 +126,14 @@ ?>   - description; ?> - + description; ?> +   Total: - total(TRUE); ?> + total(TRUE); ?>  diff --git a/modules/product/classes/controller/admin/product.php b/modules/product/classes/controller/admin/product.php new file mode 100644 index 00000000..79586cce --- /dev/null +++ b/modules/product/classes/controller/admin/product.php @@ -0,0 +1,46 @@ +TRUE, + ); + + /** + * Show a list of services + */ + public function action_list() { + Block::add(array( + 'title'=>_('Customer Products'), + 'body'=>Table::display( + ORM::factory('product')->order_by('prod_plugin_file')->find_all(), + 25, + array( + 'id'=>array('label'=>'ID','url'=>'user/product/view/'), + 'name()'=>array('label'=>'Details'), + 'active'=>array('label'=>'Active'), + 'prod_plugin'=>array('label'=>'Plugin'), + 'prod_plugin_file'=>array('label'=>'Plugin Name'), + 'prod_plugin_data'=>array('label'=>'Plugin Data'), + 'price_type'=>array('label'=>'Price Type'), + 'price_base'=>array('label'=>'Price Base'), + 'taxable'=>array('label'=>'Taxable'), + ), + array( + 'page'=>TRUE, + 'type'=>'select', + 'form'=>'user/product/view', + )), + )); + } +} +?> diff --git a/modules/service/classes/model/service.php b/modules/service/classes/model/service.php index 216bf505..7dcb2b99 100644 --- a/modules/service/classes/model/service.php +++ b/modules/service/classes/model/service.php @@ -42,33 +42,6 @@ class Model_Service extends ORMOSB { ), ); - /** - * The service_name should be implemented in child objects. - * It renders the name of the service, typically used on invoice - */ - protected function _service_name() { - throw new Kohana_Exception(':method not defined in child class :class',array(':method'=>__METHOD__,':class'=>get_class($this))); - } - - /** - * The service_view should be implemented in child objects. - * It renders the details of the ordered service - */ - protected function _service_view() { - throw new Kohana_Exception(':method not defined in child class :class',array(':method'=>__METHOD__,':class'=>get_class($this))); - } - - /** - * The _details should be implemented in child objects. - */ - protected function _details($type) { - throw new Kohana_Exception(':method not defined in child class :class',array(':method'=>__METHOD__,':class'=>get_class($this))); - } - - protected function _admin_update() { - throw new Kohana_Exception(':method not defined in child class :class',array(':method'=>__METHOD__,':class'=>get_class($this))); - } - /** * Return the object of the product plugin */ @@ -143,6 +116,8 @@ class Model_Service extends ORMOSB { return $this->price * .1; } + /** LIST FUNCTIONS **/ + public function list_active() { return $this->where('active','=','1')->find_all(); } @@ -171,7 +146,7 @@ class Model_Service extends ORMOSB { $result = array(); foreach ($this->list_active() as $so) - // @todo This should be configurable + // @todo This should be configurable (days) if (! $so->suspend_billing AND $so->date_next_invoice < time()+35*86400) array_push($result,$so); diff --git a/modules/tax/classes/tax.php b/modules/tax/classes/tax.php index e14b83f8..62699faa 100644 --- a/modules/tax/classes/tax.php +++ b/modules/tax/classes/tax.php @@ -22,16 +22,17 @@ class Tax { public static function detail($cid,$zone,$value=0) { $tax = ORM::factory('tax') ->where('country_id','=',$cid) - ->and_where('zone','=',$zone); + ->and_where('zone','=',$zone) + ->find_all(); $taxes = array(); - foreach ($tax->find_all() as $item) { + foreach ($tax as $to) { $total = array(); - $total['id'] = $item->id; - $total['description'] = $item->description; - $total['amount'] = $item->rate*$value; - $total['rate'] = $item->rate; + $total['id'] = $to->id; + $total['description'] = $to->description; + $total['amount'] = $to->rate*$value; + $total['rate'] = $to->rate; array_push($taxes,$total); }