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
*/
2016-08-15 11:23:18 +00:00
class Model_Invoice extends ORM implements Cartable {
2013-10-10 02:44:53 +00:00
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 (
2016-08-31 05:03:09 +00:00
'active' => array (
array ( 'StaticList_YesNo::get' , array ( ':value' , TRUE )),
),
2013-10-10 02:44:53 +00:00
'date_orig' => array (
2014-08-25 04:41:07 +00:00
array ( 'Site::Date' , array ( ':value' )),
2013-10-10 02:44:53 +00:00
),
'due_date' => array (
2014-08-25 04:41:07 +00:00
array ( 'Site::Date' , array ( ':value' )),
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 (
2016-08-03 06:25:26 +00:00
'invoice_item' => array ( 'service_id' , 'product_id' , 'item_type' , 'date_start' , 'date_stop' ),
2013-09-06 05:39:56 +00:00
);
2013-10-10 02:44:53 +00:00
2014-08-25 04:41:07 +00:00
protected $_compress_column = array (
'reminders' ,
);
2013-12-19 23:00:32 +00:00
private $_render = array ();
2016-07-29 13:04:34 +00:00
/** REQUIRED ABSTRACT METHODS **/
2016-08-10 06:07:00 +00:00
2016-07-29 13:04:34 +00:00
/** REQUIRED INTERFACE METHODS **/
2013-12-19 23:00:32 +00:00
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 ));
}
2016-08-03 06:25:26 +00:00
/** 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 );
}
2013-12-19 23:00:32 +00:00
/**
* 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
2016-08-31 05:03:09 +00:00
$result = $this -> active ? Currency :: round ( $this -> total () - $this -> payments_total (), 1 ) : 0 ;
2013-12-19 23:00:32 +00:00
return $format ? Currency :: display ( $result ) : $result ;
}
public function email () {
return ORM :: factory ( 'Email_Log' )
-> where ( 'module_id' , '=' , $this -> mid ())
-> where ( 'module_data' , '=' , $this );
}
2016-08-03 06:25:26 +00:00
/**
* 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 )
2016-08-15 11:23:18 +00:00
$iio -> id = '****:' . $c ++ ;
2016-08-03 06:25:26 +00:00
// 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 );
2016-08-31 05:03:09 +00:00
if ( $iio -> active )
2016-08-03 06:25:26 +00:00
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 );
2016-08-31 05:03:09 +00:00
if ( $iio -> active )
2016-08-03 06:25:26 +00:00
array_push ( $result [ 's' ], $iio );
}
}
2016-08-10 06:07:00 +00:00
// Get service items that dont have a recurring schedule
foreach ( $this -> _sub_items as $iio )
2016-08-31 05:03:09 +00:00
if ( $iio -> active AND ! in_array ( $iio -> id , $track ) AND $iio -> service_id ) {
2016-08-10 06:07:00 +00:00
// Ensure we dont process this item again
array_push ( $track , $iio -> id );
array_push ( $result [ 's' ], $iio );
}
2016-08-03 06:25:26 +00:00
// Next get the items we havent already got
$result [ 'other' ] = array ();
foreach ( $this -> _sub_items as $iio )
2016-08-31 05:03:09 +00:00
if ( $iio -> active AND ! in_array ( $iio -> id , $track ))
2016-08-03 06:25:26 +00:00
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 ;
}
2013-12-19 23:00:32 +00:00
/**
* Return a summary of all itemss on an invoice
*/
public function items_summary () {
2013-06-13 13:35:19 +00:00
$result = array ();
2016-08-15 11:23:18 +00:00
$lo = $this -> account -> language ;
2013-06-13 13:35:19 +00:00
2016-08-17 12:30:12 +00:00
$track [ 'p' ] = $track = array ();
foreach ( $this -> items_render () as $key => $items ) {
switch ( $key ) {
case 's' :
$last = '' ;
foreach ( $items as $iio ) {
if ( $iio -> product ) {
$p = $iio -> title ( $lo );
}
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
2016-08-17 12:30:12 +00:00
break ;
2013-12-19 23:00:32 +00:00
2016-08-17 12:30:12 +00:00
case 'other' :
break ;
2013-09-06 05:39:56 +00:00
}
}
2016-08-17 12:30:12 +00:00
ksort ( $result );
2013-09-06 05:39:56 +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 ;
}
2016-08-03 06:25:26 +00:00
/**
* 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 ;
}
2016-08-15 11:23:18 +00:00
/**
* Display the Invoice Reference Number
*/
public function refnum ( $short = FALSE ) {
return ( $short ? '' : $this -> account -> refnum ( FALSE ) . '-' ) . sprintf ( '%06s' , $this -> id );
}
2013-12-19 23:00:32 +00:00
/**
* Check the reminder value
*/
public function remind ( $key ) {
2015-09-29 01:54:45 +00:00
if ( isset ( $this -> reminders [ $key ])) {
$x = $this -> reminders [ $key ];
return is_array ( $x ) ? end ( $x ) : $x ;
} else
2013-12-19 23:00:32 +00:00
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 );
2018-06-13 11:40:08 +00:00
// Fix for inv 5061
return ( isset ( $this -> reminders [ $key ]) AND ! is_array ( $this -> reminders [ $key ])) ? ( $format ? Site :: Date ( $this -> reminders [ $key ]) : $this -> reminders [ $key ]) : NULL ;
2013-10-28 23:41:14 +00:00
}
}
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 ()) {
2016-08-31 05:03:09 +00:00
$this -> active = 0 ;
2013-12-19 23:00:32 +00:00
$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 ;
}
2016-08-03 06:25:26 +00:00
/**
* 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 ;
}
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
/**
* 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
2016-08-31 05:03:09 +00:00
if ( ! $iio -> active )
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
public function where_unprocessed () {
2016-08-31 05:03:09 +00:00
return $this -> where_open () -> where ( 'process_status' , '!=' , 1 ) -> or_where ( 'process_status' , 'is' , NULL ) -> where_close ();
2013-12-19 23:00:32 +00:00
}
// Our list function
2013-10-10 02:44:53 +00:00
/**
* Search for invoices matching a term
*/
2016-08-03 04:00:51 +00:00
public function list_autocomplete ( $term , $index , $value , array $label , array $limit = array (), array $options = array ()) {
2013-05-08 09:00:47 +00:00
// 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 )
2016-08-31 05:03:09 +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 ();
}
}
?>