diff --git a/modules/invoice/classes/Invoice.php b/modules/invoice/classes/Invoice.php index f8d0bfb8..f3cb91f9 100644 --- a/modules/invoice/classes/Invoice.php +++ b/modules/invoice/classes/Invoice.php @@ -59,8 +59,7 @@ class Invoice { $iio = ORM::factory('Invoice_Item'); $iio->service_id = $so->id; - $iio->module_id = $so->product->mid(); - $iio->module_ref = $so->product_id; + $iio->product_id = $so->product->id; $iio->quantity = $pdata['prorata']; $iio->price_base = $so->price(); $iio->recurring_schedule = $so->recur_schedule; @@ -81,6 +80,7 @@ class Invoice { foreach ($c->find_all() as $co) { $iio = ORM::factory('Invoice_Item'); $iio->service_id = $co->service_id; + $iio->product_id = $co->service->product_id; $iio->module_id = $co->mid(); $iio->module_ref = $co->id; $iio->quantity = $co->quantity; @@ -174,6 +174,11 @@ class Invoice { case 'html': switch ($section) { case 'body': + if (! $this->_io->status) + Style::factory() + ->type('file') + ->data('media/css/pages/invoice.css'); + return View::factory('invoice/user/view/body') ->set('show_id',(isset($args['noid']) AND $args['noid']) ? FALSE : TRUE) ->set('o',$this->_io); diff --git a/modules/invoice/classes/Model/Invoice.php b/modules/invoice/classes/Model/Invoice.php index a4fa3356..162b2654 100644 --- a/modules/invoice/classes/Model/Invoice.php +++ b/modules/invoice/classes/Model/Invoice.php @@ -43,7 +43,7 @@ class Model_Invoice extends ORM_OSB implements Cartable { // Items belonging to an invoice protected $_sub_items_load = array( - 'invoice_item'=>array('service_id','item_type','date_start','date_stop'), + 'invoice_item'=>array('service_id','product_id','item_type','date_start','date_stop'), ); protected $_compress_column = array( @@ -67,12 +67,6 @@ class Model_Invoice extends ORM_OSB implements Cartable { return new Cart_Item(1,sprintf('Invoice: %s',$this->refnum()),$this->due()); } - public function add_sub_item(Model_Invoice_Item $iio,$taxed=FALSE) { - $iio->add_sub_item($this->account->country,$taxed); - - $this->subitem_add($iio); - } - /** * Return if this invoice is already in the cart */ @@ -80,7 +74,16 @@ class Model_Invoice extends ORM_OSB implements Cartable { return count(Cart::instance()->get($this->mid(),$this->id)); } - // Our local methods + /** LOCAL METHODS **/ + + /** + * Add items to the invoice while building it + */ + public function add_sub_item(Model_Invoice_Item $iio,$taxed=FALSE) { + $iio->add_sub_item($this->account->country,$taxed); + + $this->subitem_add($iio); + } /** * Return a list of valid checkout options for this invoice @@ -136,6 +139,66 @@ class Model_Invoice extends ORM_OSB implements Cartable { return array_keys($this->_render['SCHED']); } + /** + * Return an array of items to render, in appropriate order + */ + public function items_render() { + $result = $track = array(); + + // If the invoice hasnt been saved, then we'll need to assign some fake IDs to the items + $c = 0; + if (! $this->_loaded) + foreach ($this->_sub_items as $iio) + $iio->id = 'FAKE:'.$c++; + + // First work through our recurring schedule items + $result['s'] = array(); + foreach ($this->recur_schedules() as $rs) + foreach ($this->recur_schedule_services($rs) as $sid) + foreach ($this->service_products($sid) as $pid) { + // Get all the services with this recur schedule first, then get service charges with rs=NULL + foreach ($this->_sub_items as $iio) { + + // If this is not the recur schedule or service we are working with, skip it + if ($iio->service_id != $sid OR $iio->product_id != $pid OR $iio->recurring_schedule != $rs OR in_array($iio->id,$track)) + continue; + + // Ensure we dont process this item again + array_push($track,$iio->id); + + if (! $iio->void) + array_push($result['s'],$iio); + } + + // Get all the items for this service with a NULL rs + foreach ($this->_sub_items as $iio) { + // If this is not the recur schedule or service we are working with, skip it + if ($iio->service_id != $sid OR $iio->product_id != $pid OR ! is_null($iio->recurring_schedule) OR in_array($iio->id,$track)) + continue; + + // Ensure we dont process this item again + array_push($track,$iio->id); + + if (! $iio->void) + array_push($result['s'],$iio); + } + } + + // Next get the items we havent already got + $result['other'] = array(); + foreach ($this->_sub_items as $iio) + if (! in_array($iio->id,$track)) + array_push($result['other'],$iio); + + // Debug + #foreach ($result['s'] as $iio) + # echo Debug::vars(array('s'=>$iio->object())); + #foreach ($result['other'] as $iio) + # echo Debug::vars(array('o'=>$iio->object())); + + return $result; + } + /** * Return a summary of all itemss on an invoice */ @@ -230,6 +293,38 @@ class Model_Invoice extends ORM_OSB implements Cartable { } } + /** + * For a particular recurring schedule, get al lthe services + */ + private function recur_schedule_services($rs) { + $result = array(); + + if (! $rs) + return $result; + + foreach ($this->_sub_items as $o) + if ($o->service_id AND $o->recurring_schedule == $rs AND ! in_array($o->service_id,$result)) + array_push($result,$o->service_id); + + return $result; + } + + /** + * Get the recurring schedules on an invoice + * Exclude any NULL recurring schedules + */ + private function recur_schedules() { + $result = array(); + + foreach ($this->_sub_items as $o) + if (! is_null($o->recurring_schedule) AND ! in_array($o->recurring_schedule,$result)) + array_push($result,$o->recurring_schedule); + + sort($result); + + return $result; + } + /** * Check the reminder value */ @@ -389,6 +484,29 @@ class Model_Invoice extends ORM_OSB implements Cartable { return $format ? Currency::display($result) : $result; } + /** + * For a particular service, get all the products + */ + private function service_products($sid) { + $result = array(); + + foreach ($this->_sub_items as $o) + if ($o->product_id AND $o->service_id == $sid AND ! in_array($o->product_id,$result)) + array_push($result,$o->product_id); + + return $result; + } + + public function service_products_total($sid,$pid,$format=FALSE) { + $result = 0; + + foreach ($this->_sub_items as $o) + if ($o->service_id == $sid AND $o->product_id == $pid) + $result += $o->total(); + + return $format ? Currency::display($result) : $result; + } + public function set_remind($key,$value,$add=FALSE) { $x = $this->reminders; diff --git a/modules/invoice/classes/Model/Invoice/Item.php b/modules/invoice/classes/Model/Invoice/Item.php index 2bedf46c..6b8751be 100644 --- a/modules/invoice/classes/Model/Invoice/Item.php +++ b/modules/invoice/classes/Model/Invoice/Item.php @@ -13,8 +13,11 @@ class Model_Invoice_Item extends ORM_OSB { // Relationships protected $_belongs_to = array( 'invoice'=>array(), - 'service'=>array() + 'module'=>array(), + 'product'=>array(), + 'service'=>array(), ); + protected $_has_many = array( 'tax'=>array('model'=>'Invoice_Item_Tax','far_key'=>'id') ); @@ -29,6 +32,9 @@ class Model_Invoice_Item extends ORM_OSB { 'date_stop'=>array( array('Site::Date',array(':value')), ), + 'recurring_schedule'=>array( + array('StaticList_RecurSchedule::get',array(':value')), + ), ); // Items belonging to an invoice item @@ -38,40 +44,81 @@ class Model_Invoice_Item extends ORM_OSB { /** * Invoice Item Types + * + * @see isValid() */ private $_item_type = array( - 0=>'Product/Service Charge', // Line Charge Topic on Invoice, eg: Service Name, must have corresponding SERVICE_ID - 1=>'Hardware', - 2=>'Service Relocation Fee', // Must have corresponding SERVICE_ID - 3=>'Service Change Fee', // Must have corresponding SERVICE_ID - 4=>'Service Connection Fee', // Must have corresponding SERVICE_ID - 5=>'Excess Usage', // Excess Service Item, of item 0, must have corresponding SERVICE_ID - 6=>'Service Cancellation Fee', // Must have corresponding SERVICE_ID - 7=>'Extra Product/Service Charge', // Service Billing in advance, Must have corresponding SERVICE_ID - 8=>'Product Addition', // Additional Product Customisation, Must have corresponding SERVICE_ID - 9=>'Module Charge', // Must have corresponding SERVICE_ID - 120=>'Credit/Debit Transfer', // SERVICE_ID is NULL, MODULE_ID is NULL, MODULE_REF is NULL : INVOICE_ID is NOT NULL - 124=>'Late Payment Fee', // SERVICE_ID is NULL, MODULE_ID = CHECKOUT MODULE, - 125=>'Payment Fee', // SERVICE_ID is NULL, MODULE_ID = CHECKOUT MODULE, MODULE_REF = CHECKOUT NAME - 126=>'Other', // MODEL_ID should be a module - 127=>'Rounding', // SERVICE_ID is NULL, MODULE_ID is NULL, MODULE_REF is NULL + 0=>'Product/Service', // * Line Charge Topic on Invoice. + 1=>'Hardware', // * + 2=>'Service Relocation Fee', // * Must have corresponding SERVICE_ID + 3=>'Service Change Fee', // * Must have corresponding SERVICE_ID + 4=>'Service Connection Fee', // * Must have corresponding SERVICE_ID + 5=>'Excess Usage', // * Excess Service Item, of item 0, must have corresponding SERVICE_ID + 6=>'Service Cancellation Fee', // * Must have corresponding SERVICE_ID + 7=>'Extra Product/Service Charge', // * Service Billing in advance, Must have corresponding SERVICE_ID + 8=>'Product Addition', // * Additional Product Customisation, Must have corresponding SERVICE_ID + 120=>'Credit/Debit Transfer', // * SERVICE_ID is NULL, MODULE_ID is NULL, MODULE_REF is NULL : INVOICE_ID is NOT NULL + 124=>'Late Payment Fee', // * SERVICE_ID is NULL, MODULE_ID = CHECKOUT MODULE, + 125=>'Payment Fee', // * SERVICE_ID is NULL, MODULE_ID = CHECKOUT MODULE, MODULE_REF = CHECKOUT NAME + 126=>'Other', // * MODEL_ID should be a module + 127=>'Rounding', // * SERVICE_ID is NULL, MODULE_ID is NULL, MODULE_REF is NULL ); /** REQUIRED ABSTRACT METHODS **/ public function name($variable=NULL) { + if (! $this->isValid()) + return sprintf('Record Error [%s-%s]',$this->item_type,$this->id); + switch ($this->item_type) { + case 0: + case 3: + case 4: + case 5: + return sprintf('%s: %s',$this->product->name($variable),$this->service->namesub($variable)); default: - return sprintf('Unknown [%s]',$this->id); + return sprintf('Unknown [%s-%s]',$this->item_type,$this->id); } } - public function titleline() { - return in_array($this->item_type,[0,120,124,125,126,127]); + public function namesub($variable=NULL) { + if (! $this->isValid()) + return sprintf('Record Error [%s-%s]',$this->item_type,$this->id); + + switch ($this->item_type) { + case 0: + return sprintf('%s: %s',StaticList_ItemType::get($this->item_type),$this->period()); + case 3: + case 4: + case 5: + return $this->_module()->namesub($variable); + default: + return sprintf('Unknown [%s-%s]',$this->item_type,$this->id); + } + } + + /** + * Display the Invoice Item Reference Number + */ + public function refnum($short=FALSE) { + return $short ? '' : sprintf('%03s-',$this->item_type).sprintf('%06s',$this->id); } /** LOCAL METHODS **/ + /** + * Module assocated with the invoice item + */ + public function _module() { + if (! $this->module_id OR ! $this->module_ref) + return NULL; + + return $this->module->instance($this->module_ref); + } + + /** + * Return the list of available item types + */ public function _types() { return $this->_item_type; } @@ -110,6 +157,33 @@ class Model_Invoice_Item extends ORM_OSB { return Currency::round($this->discount_amt); } + /** + * Validate that this record is correct + */ + private function isValid() { + switch ($this->item_type) { + case 0: + // @todo Validate if module_id = charge and module_ref corresponds with the line item + if (! $this->service_id OR ! $this->product_id OR ($this->module_id AND $this->module_id != 30 AND ! is_null($this>module_id)) OR $this->product_name OR is_null($this->recurring_schedule)) + return FALSE; + break; + // These items come from the charge module, so we should have a charge module_id and ref + case 3: + case 4: + if (! $this->service_id OR ! $this->product_id OR ! $this->module_id OR ! $this->module_ref OR $this->product_name OR ! is_null($this->recurring_schedule)) + return FALSE; + break; + // These items come from the charge module, so we should have a charge module_id and ref + case 5: + if (! $this->service_id OR ! $this->product_id OR ! $this->module_id OR ! $this->module_ref OR $this->product_name OR ! is_null($this->recurring_schedule)) + return FALSE; + break; + } + + // If we get here, assume it is valid + return TRUE; + } + /** * The line that will be printed on an invoice * @@ -159,15 +233,10 @@ class Model_Invoice_Item extends ORM_OSB { /** * Return an instance of the Model of this charge */ + public function module() { - $x = ORM::factory('Module',$this->module_id); - - if (! $x->loaded()) - throw new Kohana_Exception('Module :module doesnt exist?',array(':module'=>$this->module_id)); - - return ORM::factory('Module',$this->module_id)->instance($this->module_ref); + return $this->_module(); } - // Display the period that a transaction applies public function period() { return ($this->date_start == $this->date_stop) ? Site::Date($this->date_start) : sprintf('%s -> %s',Site::Date($this->date_start),Site::Date($this->date_stop)); @@ -206,7 +275,7 @@ class Model_Invoice_Item extends ORM_OSB { // This total of this item before discounts and taxes public function subtotal($format=FALSE) { - $result = $this->price_base*$this->quantity; + $result = Currency::round($this->price_base*$this->quantity); return $format ? Currency::display($result) : $result; } diff --git a/modules/invoice/media/css/pages/invoice.css b/modules/invoice/media/css/pages/invoice.css index dd6cec00..2c030c7b 100644 --- a/modules/invoice/media/css/pages/invoice.css +++ b/modules/invoice/media/css/pages/invoice.css @@ -1,4 +1,4 @@ -#watermark { +#cancelled { color: #800000; font-size: 4em; -webkit-transform: rotate(-45deg); diff --git a/modules/invoice/views/invoice/user/view.php b/modules/invoice/views/invoice/user/view.php index 89a953c2..157c53b2 100644 --- a/modules/invoice/views/invoice/user/view.php +++ b/modules/invoice/views/invoice/user/view.php @@ -11,13 +11,8 @@ if ($o->due() AND ! $o->cart_exists()) ->set('mid',$o->mid()) ->set('o',$o); -if (! $o->status) { - Style::factory() - ->type('file') - ->data('media/css/pages/invoice.css'); - - $output .= '
Invoice CANCELLED.
'; -} +if (! $o->status) + $output .= '
Invoice CANCELLED.
'; echo Block::factory() ->title(sprintf('%s: %s - %s',_('Invoice'),$o->refnum(),$o->account->name())) diff --git a/modules/invoice/views/invoice/user/view/body.php b/modules/invoice/views/invoice/user/view/body.php index c849fab6..7f239bb7 100644 --- a/modules/invoice/views/invoice/user/view/body.php +++ b/modules/invoice/views/invoice/user/view/body.php @@ -1,70 +1,72 @@ - items_periods() as $rs) : ?> - - - +items_render() as $key => $items) : + switch ($key) : + case 's': + $last = ''; + foreach ($items as $iio) : - - items_periods($rs) as $iio) : ?> - service_id) : ?> - - service_id,$sids)) continue; array_push($sids,$iio->service_id); ?> - - - - - - + // So we dont render the same recurring schedule over again + if (! is_null($iio->recurring_schedule) AND ! in_array($iio->recurring_schedule,$track['rs'])) : + array_push($track['rs'],$iio->recurring_schedule); +?> + + - service_items($iio)) : ?> - - - - - - - - - - +service_id != $last) { + $last = $iio->service_id; + $track['p'] = array(); + } - service_items_extra($iio)) : ?> - - - - - - - - - + // So we dont render the same recurring schedule over again + if (! in_array($iio->product_id,$track['p'])) : + array_push($track['p'],$iio->product_id); +?> + + + + + + + - - - - - - - + + + + + + + - - - - - + - items_periods(NULL)) : ?> - + case 'other': + if (! count($items)) + break; +?> - + - + + + + - +
 service_id),$iio->service->id()).' ' : '').$iio->title(); ?> 
service_items_total($iio,TRUE); ?>
display('recurring_schedule'); ?>
  invoice_line().($show_id ? " ({$eiio->trannum()})" : ''); ?>
total(TRUE); ?>
 
 invoice_line().($show_id ? " ({$eiio->trannum()})" : ''); ?>
total(TRUE); ?>
 
 service_id),$iio->service->refnum(TRUE)).' ' : '').$iio->name($iio->invoice->account->language); ?> 
service_products_total($iio->service_id,$iio->product_id,TRUE); ?>
 title(); ?> 
service_items_total($iio,TRUE); ?>
  namesub($iio->invoice->account->language).($show_id ? " ({$iio->refnum()})" : ''); ?>
total(TRUE); ?>
 
 invoice_line(); ?>name($iio->invoice->account->language); ?>
total(TRUE); ?>
Unknown key: []