2013-10-10 02:44:53 +00:00
< ? php defined ( 'SYSPATH' ) or die ( 'No direct access allowed.' );
/**
* This class provides invoice capabilities .
*
* @ package Invoice
* @ category Models
* @ author Deon George
* @ copyright ( c ) 2009 - 2013 Open Source Billing
* @ license http :// dev . osbill . net / license . html
*/
class Model_Invoice extends ORM_OSB implements Cartable {
protected $_belongs_to = array (
'account' => array ()
);
protected $_has_many = array (
'invoice_item' => array ( 'far_key' => 'id' ),
'invoice_item_tax' => array ( 'through' => 'invoice_item' ),
2013-10-11 02:08:50 +00:00
'invoice_memo' => array ( 'far_key' => 'id' ),
2013-10-10 02:44:53 +00:00
'service' => array ( 'through' => 'invoice_item' ),
'payment' => array ( 'through' => 'payment_item' ),
'payment_item' => array ( 'far_key' => 'id' ),
);
protected $_sorting = array (
'id' => 'DESC' ,
);
protected $_display_filters = array (
'date_orig' => array (
array ( 'Config::date' , array ( ':value' )),
),
'due_date' => array (
array ( 'Config::date' , array ( ':value' )),
),
'status' => array (
2013-11-14 11:50:35 +00:00
array ( 'StaticList_YesNo::get' , array ( ':value' , TRUE )),
2013-10-10 02:44:53 +00:00
),
);
// Items belonging to an invoice
2013-09-06 05:39:56 +00:00
protected $_sub_items_load = array (
2013-12-19 23:00:32 +00:00
'invoice_item' => 'service_id,item_type,date_start,date_stop' ,
2013-09-06 05:39:56 +00:00
);
2013-10-10 02:44:53 +00:00
2013-12-19 23:00:32 +00:00
private $_render = array ();
// Our required Interface Methods
2013-10-10 02:44:53 +00:00
public function cart_item () {
return new Cart_Item ( 1 , sprintf ( 'Invoice: %s' , $this -> refnum ()), $this -> due ());
}
/**
* Return if this invoice is already in the cart
*/
public function cart_exists () {
return count ( Cart :: instance () -> get ( $this -> mid (), $this -> id ));
}
2013-12-19 23:00:32 +00:00
// Our local methods
/**
* Return a list of valid checkout options for this invoice
*/
public function checkout () {
$due = $this -> due ();
return ORM :: factory ( 'Checkout' )
-> where_active ()
-> where ( 'amount_min' , '<=' , $due )
-> where_open ()
-> and_where ( 'amount_max' , '>=' , $due )
-> or_where ( 'amount_max' , 'is' , null )
-> where_close () -> find_all ();
}
/**
* Display the amount due
*/
public function due ( $format = FALSE ) {
// If the invoice is active calculate the due amount
$result = $this -> status ? Currency :: round ( $this -> total () - $this -> payments_total (), 1 ) : 0 ;
return $format ? Currency :: display ( $result ) : $result ;
}
public function email () {
return ORM :: factory ( 'Email_Log' )
-> where ( 'module_id' , '=' , $this -> mid ())
-> where ( 'module_data' , '=' , $this );
}
/**
* Display the Invoice Number
*/
public function id () {
return sprintf ( '%06s' , $this -> id );
}
2013-06-13 13:35:19 +00:00
/**
* Return the recurring schedules that are on an invoice
*
* @ param $period Return an Array of items for that period
*/
2013-09-06 05:39:56 +00:00
public function items_periods ( $period = FALSE ) {
2013-12-19 23:00:32 +00:00
if ( is_null ( $period ))
return isset ( $this -> _render [ 'OTHER' ]) ? $this -> _render [ 'OTHER' ] : array ();
elseif ( $period )
return isset ( $this -> _render [ 'SCHED' ][ $period ]) ? $this -> _render [ 'SCHED' ][ $period ] : array ();
else
return array_keys ( $this -> _render [ 'SCHED' ]);
}
/**
* Return a summary of all itemss on an invoice
*/
public function items_summary () {
2013-06-13 13:35:19 +00:00
$result = array ();
2013-12-19 23:00:32 +00:00
foreach ( $this -> subitems () as $iio ) {
// We only summarise item_type=0
if ( ! $iio -> item_type == 0 )
2013-06-13 13:35:19 +00:00
continue ;
2013-12-19 23:00:32 +00:00
if ( $iio -> module () instanceof Model_Product ) {
$p = $iio -> module () -> title ();
2013-09-06 05:39:56 +00:00
2013-12-19 23:00:32 +00:00
if ( ! isset ( $result [ $p ])) {
$result [ $p ][ 'quantity' ] = 0 ;
$result [ $p ][ 'subtotal' ] = 0 ;
}
$result [ $p ][ 'quantity' ] ++ ;
$result [ $p ][ 'subtotal' ] += $iio -> subtotal ();
2013-09-06 05:39:56 +00:00
}
}
return $result ;
}
/**
* Return the item titles that are on an invoice
*
* @ param $title Return an Array of items for that title
*/
public function items_titles ( $title = NULL , $period = NULL ) {
$result = array ();
2013-12-05 05:22:23 +00:00
foreach ( $this -> subitems () as $ito ) {
2013-09-06 05:39:56 +00:00
if ( $ito -> service_id ) {
if ( is_null ( $title ) AND ! in_array ( $ito -> title (), $result ) AND ( is_null ( $period ) OR ( $period == $ito -> recurring_schedule )))
array_push ( $result , $ito -> title ());
elseif (( $ito -> title () == $title AND ( is_null ( $period ) OR ( $period == $ito -> recurring_schedule ))) OR is_null ( $ito -> recurring_schedule ))
array_push ( $result , $ito );
} else {
throw HTTP_Exception :: factory ( 501 , 'Not handled non-services' );
}
2013-06-13 13:35:19 +00:00
}
return $result ;
}
2013-12-19 23:00:32 +00:00
public function payments () {
return $this -> payment_item -> find_all ();
}
public function payments_total ( $format = FALSE ) {
$result = 0 ;
foreach ( $this -> payments () as $po )
$result += $po -> alloc_amt ;
return $format ? Currency :: display ( $result ) : $result ;
}
/**
* This function takes care of all the invoice items so that they are rendered only once
*
* We organise items by
* + recurring_schedule for service items item_type = 0
* + recurring_schedule for service items for item_type != 0 ( that have an item_type = 0 previously selected )
* + Other Items
*/
public function pre_render () {
$this -> _render = array ();
$this -> _render [ 'SCHED' ] = array ();
$processed = array ();
foreach ( $this -> subitems () as $iio ) {
2014-01-07 12:29:18 +00:00
if ( $iio -> id AND in_array ( $iio -> id , $processed ))
2013-12-19 23:00:32 +00:00
continue ;
if ( $iio -> recurring_schedule ) {
$this -> _render [ 'SCHED' ][ $iio -> recurring_schedule ][] = $iio ;
} elseif ( Object :: in_array ( 'service_id' , $iio -> service_id , $this -> _render [ 'SCHED' ], TRUE )) {
// Do nothing
} else {
$this -> _render [ 'OTHER' ][] = $iio ;
}
array_push ( $processed , $iio -> id );
}
}
/**
* Display the Invoice Reference Number
*/
public function refnum () {
return sprintf ( '%s-%06s' , $this -> account -> accnum (), $this -> id );
}
/**
* Check the reminder value
*/
public function remind ( $key ) {
if ( isset ( $this -> reminders [ $key ]))
return ( is_array ( $this -> reminders [ $key ])) ? end ( $this -> reminders [ $key ]) : $this -> reminders [ $key ];
else
return FALSE ;
}
2013-10-28 23:41:14 +00:00
/**
* Returns the array of Email Template Objects
*/
public function reminders ( $key = NULL , $format = FALSE ) {
2013-11-08 11:02:32 +00:00
if ( is_null ( $key ) AND ! $this -> reminders )
return array ();
2013-10-28 23:41:14 +00:00
$prefix = 'task_invoice_' ;
if ( is_null ( $key )) {
$result = array ();
foreach ( $this -> reminders as $k => $v )
array_push ( $result , ORM :: factory ( 'Email_Template' , array ( 'name' => $prefix . $k )));
return $result ;
} else {
$key = preg_replace ( " /^ $prefix / " , '' , $key );
return isset ( $this -> reminders [ $key ]) ? ( $format ? Config :: date ( $this -> reminders [ $key ]) : $this -> reminders [ $key ]) : NULL ;
}
}
2013-12-19 23:00:32 +00:00
public function save ( Validation $validation = NULL ) {
// Our items will be clobbered once we save the object, so we need to save it here.
$subitems = $this -> subitems ();
// If this is a new invoice, we'll need to do more work
$new = ! $this -> loaded ();
// Save the invoice
parent :: save ( $validation );
// Need to save the associated items and their taxes
if ( $this -> loaded ()) {
foreach ( $subitems as $iio ) {
$iio -> invoice_id = $this -> id ;
if ( ! $iio -> changed ())
continue ;
$iio -> save ();
if ( ! $iio -> saved ()) {
$this -> void = 1 ;
$this -> save ();
break ;
}
// If this is a new invoice, we'll need to update some other items
if ( $new ) {
// Update next invoice date
if ( $iio -> item_type == 0 ) {
$iio -> service -> date_next_invoice = $iio -> date_stop + 86400 ;
// @todo Mark invoice as cancelled and write a memo, then...
if ( ! $iio -> service -> save ())
throw new Kohana_Exception ( 'Problem saving service :service for invoice_item :invoice_item - Failed save()' , array ( ':invoice_item' => $iio -> id , ':service' => $iio -> service_id ));
}
// Update charge iteme
if (( $x = $iio -> module ()) AND ( $x instanceof Model_Charge )) {
$x -> processed = 1 ;
// @todo Mark invoice as cancelled and write a memo, then...
if ( ! $x -> save ())
throw new Kohana_Exception ( 'Problem saving charge :charge for invoice_item :invoice_item - Failed save()' , array ( ':invoice_item' => $iio -> id , ':charge' => $x -> id ));
}
}
// @todo Need to save discount information
}
} else
throw new Kohana_Exception ( 'Couldnt save invoice for some reason?' );
return $this ;
}
2013-09-06 05:39:56 +00:00
/**
* Return the service items on an invoice relating to an invoice_item
* IE : item_type == 0
*/
public function service_items ( Model_Invoice_Item $o ) {
$result = array ();
// At the moment, we only return items pertaining to a service
if ( ! $o -> service_id )
return $result ;
2013-12-05 05:22:23 +00:00
foreach ( $this -> subitems () as $iio )
2013-09-06 05:39:56 +00:00
if ( $iio -> item_type == 0 AND $iio -> service_id == $o -> service_id )
array_push ( $result , $iio );
return $result ;
}
2013-06-13 13:35:19 +00:00
/**
* Return the extra items on an invoice relating to an invoice_item
* IE : item_type != 0
*/
public function service_items_extra ( Model_Invoice_Item $o ) {
$result = array ();
2013-12-05 05:22:23 +00:00
foreach ( $this -> subitems () as $iio )
2013-12-19 23:00:32 +00:00
if ( $iio -> item_type != 0 AND ( ! $o -> service_id OR ( $iio -> service_id == $o -> service_id )))
2013-06-13 13:35:19 +00:00
array_push ( $result , $iio );
return $result ;
}
/**
* Return the total of all items relating to a service
*/
public function service_items_tax ( Model_Invoice_Item $o , $format = FALSE ) {
$result = 0 ;
// At the moment, we only return extra items pertaining to a service
if ( ! $o -> service_id )
return $result ;
2013-12-05 05:22:23 +00:00
foreach ( $this -> subitems () as $iio )
2013-06-13 13:35:19 +00:00
if ( $iio -> service_id == $o -> service_id )
$result += $iio -> tax ();
return $format ? Currency :: display ( $result ) : $result ;
}
/**
* Return the total of all items relating to a service
*/
public function service_items_total ( Model_Invoice_Item $o , $format = FALSE ) {
$result = 0 ;
// At the moment, we only return extra items pertaining to a service
if ( ! $o -> service_id )
return $result ;
2013-12-05 05:22:23 +00:00
foreach ( $this -> subitems () as $iio )
2013-06-13 13:35:19 +00:00
if ( $iio -> service_id == $o -> service_id )
$result += $iio -> total ();
return $format ? Currency :: display ( $result ) : $result ;
2013-10-10 02:44:53 +00:00
}
2013-12-19 23:00:32 +00:00
public function set_remind ( $key , $value , $add = FALSE ) {
$x = $this -> reminders ;
// If our value is null, we'll remove it.
if ( is_null ( $value ) AND isset ( $x [ $key ]))
unset ( $x [ $key ]);
elseif ( $add ) {
if ( ! is_array ( $a = $x [ $key ]))
$x [ $key ] = array ( $a );
$x [ $key ][] = $value ;
} else
$x [ $key ] = $value ;
$this -> reminders = $x ;
$this -> save ();
return $this -> saved ();
}
2013-10-10 02:44:53 +00:00
/**
2013-12-05 05:22:23 +00:00
* Add an item to an invoice
2013-10-10 02:44:53 +00:00
*/
2013-12-05 05:22:23 +00:00
public function subitem_add ( Model_Invoice_Item $iio , Model_Country $co , $taxed = FALSE ) {
$iio -> subitem_add ( $co , $taxed );
2013-10-10 02:44:53 +00:00
2013-12-05 05:22:23 +00:00
array_push ( $this -> _sub_items , $iio );
2013-10-10 02:44:53 +00:00
2013-12-05 05:22:23 +00:00
$this -> _sub_items_sorted = FALSE ;
2013-10-10 02:44:53 +00:00
}
/**
* Return a list of invoice items for this invoice .
* @ param type [ CHARGE | CREDIT | ALL ]
* @ see invoice_items
*/
2013-12-05 05:22:23 +00:00
public function subitems ( $type = 'ALL' ) {
2013-10-10 02:44:53 +00:00
$result = array ();
2013-12-19 23:00:32 +00:00
foreach ( $this -> _sub_items as $iio ) {
2013-10-09 12:26:59 +00:00
// Ignore voided items
2013-12-19 23:00:32 +00:00
if ( $iio -> void )
2013-10-09 12:26:59 +00:00
continue ;
2013-10-10 02:44:53 +00:00
$return = FALSE ;
switch ( $type ) {
case 'CHARGE' :
2013-12-19 23:00:32 +00:00
if ( $iio -> quantity > 0 )
2013-10-10 02:44:53 +00:00
$return = TRUE ;
break ;
case 'CREDIT' :
2013-12-19 23:00:32 +00:00
if ( $iio -> quantity < 0 )
2013-10-10 02:44:53 +00:00
$return = TRUE ;
break ;
case 'ALL' :
default :
$return = TRUE ;
break ;
}
if ( $return )
2013-12-19 23:00:32 +00:00
array_push ( $result , $iio );
2013-10-10 02:44:53 +00:00
}
return $result ;
}
/**
* Return the subtotal of all items
*/
public function subtotal ( $format = FALSE ) {
$result = 0 ;
2013-12-05 05:22:23 +00:00
foreach ( $this -> subitems () as $ito )
2013-10-10 02:44:53 +00:00
$result += $ito -> subtotal ();
2013-12-19 23:00:32 +00:00
return $format ? Currency :: display ( $result ) : $result ;
2013-10-10 02:44:53 +00:00
}
public function tax ( $format = FALSE ) {
$result = 0 ;
2013-12-05 05:22:23 +00:00
foreach ( $this -> subitems () as $ito )
2013-10-10 02:44:53 +00:00
$result += $ito -> tax ();
2013-12-19 23:00:32 +00:00
return $format ? Currency :: display ( $result ) : $result ;
2013-10-10 02:44:53 +00:00
}
/**
* Return a list of taxes used on this invoice
* @ todo Move some of this to invoice_item_tax .
*/
public function tax_summary () {
$summary = array ();
2013-12-19 23:00:32 +00:00
foreach ( $this -> subitems () as $iio ) {
foreach ( $iio -> tax -> find_all () as $iito ) {
if ( ! isset ( $summary [ $iito -> tax_id ]))
$summary [ $iito -> tax_id ] = $iito -> amount ;
2013-10-10 02:44:53 +00:00
else
2013-12-19 23:00:32 +00:00
$summary [ $iito -> tax_id ] += $iito -> amount ;
2013-10-10 02:44:53 +00:00
}
}
// @todo This should be removed eventually
if ( ! $summary )
$summary [ 1 ] = $this -> tax ();
return $summary ;
}
/**
* Return the total of all items
*/
public function total ( $format = FALSE ) {
$result = 0 ;
// This will include charges and credits
2013-12-05 05:22:23 +00:00
foreach ( $this -> subitems () as $ito )
2013-10-10 02:44:53 +00:00
$result += $ito -> total ();
2013-12-19 23:00:32 +00:00
return $format ? Currency :: display ( $result ) : $result ;
2013-10-10 02:44:53 +00:00
}
public function total_charges ( $format = FALSE ) {
$result = 0 ;
2013-12-05 05:22:23 +00:00
foreach ( $this -> subitems ( 'CHARGE' ) as $ito )
2013-10-10 02:44:53 +00:00
$result += $ito -> subtotal () + $ito -> tax ();
2013-12-19 23:00:32 +00:00
return $format ? Currency :: display ( $result ) : $result ;
2013-10-10 02:44:53 +00:00
}
public function total_credits ( $format = FALSE ) {
$result = 0 ;
2013-12-05 05:22:23 +00:00
foreach ( $this -> subitems ( 'CREDIT' ) as $ito )
2013-10-10 02:44:53 +00:00
$result += ( $ito -> subtotal () + $ito -> tax ()) *- 1 ;
2013-12-19 23:00:32 +00:00
return $format ? Currency :: display ( $result ) : $result ;
2013-10-10 02:44:53 +00:00
}
public function total_discounts ( $format = FALSE ) {
$result = 0 ;
2013-12-05 05:22:23 +00:00
foreach ( $this -> subitems () as $ito )
2013-10-10 02:44:53 +00:00
$result += $ito -> discount ();
2013-12-19 23:00:32 +00:00
return $format ? Currency :: display ( $result ) : $result ;
2013-10-10 02:44:53 +00:00
}
2013-12-19 23:00:32 +00:00
private function _where_unprocessed () {
return $this -> where_open () -> where ( 'process_status' , '!=' , 1 ) -> or_where ( 'process_status' , 'is' , NULL ) -> where_close ();
}
public function where_unprocessed () {
return $this -> _where_unprocessed ();
}
// Our list function
2013-10-10 02:44:53 +00:00
/**
* Search for invoices matching a term
*/
2013-05-08 09:00:47 +00:00
public function list_autocomplete ( $term , $index , $value , array $label , array $limit = array (), array $options = NULL ) {
// We only show invoice numbers.
if ( ! is_numeric ( $term ))
return array ();
2013-10-10 02:44:53 +00:00
2013-05-08 09:00:47 +00:00
$ao = Auth :: instance () -> get_user ();
2013-10-10 02:44:53 +00:00
2013-05-08 09:00:47 +00:00
$this -> clear ();
$this -> where_active ();
2013-10-10 02:44:53 +00:00
2013-05-08 09:00:47 +00:00
// Build our where clause
$this -> where_open ()
-> where ( 'id' , 'like' , '%' . $term . '%' )
-> where_close ();
2013-10-10 02:44:53 +00:00
2013-05-08 09:00:47 +00:00
// Restrict results to authorised accounts
array_push ( $limit , array ( 'account_id' , 'IN' , $ao -> RTM -> customers ( $ao -> RTM )));
return parent :: list_autocomplete ( $term , $index , $value , $label , $limit , $options );
2013-10-10 02:44:53 +00:00
}
2013-10-11 00:00:16 +00:00
private function _list_due ( $authorised ) {
2013-10-10 02:44:53 +00:00
static $result = array ();
2013-10-11 00:00:16 +00:00
if ( $authorised )
$this -> where_authorised ();
2013-10-10 02:44:53 +00:00
if ( ! $result )
2013-10-11 00:00:16 +00:00
foreach ( $this -> _where_active () -> _where_unprocessed () -> find_all () as $io )
2013-10-10 02:44:53 +00:00
if ( $io -> due ())
array_push ( $result , $io );
return $result ;
}
2013-12-19 23:00:32 +00:00
/**
* Return a list of invoices that are due , excluding overdue .
*/
public function list_due ( $time = NULL , $authorised = TRUE ) {
$result = array ();
foreach ( $this -> _list_due ( $authorised ) as $io )
if ( is_null ( $time ) OR $io -> due_date > $time )
array_push ( $result , $io );
return $result ;
2013-10-10 02:44:53 +00:00
}
2013-12-19 23:00:32 +00:00
public function list_due_total ( $format = FALSE , $time = NULL , $authorised = TRUE ) {
$result = 0 ;
foreach ( $this -> list_due ( $time , $authorised ) as $io )
$result += $io -> due ();
return $format ? Currency :: display ( $result ) : $result ;
2013-10-10 02:44:53 +00:00
}
/**
* Identify all the invoices that are due
*/
2013-10-11 00:00:16 +00:00
public function list_overdue ( $time = NULL , $authorised = TRUE ) {
2013-10-10 02:44:53 +00:00
$result = array ();
if ( is_null ( $time ))
$time = time ();
2013-10-11 00:00:16 +00:00
foreach ( $this -> _list_due ( $authorised ) as $io )
2013-10-10 02:44:53 +00:00
if ( $io -> due_date <= $time )
array_push ( $result , $io );
return $result ;
}
/**
* Return a list of invoices that are over their due date with / without auto billing
*/
2013-10-11 02:08:50 +00:00
public function list_overdue_billing ( $time = NULL , $billing = FALSE , $authorised = TRUE ) {
2013-10-10 02:44:53 +00:00
$result = array ();
2013-10-11 02:08:50 +00:00
foreach ( $this -> list_overdue ( $time , $authorised ) as $io ) {
2013-10-10 02:44:53 +00:00
$i = FALSE ;
foreach ( $io -> service -> find_all () as $so )
if (( $billing AND $so -> account_billing_id ) OR ( ! $billing AND ! $so -> account_billing_id )) {
array_push ( $result , $io );
break ;
}
}
return $result ;
}
/**
* Return a list of invoices that need to be sent .
* @ todo This should be optimised a little to return only invoices to send , instead of looking for them .
*/
public function list_tosend () {
return ORM :: factory ( 'Invoice' ) -> where_active () -> where_open () -> where ( 'print_status' , 'is' , NULL ) -> or_where ( 'print_status' , '!=' , 1 ) -> where_close ();
}
}
?>