This repository has been archived on 2024-04-08. You can view files and clone it, but cannot push or open issues or pull requests.
Deon George 130a87aa9a Major work to domain and hosting
Minor updates for ADSL services
Updates to Sort::MAsort()
Move core OSB items under application/
Moved ACCOUNT functions under application
Minor updates to task
2012-01-12 19:53:55 +11:00

431 lines
10 KiB
PHP

<?php defined('SYSPATH') or die('No direct access allowed.');
/**
* This class provides invoice capabilities.
*
* @package OSB
* @subpackage Invoice
* @category Models
* @author Deon George
* @copyright (c) 2010 Open Source Billing
* @license http://dev.osbill.net/license.html
*/
class Model_Invoice extends ORMOSB {
private $invoice_items = array();
protected $_belongs_to = array(
'account'=>array()
);
protected $_has_many = array(
'invoice_item'=>array('far_key'=>'id'),
'invoice_item_tax'=>array(),
'service'=>array('through'=>'invoice_item'),
'payment'=>array('through'=>'payment_item'),
'payment_item'=>array('far_key'=>'id'),
);
protected $_sorting = array(
'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')),
),
'due_date'=>array(
array('Config::date',array(':value')),
),
'billed_amt'=>array(
array('Currency::display',array(':value')),
),
'credit_amt'=>array(
array('Currency::display',array(':value')),
),
'status'=>array(
array('StaticList_YesNo::display',array(':value')),
),
'total_amt'=>array(
array('Currency::display',array(':value')),
),
);
/**
* Display the Invoice Number
*/
public function id() {
return sprintf('%06s',$this->id);
}
/**
* Display the Invoice Reference Number
*/
public function refnum() {
return sprintf('%s-%06s',$this->account->accnum(),$this->id);
}
/**
* Display the amount due
*/
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;
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;
}
/**
* Return the subtotal of all items
*/
public function subtotal($format=FALSE) {
$result = 0;
foreach ($this->items() as $ito)
$result += $ito->subtotal();
return $format ? Currency::display($result) : $result;
}
public function tax($format=FALSE) {
$result = 0;
foreach ($this->items() as $ito)
$result += $ito->tax_amt;
return $format ? Currency::display($result) : $result;
}
/**
* Return the total of all items
*/
public function total($format=FALSE) {
$result = 0;
foreach ($this->items() as $ito)
$result += $ito->total();
// Reduce by any credits
$result -= $this->credit_amt;
return $format ? Currency::display($result) : $result;
}
public function payments() {
return ($this->loaded() AND ! $this->_changed) ? $this->payment_item->find_all() : NULL;
}
public function payments_total($format=FALSE) {
$result = 0;
foreach ($this->payments() as $po)
$result += $po->alloc_amt;
return $format ? Currency::display($result) : $result;
}
/**
* Return a list of our main invoice items (item_id=0)
*/
public function items_main() {
return ($this->loaded() AND ! $this->_changed) ? $this->invoice_item->where('item_type','=',0)->find_all() : NULL;
}
/**
* Return a list of our sub invoice items (item_id!=0)
*
* @param sid int Service ID
*/
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;
}
/**
* Summarise the items on an invoice
*/
public function items_summary() {
$result = array();
foreach ($this->items_main() as $ito) {
$unique = TRUE;
$t = $ito->product->name();
if (! isset($result[$t])) {
$result[$t]['quantity'] = 0;
$result[$t]['subtotal'] = 0;
}
$result[$t]['quantity'] += $ito->quantity;
$result[$t]['subtotal'] += $ito->subtotal();
}
return $result;
}
/**
* Find all the invoice items relating to a service
*
* @param int Service ID
*/
private function 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->items_service($sid) as $ito)
$total += $ito->total();
return $total;
}
/**
* Calculate the tax of items for a service
*/
public function items_service_tax($sid) {
$total = 0;
foreach ($this->items_service($sid) as $ito)
$total += $ito->tax_amt;
return $total;
}
/**
* Return a list of items based on a sort criteria
*/
public function sorted_service_items($index) {
$summary = array();
foreach ($this->items() as $ito) {
$key = $ito->service->$index;
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 a list of taxes used on this invoice
*/
public function tax_summary() {
$summary = array();
foreach ($this->items() as $ito) {
foreach ($ito->invoice_item_tax->find_all() as $item_tax) {
if (! isset($summary[$item_tax->tax_id]))
$summary[$item_tax->tax_id] = $item_tax->amount;
else
$summary[$item_tax->tax_id] += $item_tax->amount;
}
}
return $summary;
}
/**
* Add an item to an invoice
*/
public function add_item() {
$c = count($this->invoice_items);
$this->invoice_items[$c] = ORM::factory('invoice_item');
return $this->invoice_items[$c];
}
public function save(Validation $validation = NULL) {
// 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 ($this->items() as $invoice_item) {
$invoice_item->invoice_id = $this->id;
if (! $invoice_item->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();
if (! $invoice_item->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));
}
// @todo Need to save tax information
// @todo Need to save discount information
}
} else
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
*/
public function remind($key) {
if (! $this->loaded())
return NULL;
if (! trim($this->reminders))
return FALSE;
if (! preg_match('/^a:/',$this->reminders))
throw new Kohana_Exception('Reminder is not an array? (:reminder)',array(':remind',$this->reminders));
$remind = unserialize($this->reminders);
if (isset($remind[$key]))
return (is_array($remind[$key])) ? end($remind[$key]) : $remind[$key];
else
return FALSE;
}
public function set_remind($key,$value,$add=FALSE) {
if (! $this->loaded())
throw new Kohana_Exception('Cant call :method when a record not loaded.',array(':method',__METHOD__));
if (! trim($this->reminders)) {
$remind = array();
} else {
if (! preg_match('/^a:/',$this->reminders))
throw new Kohana_Exception('Reminder is not an array? (:reminder)',array(':remind',$this->reminders));
$remind = unserialize($this->reminders);
}
// If our value is null, we'll remove it.
if (is_null($value) AND isset($remind[$key]))
unset($remind[$key]);
elseif ($add)
$remind[$key][] = $value;
else
$remind[$key] = $value;
$this->reminders = serialize($remind);
$this->save();
return $this->saved();
}
/** 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');
}
/**
* Identify all the invoices that are due
*/
public function list_overdue($time=NULL) {
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();
}
/**
* Return a list of invoices that are over their due date with/without auto billing
*/
public function list_overdue_billing($time=NULL,$billing=FALSE) {
$return = array();
foreach ($this->list_overdue($time) as $io) {
$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($return,$io);
break;
}
}
return $return;
}
/**
* 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();
}
}
?>