Work on invoices, products and other minor things
This commit is contained in:
parent
50a096e22a
commit
718c42be65
@ -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'),
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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),
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
@ -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)
|
||||
*
|
||||
* @param sid int Service ID
|
||||
* Return all invoice items for a service optionally by recurring schedule
|
||||
*/
|
||||
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($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
|
||||
*
|
||||
* This is so that we can list items summarised by billing period
|
||||
*/
|
||||
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) {
|
||||
$result = array();
|
||||
|
||||
foreach ($this->_list_due() as $io)
|
||||
if ($io->due_date > time())
|
||||
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();
|
||||
array_push($result,$io);
|
||||
elseif ($this->due_date <= $time)
|
||||
array_push($result,$io);
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
@ -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?');
|
||||
}
|
||||
|
@ -26,11 +26,11 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Current Charges Due</td>
|
||||
<td class="bold-right"><?php echo $io->display('total_amt'); ?></td>
|
||||
<td class="bold-right"><?php echo $io->total(TRUE); ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Payments Received to Date</td>
|
||||
<td class="bold-right"><?php echo $io->display('billed_amt'); ?></td>
|
||||
<td class="bold-right"><?php echo $io->payments_total(TRUE); ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Total Charges Due</td>
|
||||
@ -46,94 +46,61 @@
|
||||
<tr>
|
||||
<td class="head" colspan="4">Charges Detail:</td>
|
||||
</tr>
|
||||
<?php foreach ($io->sorted_service_items('recur_schedule') as $cat => $catitems) { ?>
|
||||
<?php if ($cat) { ?>
|
||||
<?php foreach ($io->items_service_periods() as $rs => $items) { ?>
|
||||
<tr>
|
||||
<td><div id="toggle_<?php echo $cat; ?>"><?php echo HTML::image($mediapath->uri(array('file'=>'img/toggle-closed.png')),array('alt'=>'+')); ?></div><script type="text/javascript">$("#toggle_<?php echo $cat; ?>").click(function() {$('#detail_toggle_<?php echo $cat; ?>').toggle();});</script></td>
|
||||
<td><?php echo StaticList_RecurSchedule::display($cat); ?></td>
|
||||
<td><?php printf('%s Services',count($catitems['items'])); ?></td>
|
||||
<td class="bold-right"><?php echo Currency::display($catitems['total']); ?></td>
|
||||
</tr>
|
||||
<td><div id="toggle_<?php echo $rs; ?>"><?php echo HTML::image($mediapath->uri(array('file'=>'img/toggle-closed.png')),array('alt'=>'+')); ?></div><script type="text/javascript">$("#toggle_<?php echo $rs; ?>").click(function() {$('#detail_toggle_<?php echo $rs; ?>').toggle();});</script></td>
|
||||
<?php if ($rs) { ?>
|
||||
<td><?php echo StaticList_RecurSchedule::display($rs); ?></td>
|
||||
<td colspan="1"><?php printf('%s Service(s)',count($items)); ?></td>
|
||||
<?php } else { ?>
|
||||
<tr>
|
||||
<td colspan="3">Other Items</td>
|
||||
<td class="bold-right"><?php echo Currency::display($catitems['total']); ?></td>
|
||||
</tr>
|
||||
<td colspan="2">Other Items</td>
|
||||
<?php } ?>
|
||||
<td> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td colspan="2">
|
||||
<div id="detail_toggle_<?php echo $cat; ?>">
|
||||
<div id="detail_toggle_<?php echo $rs; ?>">
|
||||
<table class="box-full" border="0">
|
||||
<?php if ($catitems['items']) { ?>
|
||||
<?php foreach ($catitems['items'] as $item) { ?>
|
||||
<?php if ($items) { ?>
|
||||
<?php foreach ($io->items_services($items) as $sid) { ?>
|
||||
<?php $so = ORM::factory('service',$sid); ?>
|
||||
<!-- Product Information -->
|
||||
<tr class="head">
|
||||
<td><?php echo HTML::anchor('/user/service/view/'.$item->service->id,$item->service->id()); ?></td>
|
||||
<td colspan="3"><?php echo $item->product_name ? $item->product_name : $item->product->product_translate->find()->name; ?> (<?php echo $item->product_id; ?>)</td>
|
||||
<td class="right"><?php echo Currency::display($io->items_service_total($item->service_id));?></td>
|
||||
<td><?php echo HTML::anchor('/user/service/view/'.$so->id,$so->id()); ?></td>
|
||||
<td colspan="5"><?php echo $so->service_name(); ?> (<?php echo $so->product_id; ?>)</td>
|
||||
<td class="right"><?php echo Currency::display($io->items_service_total($so->id));?></td>
|
||||
</tr>
|
||||
<!-- End Product Information -->
|
||||
|
||||
<?php foreach ($io->items_service($sid) as $ito) { ?>
|
||||
<!-- Product Sub Information -->
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td><?php echo $item->trannum();?></td>
|
||||
<td><?php echo $item->period();?></td>
|
||||
<td class="right"><?php echo Currency::display($item->subtotal());?></td>
|
||||
<td><?php echo $ito->trannum();?></td>
|
||||
<td><?php echo $ito->name();?></td>
|
||||
<td><?php echo $ito->detail();?></td>
|
||||
<td><?php echo $ito->period();?></td>
|
||||
<td class="right"><?php echo Currency::display($ito->subtotal());?> </td>
|
||||
</tr>
|
||||
<!-- End Product Sub Information -->
|
||||
<?php } ?>
|
||||
|
||||
<!-- Product Sub Items -->
|
||||
<?php
|
||||
foreach ($io->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';
|
||||
}
|
||||
?>
|
||||
<?php if ($ito->discount_amt) { ?>
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td><?php echo $subitem->trannum(); ?></td>
|
||||
<td><?php echo $display; ?></td>
|
||||
<td class="right"><?php echo Currency::display($subitem->subtotal());?></td>
|
||||
<td colspan="4"> </td>
|
||||
<td><?php echo _('Discounts'); ?></td>
|
||||
<td class="right">(<?php echo Currency::display($io->items_service_discount($so->id));?>)</td>
|
||||
</tr>
|
||||
<?php } ?>
|
||||
<!-- Product End Sub Items -->
|
||||
|
||||
<!-- Product Sub Items Tax -->
|
||||
<tr>
|
||||
<td colspan="2"> </td>
|
||||
<td colspan="4"> </td>
|
||||
<td><?php echo _('Taxes'); ?></td>
|
||||
<td class="right"><?php echo $io->tax(TRUE);?></td>
|
||||
<td class="right"><?php echo Currency::display($io->items_service_tax($so->id));?> </td>
|
||||
</tr>
|
||||
<!-- Product End Sub Items Tax -->
|
||||
<?php } ?>
|
||||
<?php } else { ?>
|
||||
<!-- Product Sub Items -->
|
||||
<?php
|
||||
foreach ($io->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';
|
||||
}
|
||||
?>
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td><?php echo $subitem->trannum(); ?></td>
|
||||
<td><?php echo $display; ?></td>
|
||||
<td class="right"><?php echo Currency::display($subitem->subtotal());?></td>
|
||||
</tr>
|
||||
<?php } ?>
|
||||
<!-- Product End Sub Items -->
|
||||
<?php } ?>
|
||||
</table>
|
||||
</div>
|
||||
@ -141,9 +108,15 @@
|
||||
</tr>
|
||||
<?php } ?>
|
||||
<tr>
|
||||
<td class="head" colspan="2">Sub Total:</td>
|
||||
<td class="bold-right"><?php echo $io->subtotal(TRUE); ?></td>
|
||||
<td class="head" colspan="3">Sub Total of Items:</td>
|
||||
<td class="bold-right"><?php echo $io->subtotal(TRUE); ?> </td>
|
||||
</tr>
|
||||
<?php if ($io->discount()) { ?>
|
||||
<tr>
|
||||
<td class="head" colspan="3">Discounts:</td>
|
||||
<td class="bold-right">(<?php echo $io->discount(TRUE); ?>)</td>
|
||||
</tr>
|
||||
<?php } ?>
|
||||
<tr>
|
||||
<td class="head" colspan="4">Taxes Included:</td>
|
||||
</tr>
|
||||
@ -153,14 +126,14 @@
|
||||
?>
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td><?php echo $m->description; ?></td>
|
||||
<td class="bold-right"><?php echo Currency::display($amount); ?></td>
|
||||
<td colspan="2"><?php echo $m->description; ?></td>
|
||||
<td class="bold-right"><?php echo Currency::display($amount); ?> </td>
|
||||
</tr>
|
||||
<?php }?>
|
||||
<!-- @todo Add discounts -->
|
||||
<tr>
|
||||
<td class="head" colspan="3">Total:</td>
|
||||
<td class="bold-right"><?php echo $io->total(TRUE); ?></td>
|
||||
<td class="bold-right"><?php echo $io->total(TRUE); ?> </td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
|
46
modules/product/classes/controller/admin/product.php
Normal file
46
modules/product/classes/controller/admin/product.php
Normal file
@ -0,0 +1,46 @@
|
||||
<?php defined('SYSPATH') or die('No direct access allowed.');
|
||||
|
||||
/**
|
||||
* This class provides Admin Product functions
|
||||
*
|
||||
* @package OSB
|
||||
* @subpackage Product
|
||||
* @category Controllers/Admin
|
||||
* @author Deon George
|
||||
* @copyright (c) 2010 Open Source Billing
|
||||
* @license http://dev.osbill.net/license.html
|
||||
*/
|
||||
class Controller_Admin_Product extends Controller_TemplateDefault_Admin {
|
||||
protected $secure_actions = array(
|
||||
'list'=>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',
|
||||
)),
|
||||
));
|
||||
}
|
||||
}
|
||||
?>
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
Reference in New Issue
Block a user