Updated charge, Invoice improvements and other minor fixes

This commit is contained in:
Deon George 2013-09-06 15:39:56 +10:00
parent 58afecb160
commit d634bd636c
52 changed files with 748 additions and 560 deletions

View File

@ -11,27 +11,11 @@
*/
class Controller_Reseller_Account extends Controller_Account {
protected $secure_actions = array(
'ajaxlist'=>TRUE,
'list'=>TRUE,
'listlog'=>TRUE,
'view'=>TRUE,
);
/**
* Used by AJAX calls to find accounts
* @note list_autocomplete() will limit to authorised accounts
*/
public function action_ajaxlist() {
$result = array();
if (isset($_REQUEST['term']) AND trim($_REQUEST['term']))
$result += ORM::factory('Account')->list_autocomplete($_REQUEST['term']);
$this->auto_render = FALSE;
$this->response->headers('Content-Type','application/json');
$this->response->body(json_encode(array_values($result)));
}
/**
* Show a list of accounts
*/
@ -138,7 +122,7 @@ class Controller_Reseller_Account extends Controller_Account {
->title(sprintf('Next Invoice Items for Account: %s',$ao->accnum()))
->title_icon('icon-info-sign')
->span(6)
->body($i->render('html','body'));
->body($i->render('html','body',array('noid'=>TRUE)));
}
}
?>

View File

@ -41,7 +41,7 @@ class Controller_TemplateDefault extends lnApp_Controller_TemplateDefault {
// @todo To rework
public function after() {
$dc = 'u/welcome/index';
$dc = URL::link('user','welcome/index');
$m = sprintf('%s/%s',Request::current()->directory(),Request::current()->controller());
BreadCrumb::URL(Request::current()->directory(),sprintf('%s/%s',Request::current()->directory(),$dc),FALSE);

View File

@ -21,12 +21,12 @@ class Controller_User_Search extends Controller_Search {
$result = array();
if (isset($_REQUEST['term']) AND trim($_REQUEST['term'])) {
$result = Arr::merge($result,ORM::factory('Account')->list_autocomplete($_REQUEST['term'],'url','id',array('ACC %s: %s'=>array('id','name(TRUE)')),array(),array('urlprefix'=>'r/account/view/')));
$result = Arr::merge($result,ORM::factory('Service')->list_autocomplete($_REQUEST['term'],'url','id',array('SVC %s: %s'=>array('id','service_name()')),array(),array('urlprefix'=>'u/service/view/')));
$result = Arr::merge($result,ORM::factory('Invoice')->list_autocomplete($_REQUEST['term'],'url','id',array('INV %s: %s'=>array('id','account->name(TRUE)')),array(),array('urlprefix'=>'u/invoice/view/')));
$result = Arr::merge($result,ORM::factory('Account')->list_autocomplete($_REQUEST['term'],'url','id',array('ACC %s: %s'=>array('id','name(TRUE)')),array(),array('urlprefix'=>URL::link('reseller','account/view/'))));
$result = Arr::merge($result,ORM::factory('Service')->list_autocomplete($_REQUEST['term'],'url','id',array('SVC %s: %s'=>array('id','service_name()')),array(),array('urlprefix'=>URL::link('user','service/view/'))));
$result = Arr::merge($result,ORM::factory('Invoice')->list_autocomplete($_REQUEST['term'],'url','id',array('INV %s: %s'=>array('id','account->name(TRUE)')),array(),array('urlprefix'=>URL::link('user','invoice/view/'))));
foreach (array('Service_Plugin_Adsl','Service_Plugin_Domain','Service_Plugin_Host') as $o)
$result = Arr::merge($result,ORM::factory($o)->list_autocomplete($_REQUEST['term'],'url','service_id',array('SVC %s: %s'=>array('service_id','service_name()')),array(),array('urlprefix'=>'u/service/view/')));
$result = Arr::merge($result,ORM::factory($o)->list_autocomplete($_REQUEST['term'],'url','service_id',array('SVC %s: %s'=>array('service_id','service_name()')),array(),array('urlprefix'=>URL::link('user','service/view/'))));
}
$this->response->headers('Content-Type','application/json');

View File

@ -10,11 +10,17 @@
* @license http://dev.osbill.net/license.html
*/
abstract class Minion_Task extends Kohana_Minion_Task {
protected $_options = array(
protected $_sysoptions = array(
'site'=>NULL,
'id'=>NULL,
'force'=>FALSE,
'verbose'=>FALSE,
);
/**
* Override our __construct so that we can specify options in each class file
*/
protected function __construct() {
// Populate $_accepted_options based on keys from $_options
$this->_accepted_options = array_keys(Arr::merge($this->_sysoptions,$this->_options));
}
}
?>

View File

@ -14,6 +14,10 @@ class Model_Country extends ORM_OSB {
'currency'=>array('far_key'=>'id'),
);
protected $_sorting = array(
'name'=>'ASC',
);
protected $_form = array('id'=>'id','value'=>'name');
public static function icon() {

View File

@ -10,6 +10,10 @@
* @license http://dev.osbill.net/license.html
*/
class Model_Currency extends ORM_OSB {
protected $_sorting = array(
'name'=>'ASC',
);
protected $_form = array('id'=>'id','value'=>'name');
}
?>

View File

@ -10,6 +10,10 @@
* @license http://dev.osbill.net/license.html
*/
class Model_Language extends ORM_OSB {
protected $_sorting = array(
'name'=>'ASC',
);
protected $_form = array('id'=>'id','value'=>'name');
}
?>

View File

@ -27,6 +27,11 @@ abstract class ORM_OSB extends ORM {
// Our attribute values that need to be stored as serialized
protected $_serialize_column = array();
// If we need to load any sub items on loading this model
protected $_sub_items = array();
protected $_sub_items_load = array();
protected $_sub_items_sorted = FALSE;
// Rules to assist with site ID and getting next record ID for inserts.
public function rules() {
return array(
@ -101,6 +106,25 @@ abstract class ORM_OSB extends ORM {
return parent::__get($column);
}
/**
* Intercept our object load, so that we can load our subitems
*/
protected function _load_values(array $values) {
parent::_load_values($values);
$sort = FALSE;
if ($this->_loaded AND $this->_sub_items_load AND count($this->_sub_items_load) == 1)
foreach ($this->_sub_items_load as $item => $sort)
$this->_sub_items = $this->$item->find_all()->as_array();
if ($sort) {
Sort::MAsort($this->_sub_items,$sort);
$this->_sub_items_sorted = TRUE;
}
return $this;
}
/**
* If a column is marked to be nullified if it is empty, this is where it is done.
*/
@ -266,7 +290,7 @@ abstract class ORM_OSB extends ORM {
if ($this->_form AND array_intersect(array('id','value'),$this->_form))
foreach ($this->find_all() as $o)
$result[$o->{$this->_form['id']}] = $o->{$this->_form['value']};
$result[$o->{$this->_form['id']}] = $o->resolve($this->_form['value']);
return $result;
}

View File

@ -12,8 +12,15 @@
class StaticList_ItemType extends StaticList {
protected function _table() {
return array(
0=>'MAIN', // Line Charge Topic on Invoice, eg: Service Name
5=>'EXCESS', // Excess Service Item, of item 0
0=>_('Product/Service Charge'), // Line Charge Topic on Invoice, eg: Service Name
1=> _('Hardware'),
2=> _('Service Relocation Fee'),
3=> _('Service Change Fee'),
4=> _('Service Connection Fee'),
5=>_('Excess Usage'), // Excess Service Item, of item 0
125=>_('Payment Fee'), // Payment processing fee
126=> _('Rounding'),
127=> _('Late Payment Fee'),
);
}

View File

@ -4,7 +4,6 @@
* OSB Configuration - Authentication
*
* @package OSB
* @subpackage Authentication
* @category Configuration
* @author Deon George
* @copyright (c) 2010 Open Source Billing

View File

@ -4,7 +4,6 @@
* OSB Caching
*
* @package OSB
* @subpackage System
* @category Configuration
* @author Deon George
* @copyright (c) 2010 Open Source Billing

View File

@ -4,7 +4,6 @@
* OSB Configuration - System Default Configurable Items.
*
* @package OSB
* @subpackage System
* @category Configuration
* @author Deon George
* @copyright (c) 2010 Open Source Billing

View File

@ -4,7 +4,6 @@
* OSB Configuration - Database Driver
*
* @package OSB
* @subpackage Database
* @category Configuration
* @author Deon George
* @copyright (c) 2010 Open Source Billing

View File

@ -4,7 +4,6 @@
* OSB Configuration - Debug Settings
*
* @package OSB
* @subpackage Debug
* @category Configuration
* @author Deon George
* @copyright (c) 2010 Open Source Billing

View File

@ -4,7 +4,6 @@
* OSB Configuration - PWGen Configuration
*
* @package OSB
* @subpackage PWGen
* @category PWGen
* @author Deon George
* @copyright (c) 2010 Open Source Billing

View File

@ -0,0 +1,187 @@
<?php defined('SYSPATH') or die('No direct access allowed.');
/**
* Test that PeriodTest is doing as expected
*
* @group osb
* @group osb.helpers
*/
class PeriodTest extends UnitTest_TestCase {
private $DATE = '2012-07-20'; // Fri (5)
private $TIME;
public function __construct() {
parent::__construct();
$this->TIME = strtotime($this->DATE);
$this->w = date('w',$this->TIME);
}
// Weekly
public function test_weekly() {
# +7 week
foreach (array(0,2,6) as $x) {
$p = Period::details(0,NULL,$this->TIME+86400*$x,TRUE,FALSE);
$this->assertEquals(date('d-M-Y',$this->TIME+86400*(7+$x-1)),$p['end']);
}
# +7 week, SUN
foreach (array(0,2,6) as $x) {
$p = Period::details(0,0,$this->TIME+86400*$x,TRUE,FALSE);
$this->assertEquals(date('d-M-Y',$this->TIME+86400*(7-($this->w+$x)%7+$x-1)),$p['end']); // Sat
}
# +7 week, TUE
foreach (array(0,2,6) as $x) {
$p = Period::details(0,2,$this->TIME+86400*$x,TRUE,FALSE);
$this->assertEquals(date('d-M-Y',$this->TIME+86400*(7-($this->w+$x)%7+$x-1+2)),$p['end']); // Sat
}
}
public function test_monthly() {
# +1 month
$p = Period::details(1,NULL,strtotime('2012-07-20'),TRUE,FALSE);
$this->assertEquals(date('d-M-Y',strtotime('2012-08-19')),$p['end']);
$p = Period::details(1,NULL,strtotime('2012-07-01'),TRUE,FALSE);
$this->assertEquals(date('d-M-Y',strtotime('2012-07-31')),$p['end']);
$p = Period::details(1,NULL,strtotime('2012-07-31'),TRUE,FALSE);
$this->assertEquals(date('d-M-Y',strtotime('2012-08-30')),$p['end']);
# +1 month, 1st of the month
$p = Period::details(1,1,strtotime('2012-07-20'),TRUE,FALSE);
$this->assertEquals(date('d-M-Y',strtotime('2012-07-31')),$p['end']);
$p = Period::details(1,1,strtotime('2012-07-01'),TRUE,FALSE);
$this->assertEquals(date('d-M-Y',strtotime('2012-07-31')),$p['end']);
$p = Period::details(1,1,strtotime('2012-07-31'),TRUE,FALSE);
$this->assertEquals(date('d-M-Y',strtotime('2012-07-31')),$p['end']);
# +1 month, 15th of the month
$p = Period::details(1,15,strtotime('2012-07-20'),TRUE,FALSE);
$this->assertEquals(date('d-M-Y',strtotime('2012-08-14')),$p['end']);
$p = Period::details(1,15,strtotime('2012-02-20'),TRUE,FALSE);
$this->assertEquals(date('d-M-Y',strtotime('2012-03-14')),$p['end']);
$p = Period::details(1,15,strtotime('2012-02-14'),TRUE,FALSE);
$this->assertEquals(date('d-M-Y',strtotime('2012-02-14')),$p['end']);
$p = Period::details(1,15,strtotime('2012-02-15'),TRUE,FALSE);
$this->assertEquals(date('d-M-Y',strtotime('2012-03-14')),$p['end']);
# +1 month, 1st of the month, strict doesnt have any affect
$p = Period::details(1,1,strtotime('2012-07-20'),TRUE,TRUE);
$this->assertEquals(date('d-M-Y',strtotime('2012-07-31')),$p['end']);
$p = Period::details(1,1,strtotime('2012-07-01'),TRUE,TRUE);
$this->assertEquals(date('d-M-Y',strtotime('2012-07-31')),$p['end']);
$p = Period::details(1,1,strtotime('2012-07-31'),TRUE,TRUE);
$this->assertEquals(date('d-M-Y',strtotime('2012-07-31')),$p['end']);
}
public function test_quarterly() {
# +3 months
$p = Period::details(2,NULL,strtotime('2012-02-15'),TRUE,FALSE);
$this->assertEquals(date('d-M-Y',strtotime('2012-05-14')),$p['end']);
$p = Period::details(2,NULL,strtotime('2012-11-15'),TRUE,FALSE);
$this->assertEquals(date('d-M-Y',strtotime('2013-02-14')),$p['end']);
$p = Period::details(2,NULL,strtotime('2012-08-20'),TRUE,FALSE);
$this->assertEquals(date('d-M-Y',strtotime('2012-11-19')),$p['end']);
# +3 months, 1st of the month
$p = Period::details(2,1,strtotime('2012-08-20'),TRUE,FALSE);
$this->assertEquals(date('d-M-Y',strtotime('2012-10-31')),$p['end']);
$p = Period::details(2,1,strtotime('2012-11-15'),TRUE,FALSE);
$this->assertEquals(date('d-M-Y',strtotime('2013-01-31')),$p['end']);
# +3 months, 1st of the month, strict JAN-MAR,APR-JUN,JUL-SEP,OCT-DEC
$p = Period::details(2,1,strtotime('2012-11-15'),TRUE,TRUE);
$this->assertEquals(date('d-M-Y',strtotime('2012-12-31')),$p['end']);
$p = Period::details(2,1,strtotime('2013-01-01'),TRUE,TRUE);
$this->assertEquals(date('d-M-Y',strtotime('2013-03-31')),$p['end']);
}
public function test_halfyearly() {
# +6 months
$p = Period::details(3,NULL,strtotime('2012-02-15'),TRUE,FALSE);
$this->assertEquals(date('d-M-Y',strtotime('2012-08-14')),$p['end']);
$p = Period::details(3,NULL,strtotime('2011-09-15'),TRUE,FALSE);
$this->assertEquals(date('d-M-Y',strtotime('2012-03-14')),$p['end']);
# +6 months, 1st of the month
$p = Period::details(3,1,strtotime('2012-02-15'),TRUE,FALSE);
$this->assertEquals(date('d-M-Y',strtotime('2012-07-31')),$p['end']);
$p = Period::details(3,1,strtotime('2011-09-15'),TRUE,FALSE);
$this->assertEquals(date('d-M-Y',strtotime('2012-02-29')),$p['end']);
# +6 months, 1st of the month, strict JAN-JUN,JUL-DEC
$p = Period::details(3,1,strtotime('2012-02-15'),TRUE,TRUE);
$this->assertEquals(date('d-M-Y',strtotime('2012-06-30')),$p['end']);
$p = Period::details(3,1,strtotime('2012-01-01'),TRUE,TRUE);
$this->assertEquals(date('d-M-Y',strtotime('2012-06-30')),$p['end']);
}
public function test_yearly() {
# +1 year
$p = Period::details(4,NULL,strtotime('2012-02-29'),TRUE,FALSE);
$this->assertEquals(date('d-M-Y',strtotime('2013-02-28')),$p['end']);
# +1 year, 1st of the month
$p = Period::details(4,1,strtotime('2012-02-29'),TRUE,FALSE);
$this->assertEquals(date('d-M-Y',strtotime('2013-01-31')),$p['end']);
$p = Period::details(4,1,strtotime('2012-02-01'),TRUE,FALSE);
$this->assertEquals(date('d-M-Y',strtotime('2013-01-31')),$p['end']);
# +1 year, 1st of the month, strict JAN-DEC
$p = Period::details(4,1,strtotime('2012-02-29'),TRUE,TRUE);
$this->assertEquals(date('d-M-Y',strtotime('2012-12-31')),$p['end']);
$p = Period::details(4,1,strtotime('2012-12-31'),TRUE,TRUE);
$this->assertEquals(date('d-M-Y',strtotime('2012-12-31')),$p['end']);
$p = Period::details(4,1,strtotime('2013-02-28'),TRUE,TRUE);
$this->assertEquals(date('d-M-Y',strtotime('2013-12-31')),$p['end']);
$p = Period::details(4,1,strtotime('2013-12-31'),TRUE,TRUE);
$this->assertEquals(date('d-M-Y',strtotime('2013-12-31')),$p['end']);
}
public function test_twoyear() {
# +2 year
$p = Period::details(5,NULL,strtotime('2012-02-29'),TRUE,FALSE);
$this->assertEquals(date('d-M-Y',strtotime('2014-02-28')),$p['end']);
# +2 year, 1st of the month
$p = Period::details(5,1,strtotime('2012-02-29'),TRUE,FALSE);
$this->assertEquals(date('d-M-Y',strtotime('2014-01-31')),$p['end']);
$p = Period::details(5,1,strtotime('2012-02-01'),TRUE,FALSE);
$this->assertEquals(date('d-M-Y',strtotime('2014-01-31')),$p['end']);
# +2 year, 1st of the month, strict JAN/YEAR%2
$p = Period::details(5,1,strtotime('2012-02-29'),TRUE,TRUE);
$this->assertEquals(date('d-M-Y',strtotime('2013-12-31')),$p['end']);
$p = Period::details(5,1,strtotime('2012-01-31'),TRUE,TRUE);
$this->assertEquals(date('d-M-Y',strtotime('2013-12-31')),$p['end']);
$p = Period::details(5,1,strtotime('2013-01-31'),TRUE,TRUE);
$this->assertEquals(date('d-M-Y',strtotime('2014-12-31')),$p['end']);
$p = Period::details(5,1,strtotime('2012-12-31'),TRUE,TRUE);
$this->assertEquals(date('d-M-Y',strtotime('2013-12-31')),$p['end']);
}
}
?>

View File

@ -9,11 +9,9 @@
<?php echo Form::input('email',$o->display('email'),array('label'=>'Email','class'=>'input-xxlarge','placeholder'=>'Email Address','type'=>'email','required')); ?>
<div class="row">
<div class="span1">
<?php echo Form::select('title',StaticList_Title::table(),$o->display('title'),array('class'=>'input-small','label'=>'Title','required')); ?>
</div>
<?php echo Form::select('title',StaticList_Title::table(),$o->display('title'),array('class'=>'input-small','label'=>'Title','required')); ?>
<div class="row">
<div class="span2">
<?php echo Form::input('first_name',$o->display('first_name'),array('label'=>'','class'=>'input-medium','placeholder'=>'First Name','required')); ?>
</div>
@ -21,7 +19,6 @@
<div class="span3">
<?php echo Form::input('last_name',$o->display('last_name'),array('label'=>'','class'=>'input-large','placeholder'=>'Last Name','required')); ?>
</div>
</div>
<?php echo Form::input('company',$o->display('company'),array('label'=>'Company','class'=>'input-xxlarge','placeholder'=>'Company Name')); ?>

View File

@ -18,7 +18,7 @@ class Task_Adsl_Trafficget extends Minion_Task {
protected function _execute(array $params) {
foreach ($this->_traffic_suppliers(TRUE) as $aso) {
if (Minion_CLI::options('verbose'))
if ($params['verbose'])
echo $aso->name."\n";
Service_Traffic_Adsl::instance($aso->name)->update_traffic();

View File

@ -1,133 +0,0 @@
<?php defined('SYSPATH') or die('No direct access allowed.');
/**
* This class provides charge capabilities.
*
* @package Charge
* @category Controllers/Admin
* @author Deon George
* @copyright (c) 2009-2013 Open Source Billing
* @license http://dev.osbill.net/license.html
*/
class Controller_Admin_Charge extends Controller_TemplateDefault_Admin {
protected $secure_actions = array(
'add'=>TRUE,
'list'=>TRUE,
'auditinvoiceitems'=>TRUE,
);
/**
* Show a list of invoices
*/
public function action_list() {
Block::add(array(
'title'=>_('Customer Charges'),
'body'=>Table::display(
ORM::factory('Charge')->where('sweep_type','>=',0)->order_by('date_orig DESC')->find_all(),
25,
array(
'id'=>array('label'=>'ID','url'=>URL::link('user','charge/view/')),
'date_orig'=>array('label'=>'Date'),
'sweep_type'=>array('label'=>'Sweep'),
'status'=>array('label'=>'Status'),
'quantity'=>array('label'=>'Quantity','class'=>'right'),
'amount'=>array('label'=>'Total','class'=>'right'),
'description'=>array('label'=>'Description'),
'service_id'=>array('label'=>'Service'),
'account->accnum()'=>array('label'=>'Cust ID'),
'account->name()'=>array('label'=>'Customer'),
'attributes'=>array('label'=>'Attributes'),
),
array(
'page'=>TRUE,
'type'=>'select',
'form'=>URL::link('user','charge/view'),
)),
));
}
public function action_add() {
$output = '';
$co = ORM::factory('Charge');
if ($_POST) {
// Trim down our attributes
if (is_array($_POST['attributes']))
foreach ($_POST['attributes'] as $k=>$v)
if (! trim($v))
unset($_POST['attributes'][$k]);
if ($co->values($_POST)->check()) {
$co->status=0;
// Entry updated
if (! $co->save())
throw new Kohana_Exception('Unable to save charge');
}
}
$output .= Form::open();
$output .= View::factory($this->viewpath());
$output .= Form::submit('submit','submit');
$output .= Form::close();
Style::add(array(
'type'=>'stdin',
'data'=>'.ui-autocomplete-loading { background: white url("'.URL::site('media/img/ui-anim_basic_16x16.gif').'") right center no-repeat; }'
));
Style::add(array(
'type'=>'file',
'data'=>'media/js/jquery.ui/css/smoothness/jquery-ui-1.8.16.custom.css',
));
Script::add(array(
'type'=>'file',
'data'=>'media/js/jquery-ui-1.8.16.custom.min.js',
));
Script::add(array('type'=>'stdin','data'=>'
$(document).ready(function() {
$("input[name=account_id]").autocomplete({
source: "'.URL::link('admin','account/ajaxlist').'",
minLength: 2,
change: function(event,ui) {
// Send the request and update sub category dropdown
$.ajax({
type: "GET",
data: "aid="+$(this).val(),
dataType: "json",
cache: false,
url: "'.URL::link('admin','service/ajaxlist',TRUE).'",
timeout: 2000,
error: function() {
alert("Failed to submit");
},
success: function(data) {
// Clear all options from sub category select
$("select[name=service_id] option").remove();
// Prepopulate a blank
var row = "<option value=\"\">&nbsp;</option>";
$(row).appendTo("select[name=service_id]");
// Fill sub category select
$.each(data, function(i, j){
var row = "<option value=\"" + j.value + "\">" + j.label + "</option>";
$(row).appendTo("select[name=service_id]");
});
}
});
}
});
});'
));
Block::add(array(
'title'=>_('Add Customer Charges'),
'body'=>$output,
));
}
}
?>

View File

@ -0,0 +1,14 @@
<?php defined('SYSPATH') or die('No direct access allowed.');
/**
* This class provides item charging management
*
* @package Charge
* @category Controllers
* @author Deon George
* @copyright (c) 2009-2013 Open Source Billing
* @license http://dev.osbill.net/license.html
*/
class Controller_Charge extends Controller_TemplateDefault {
}
?>

View File

@ -0,0 +1,171 @@
<?php defined('SYSPATH') or die('No direct access allowed.');
/**
* This class provides charge capabilities.
*
* @package Charge
* @category Controllers/Reseller
* @author Deon George
* @copyright (c) 2009-2013 Open Source Billing
* @license http://dev.osbill.net/license.html
*/
class Controller_Reseller_Charge extends Controller_Charge {
protected $secure_actions = array(
'add'=>TRUE,
'ajaxlist'=>TRUE,
'ajaxlistservice'=>TRUE,
'edit'=>TRUE,
'list'=>TRUE,
);
public function action_add() {
Block::factory()
->type('form-horizontal')
->title('Add/View Charge')
->title_icon('icon-wrench')
->body($this->add_edit());
}
public function action_ajaxlist() {
$result = array();
if (isset($_REQUEST['term']) AND trim($_REQUEST['term'])) {
$result = Arr::merge($result,ORM::factory('Account')->list_autocomplete($_REQUEST['term'],'id','id',array('ACC %s: %s'=>array('id','name(TRUE)'))));
$result = Arr::merge($result,ORM::factory('Service')->list_autocomplete($_REQUEST['term'],'account_id','id',array('ACC %s: %s (%s)'=>array('account_id','account->name()','name()'))));
foreach (array('Service_Plugin_Adsl','Service_Plugin_Domain','Service_Plugin_Host') as $o)
$result = Arr::merge($result,ORM::factory($o)->list_autocomplete($_REQUEST['term'],'account_id','service->account_id',array('ACC %s: %s (%s)'=>array('service->account_id','service->account->name()','name()'))));
}
$this->response->headers('Content-Type','application/json');
$this->response->body(json_encode(array_values($result)));
}
public function action_ajaxlistservice() {
$result = array();
if (isset($_REQUEST['key']) AND trim($_REQUEST['key']))
$result = Arr::merge($result,ORM::factory('Service')->list_autocomplete('','id','id',array('SVC %s: %s'=>array('id','service_name()')),array(array('account_id','=',$_REQUEST['key']))));
$this->response->headers('Content-Type','application/json');
$this->response->body(json_encode(array_values($result)));
}
private function add_edit($id=NULL,$output='') {
$co = ORM::factory('Charge',$id);
if ($_POST) {
// Entry updated
if ($co->values($_POST)->check() AND $co->save())
SystemMessage::factory()
->title('Record updated')
->type('success')
->body(_('Your Charge record has been recorded/updated.'));
}
Script::factory()
->type('file')
->data('media/theme/bootstrap/vendor/datepicker/js/bootstrap-datepicker.js');
Style::factory()
->type('file')
->data('media/theme/bootstrap/vendor/datepicker/css/datepicker.css');
Script::factory()
->type('stdin')
->data('
$(document).ready(function() {
var nowTemp = new Date();
var now = new Date(nowTemp.getFullYear(), nowTemp.getMonth(), nowTemp.getDate(), 0, 0, 0, 0);
$("#date_charge_label").datepicker({
autoclose : true,
endDate : now,
format : "dd-mm-yyyy",
todayBtn : true,
}).on("hide",function(ev) {
$("input[name=date_charge]").val(ev.date.valueOf()/1000);
});
$("input[name=account_id_label]").typeahead({
minLength: 2,
source: function (query,process) {
search("'.URL::link('reseller','charge/ajaxlist').'",query,process);
},
matcher: function () { return true; },
updater: function (item) {
$("input[name=account_id]").val(users[item]);
// Send the request and update sub category dropdown
$.ajax({
type: "GET",
data: "key="+users[item],
dataType: "json",
cache: false,
url: "'.URL::link('reseller','charge/ajaxlistservice',TRUE).'",
timeout: 2000,
error: function(x) {
alert("Failed to submit");
},
success: function(data) {
$.each(data, function(i, j){
var row = "<option value=\"" + j.value + "\">" + j.label + "</option>";
$(row).appendTo("select[name=service_id]");
});
}
});
return item;
},
});
});
');
return View::factory('charge/reseller/add_edit')
->set('o',$co);
}
public function action_edit() {
list($id,$output) = Table::page(__METHOD__);
Block::factory()
->type('form-horizontal')
->title(sprintf('%s: %s',_('View Charges'),$id))
->title_icon('icon-wrench')
->body($this->add_edit($id,$output));
}
/**
* Show a list of invoices
*/
public function action_list() {
Block::factory()
->title('Customer Charges')
->title_icon('icon-th-list')
->body(Table::factory()
->page_items(50)
->data(ORM::factory('Charge')->where('account_id','IN',$this->ao->RTM->customers($this->ao->RTM))->order_by('id DESC')->find_all())
->columns(array(
'id'=>'ID',
'date_orig'=>'Processed',
'date_charge'=>'Date',
'sweep_type'=>'Sweep',
'status'=>'Status',
'quantity'=>'Quantity',
'amount'=>'Amount',
'description'=>'Description',
'service_id'=>'Service',
'account->accnum()'=>'Cust ID',
'account->name()'=>'Customer',
))
->prepend(array(
'id'=>array('url'=>URL::link('reseller','charge/edit/')),
))
);
}
}
?>

View File

@ -10,21 +10,25 @@
* @license http://dev.osbill.net/license.html
*/
class Model_Charge extends ORM_OSB {
// Charge doesnt use the update column
protected $_updated_column = FALSE;
protected $_belongs_to = array(
'account'=>array(),
);
protected $_nullifempty = array(
'attributes',
);
protected $_serialize_column = array(
'attributes',
);
protected $_belongs_to = array(
'account'=>array(),
);
protected $_display_filters = array(
'date_orig'=>array(
array('Config::date',array(':value')),
),
'date_charge'=>array(
array('Config::date',array(':value')),
),
'amount'=>array(
array('Currency::display',array(':value')),
),

View File

@ -1,37 +0,0 @@
<table border="1">
<tr>
<td>Account</td>
<td><?php echo Form::input('account_id',''); ?></td>
</tr>
<tr>
<td>Service</td>
<td><?php echo Form::select('service_id',array('NONE')); ?></td>
</tr>
<tr>
<td>Sweep</td>
<td><?php echo StaticList_SweepType::form('sweep_type',6); ?></td>
</tr>
<tr>
<td>Quantity</td>
<td><?php echo Form::input('quantity',NULL); ?></td>
</tr>
<tr>
<td>Amount</td>
<td><?php echo Form::input('amount',NULL); ?></td>
</tr>
<tr>
<td>Taxable</td>
<td><?php echo StaticList_YesNo::form('taxable',true); ?></td>
</tr>
<tr>
<td>Description</td>
<td><?php echo Form::input('description',NULL); ?></td>
</tr>
<!-- @todo This to be dynamic -->
<?php for($x=0;$x<10;$x++) { ?>
<tr>
<td>Attributes</td>
<td><?php echo Form::input('attributes['.$x.']',NULL); ?></td>
</tr>
<?php } ?>
</table>

View File

@ -0,0 +1,31 @@
<div class="row">
<div class="span9 offset1">
<fieldset>
<legend>Charge Details</legend>
<div class="input-append date" id="date_charge_label">
<?php echo Form::input('date_charge_label',$o->display('date_charge'),array('class'=>'span2','label'=>'Date of Charge','add-on'=>'<i class="icon-calendar"></i>','disabled')); ?>
</div>
<?php echo Form::hidden('date_charge',$o->date_charge); ?>
<?php echo Form::input('account_id_label',$o->account->name(),array('class'=>'span5','label'=>'Account','placeholder'=>'Account','data-provide'=>'typeahead','required')); ?>
<?php echo Form::hidden('account_id',$o->account_id); ?>
<?php echo Form::select('service_id',$o->account_id ? $o->account->service->list_select() : array(),$o->service_id,array('class'=>'span5','label'=>'Service')); ?>
<?php echo StaticList_SweepType::form('sweep_type',is_null($o->sweep_type) ? 6 : $o->sweep_type,FALSE,array('label'=>'Sweep')); ?>
<?php echo StaticList_ItemType::form('type',is_null($o->type) ? 6 : $o->type,FALSE,array('label'=>'Item Type')); ?>
<?php echo Form::input('quantity',$o->quantity,array('class'=>'span1','label'=>'Quantity','placeholder'=>'Quantity')); ?>
<?php echo Form::input('amount',$o->amount,array('class'=>'span1','label'=>'Amount','placeholder'=>'Total',)); ?>
<?php echo StaticList_YesNo::form('taxable',is_null($o->taxable) ? TRUE : $o->taxable,FALSE,array('label'=>'Taxable','class'=>'span1')); ?>
<?php echo Form::input('description',$o->description,array('class'=>'span5','label'=>'Description','placeholder'=>'Any notes about this charge?')); ?>
<!-- @todo Use JS to dynamically add more lines as required -->
<?php for ($i=0;$i<10;$i++) :
echo Form::input("attributes[$i]",isset($o->attributes[$i]) ? $o->attributes[$i] : "",array('class'=>'span5','label'=>'Attributes'));
endfor ?>
</fieldset>
<?php echo Form::button('submit','Submit',array('class'=>'btn btn-primary')); ?>
</div> <!-- /span -->
</div> <!-- /row -->

View File

@ -10,7 +10,7 @@
<dd><?php echo $o->display('domain_expire'); ?></dd>
<dt>Domain Auth Password</dt>
<dd><?php echo $o->display('registrar_auth_password'); ?></dd>
<dd><?php echo ! $o->service->expiring() ? $o->display('registrar_auth_password') : 'EXPIRED'; ?></dd>
<?php if ($x=$o->manage_button()) : ?>
<dt>Registrar</dt>

View File

@ -12,155 +12,6 @@
class Controller_Task_Invoice extends Controller_Task {
public $auto_render = FALSE;
/**
* Email a customers a reminder of their upcoming invoices that are due.
*/
public function action_remind_due() {
$action = array();
$key = 'remind_due';
$days = ORM::factory('Invoice')->config('REMIND_DUE');
foreach (ORM::factory('Invoice')->list_due(time()+86400*$days) as $io) {
// @todo Use another option to supress reminders
// If we have already sent a reminder, we'll skip to the next one.
if (($io->remind($key) AND (is_null($x=$this->request->param('id')) OR $x != 'again')) OR ($io->account->invoice_delivery != 1))
continue;
// Send our email
$et = Email_Template::instance('task_invoice_'.$key);
$et->to = array('account'=>array($io->account_id));
$et->variables = array(
'DUE'=>$io->due(TRUE),
'DUE_DATE'=>$io->display('due_date'),
'FIRST_NAME'=>$io->account->first_name,
'INV_NUM'=>$io->refnum(),
'INV_URL'=>URL::site(URL::link('user','invoice/view/'.$io->id),'http'),
'SITE_NAME'=>Company::instance()->name(),
);
// @todo Record email log id if possible.
if ($et->send()) {
$io->set_remind($key,time());
array_push($action,(string)$io);
}
}
$this->response->body(_('Due 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) - multiple services colon separated
*/
public function action_services() {
// Used to only process X invoices in a row.
$max = ($x=Kohana::$config->load('debug')->invoice) ? $x : ORM::factory('Invoice')->config('GEN_INV_MAX');
// Our service next billing dates that need to be updated if this is successful.
$snd = array();
// Our charges that need to be updated if this is successful.
$chgs = array();
// If we are invoicing a specific service
$sid = is_null($this->request->param('id')) ? NULL : explode(':',$this->request->param('id'));
// Sort our service by account_id, then we can generate 1 invoice.
$svs = ORM::factory('Service')->list_invoicesoon()->as_array();
Sort::MAsort($svs,'account_id,date_next_invoice');
$aid = $due = $io = NULL;
$max_count = 0;
foreach ($svs as $so) {
// If we are generating an invoice for a service, skip to that service.
if (! is_null($sid) AND ! in_array($so->id,$sid))
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_object($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));
}
// If we have issued the max number of invoices this round, finish.
if ($max AND (++$max_count > $max))
break;
// 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;
}
$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 = $pdata['prorata'];
$iio->item_type = 0; // Service Billing
$iio->discount_amt = NULL; // @todo
$iio->price_base = $so->price();
$iio->recurring_schedule = $so->recur_schedule;
$iio->date_start = $pdata['start_time'];
$iio->date_stop = $pdata['end_time'];
// Our service next billing date, if this invoice generation is successful.
$snd[$so->id] = $pdata['end_time']+86400;
// Check if there are any charges
$c = ORM::factory('Charge')
->where('service_id','=',$so->id)
->where('status','=',0)
->where('sweep_type','=',6); // @todo This needs to be dynamic, not "6"
foreach ($c->find_all() as $co) {
$iio = $io->add_item();
$iio->service_id = $co->service_id;
$iio->product_id = $co->product_id;
$iio->charge_id = $co->id;
$iio->quantity = $co->quantity;
$iio->item_type = 5; // @todo This probably should not be hard coded as "5".
$iio->discount_amt = NULL; // @todo
$iio->price_base = $co->amount;
$iio->date_start = $co->date_orig;
$iio->date_stop = $co->date_orig; // @todo
// @todo Temp
// We'll mark any charges as temporarily processed, although they should be set to status=1 later.
$co->status=2;
$co->save();
array_push($chgs,$co->id);
}
}
// Save our invoice.
if ($io AND ! $io->saved() AND ! $io->save()) {
print_r($io->items());
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.
// @todo Catch any update errors
foreach ($snd as $sid=>$date) {
$so = ORM::factory('Service',$sid);
$so->date_next_invoice = $date;
$so->save();
}
// Update any processed charges as such
// @todo Catch any update errors
foreach ($chgs as $cid) {
$co = ORM::factory('Charge',$cid);
$co->status=1;
$co->save();
}
$this->response->body(_('Services Invoiced: ').join('|',array_keys($snd)));
}
public function action_send() {
// Used to only process X invoices in a row.
$max = ORM::factory('Invoice')->config('EMAIL_INV_MAX');

View File

@ -67,7 +67,7 @@ class Controller_User_Invoice extends Controller_Invoice {
->set('o',$io);
if ($io->due() AND ! $io->cart_exists())
$output .= View::factory('/invoice/user/view/pay')
$output .= View::factory('invoice/user/view/pay')
->set('mid',$io->mid())
->set('o',$io);

View File

@ -1,16 +0,0 @@
<?php defined('SYSPATH') or die('No direct access allowed.');
/**
* This interface ensures that all invoice processing objects have the right methods
*
* @package Invoice
* @category Interface
* @author Deon George
* @copyright (c) 2009-2013 Open Source Billing
* @license http://dev.osbill.net/license.html
*/
interface Invoicable {
// Render the line item for an invoice
public function invoice_line();
}
?>

View File

@ -13,8 +13,26 @@ class Invoice {
// This invoice Object
private $io;
private $_methods = array(
'min_due',
'save',
'saved',
);
// Enable passing calls to ORM::Invoice()
public function __call($method,array $args) {
if (in_array($method,$this->_methods))
return call_user_func_array(array($this->io,$method),$args);
else
throw HTTP_Exception::factory(501,'Unknown/unauthorised method :method',array(':method'=>$method));
}
public function __construct(Model_Invoice $io=NULL) {
$this->io = is_null($io) ? ORM::factory('Invoice') : $io;
// Set our invoice as valid
if (is_null($io))
$this->io->status = 1;
}
public static function instance(Model_Invoice $io=NULL) {
@ -27,6 +45,19 @@ class Invoice {
* @param $so Model_Servie
*/
public function add_service(Model_Service $so) {
if ($this->io->loaded())
throw HTTP_Exception::factory(501,'Cannot use add service :service to an existing invoice :invoice ',array(':service'=>$so->id,':invoice'=>$this->io->id));
if (! $this->io->account_id)
$this->io->account_id = $so->account_id;
else if ($this->io->account_id != $so->account_id)
throw HTTP_Exception::factory(501,'Cannot add service :service to invoice - it is for a different account',array(':service'=>$so->id));
// Set the invoice due date
if (! $this->io->due_date OR $this->io->due_date > $this->io->min_due($so->date_next_invoice))
$this->io->due_date = $this->io->min_due($so->date_next_invoice);
$pdata = Period::details($so->recur_schedule,$so->product->price_recurr_day,$so->invoiced_to()+86400,FALSE,$so->product->price_recurr_strict);
$iio = ORM::factory('Invoice_Item');
@ -39,13 +70,14 @@ class Invoice {
$iio->date_stop = $pdata['end_time'];
// Service Billing
$iio->item_type = StaticList_ItemType::index('MAIN');
$iio->item_type = 0;
$this->io->add_item($iio);
// Check if there are any charges
$c = ORM::factory('Charge')
->where('service_id','=',$so->id)
->where('void','is',NULL)
->where('processed','is',NULL);
foreach ($c->find_all() as $co) {
@ -57,9 +89,7 @@ class Invoice {
$iio->price_base = $co->amount;
$iio->date_start = $co->date_orig;
$iio->date_stop = $co->date_orig;
// @todo This should be $co->item_type;
$iio->item_type = StaticList_ItemType::index('EXCESS');
$iio->item_type = $co->type;
$this->io->add_item($iio);
}
@ -67,12 +97,13 @@ class Invoice {
return $this;
}
public function render($type,$section) {
public function render($type,$section,$args=array()) {
switch ($type) {
case 'html':
switch ($section) {
case 'body':
return View::factory('invoice/user/view/body')
->set('show_id',(isset($args['noid']) AND $args['noid']) ? FALSE : TRUE)
->set('o',$this->io);
break;

View File

@ -38,8 +38,9 @@ class Model_Invoice extends ORM_OSB implements Cartable {
);
// Items belonging to an invoice
private $invoice_items = array();
private $invoice_items_unsorted = TRUE;
protected $_sub_items_load = array(
'invoice_item'=>'service_id,item_type',
);
/** INTERFACE REQUIREMENTS **/
public function cart_item() {
@ -53,32 +54,21 @@ class Model_Invoice extends ORM_OSB implements Cartable {
return count(Cart::instance()->get($this->mid(),$this->id));
}
/**
* Intercept our object load, so that we can load our subitems
*/
protected function _load_values(array $values) {
parent::_load_values($values);
if ($this->_loaded)
$this->invoice_items = $this->invoice_item->find_all()->as_array();
return $this;
}
/**
* Add an item to an invoice
*/
public function add_item(Model_Invoice_Item $iio=NULL) {
// @todo This is the old calling of this function, which should be removed
if (is_null($iio)) {
$c = count($this->invoice_items);
$c = count($this->_sub_items);
$this->invoice_items[$c] = ORM::factory('Invoice_Item');
$this->_sub_items[$c] = ORM::factory('Invoice_Item');
return $this->invoice_items[$c];
return $this->_sub_items[$c];
}
array_push($this->invoice_items,$iio);
array_push($this->_sub_items,$iio);
$this->_sub_items_sorted = FALSE;
}
/**
@ -86,7 +76,7 @@ class Model_Invoice extends ORM_OSB implements Cartable {
*
* @param $period Return an Array of items for that period
*/
public function items_periods($period=NULL) {
public function items_periods($period=FALSE) {
$result = array();
foreach ($this->items() as $ito) {
@ -94,15 +84,60 @@ class Model_Invoice extends ORM_OSB implements Cartable {
if ($ito->item_type != 0)
continue;
if (is_null($period) AND ! in_array($ito->recurring_schedule,$result))
if ($period === FALSE AND ! in_array($ito->recurring_schedule,$result))
array_push($result,$ito->recurring_schedule);
elseif ($ito->recurring_schedule == $period)
array_push($result,$ito);
elseif ($ito->recurring_schedule == $period) {
// If we have this service already, we'll skip
if (! Object::in_array('service_id',$ito->service_id,$result))
array_push($result,$ito);
}
}
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();
foreach ($this->items() as $ito) {
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');
}
}
return $result;
}
/**
* 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;
foreach ($this->items() as $iio)
if ($iio->item_type == 0 AND $iio->service_id == $o->service_id)
array_push($result,$iio);
return $result;
}
/**
* Return the extra items on an invoice relating to an invoice_item
* IE: item_type != 0
@ -195,7 +230,7 @@ class Model_Invoice extends ORM_OSB implements Cartable {
public function items($type='ALL') {
$result = array();
foreach ($this->invoice_items as $ito) {
foreach ($this->_sub_items as $ito) {
$return = FALSE;
switch ($type) {
@ -429,9 +464,11 @@ class Model_Invoice extends ORM_OSB implements Cartable {
// Our items will be clobbered once we save the object, so we need to save it here.
$items = $this->items();
// If this is a new invoice, we'll need to do more work
$new = ! $this->loaded();
// Save the invoice
if ($this->changed())
parent::save($validation);
parent::save($validation);
// Need to save the associated items and their taxes
if ($this->loaded()) {
@ -453,6 +490,27 @@ class Model_Invoice extends ORM_OSB implements Cartable {
throw new Kohana_Exception('Problem saving invoice_item for invoice :invoice - Failed save()',array(':invoice'=>$this->id));
}
// 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 ($iio->charge_id) {
$iio->charge->processed = 1;
// @todo Mark invoice as cancelled and write a memo, then...
if (! $iio->charge->save())
throw new Kohana_Exception('Problem saving charge :charge for invoice_item :invoice_item - Failed save()',array(':invoice_item'=>$iio->id,':charge'=>$iio->charge_id));
}
}
// @todo Need to save discount information
}

View File

@ -12,7 +12,7 @@
* Column Definitions:
* + item_type: 0=MAIN Service Item,2=?,3=?,4=Connection/Setup,5=Excess Service Item,6=Change Service,126=Payment Fee,127=Late Fee
*/
class Model_Invoice_Item extends ORM_OSB implements Invoicable {
class Model_Invoice_Item extends ORM_OSB {
// Relationships
protected $_belongs_to = array(
'product'=>array(),
@ -38,11 +38,21 @@ class Model_Invoice_Item extends ORM_OSB implements Invoicable {
),
);
// Items belonging to an invoice
private $subitems = array();
private $subitems_loaded = FALSE;
// Items belonging to an invoice item
protected $_sub_items_load = array(
'invoice_item_tax'=>FALSE,
);
/**
* The title for invoice items
*/
public function title() {
if ($this->product_id AND $this->service_id)
return $this->product_id ? sprintf('%s: %s',$this->product->title(),$this->service->name()) : $this->service->name;
else
return 'Unknown Item';
}
/** INTERFACE REQUIREMENTS **/
public function invoice_line() {
if ($this->charge_id)
return $this->charge->description.' '.$this->period();
@ -52,23 +62,6 @@ class Model_Invoice_Item extends ORM_OSB implements Invoicable {
throw HTTP_Exception::factory(501,'Unable to render invoice item :id',array(':id'=>$this->id));
}
public function __construct($id = NULL) {
// Load our model.
parent::__construct($id);
return $this->load_sub_items();
}
private function load_sub_items() {
// Load our sub items
if (! $this->subitems_loaded AND $this->loaded()) {
$this->subitems['tax'] = $this->invoice_item_tax->find_all()->as_array();
$this->subitems_loaded = TRUE;
}
return $this;
}
// Display a transaction number
public function trannum() {
return sprintf('%03s-%06s',$this->item_type,$this->id);
@ -125,7 +118,7 @@ class Model_Invoice_Item extends ORM_OSB implements Invoicable {
}
public function total($format=FALSE) {
$result = Currency::round($this->subtotal()+$this->tax()-$this->discount());
$result = $this->void ? 0 : Currency::round($this->subtotal()+$this->tax()-$this->discount());
return $format ? Currency::display($result) : $result;
}
@ -205,9 +198,9 @@ class Model_Invoice_Item extends ORM_OSB implements Invoicable {
if ($this->saved()) {
$iito = ORM::factory('Invoice_Item_Tax');
if ($this->subitems_loaded) {
if ($this->_sub_items) {
foreach (array('tax') as $i)
foreach ($this->subitems[$i] as $io)
foreach ($this->_sub_items[$i] as $io)
if ($io->changed())
$io->save();

View File

@ -0,0 +1,64 @@
<?php defined('SYSPATH') or die('No direct access allowed.');
/**
* Generate customer invoices for services
*
* @package Invoice
* @category Tasks
* @author Deon George
* @copyright (c) 2009-2013 Open Source Billing
* @license http://dev.osbill.net/license.html
*/
class Task_Invoice_Service extends Minion_Task {
protected $_options = array(
'days'=>1, // Days in advance to generate for
'id'=>NULL, // Service invoice to generate for
);
protected function _execute(array $params) {
// Used to only process X invoices in a row.
$max = ($x=Kohana::$config->load('debug')->invoice) ? $x : ORM::factory('Invoice')->config('GEN_INV_MAX');
// Sort our service by account_id, then we can generate 1 invoice.
$svs = ORM::factory('Service')->list_invoicesoon($params['days'])->as_array();
Sort::MAsort($svs,'account_id');
$sids = $params['id'] ? explode(':',$params['id']) : array();
$aid = $io = NULL;
$max_count = 0;
$action = array();
foreach ($svs as $so) {
// If we are generating an invoice for a service, skip to that service.
if ($sids AND ! in_array($so->id,$sids))
continue;
// Close off invoice, and start a new one, if this is for a different account.
if (is_null($io) OR (! is_null($aid) AND $aid != $so->account_id)) {
// Close and save this invoice.
if (is_object($io) AND ! $io->save())
throw new Kohana_Exception('Failed to save invoice :invoice for service :service',array(':invoice'=>$io->id,':service'=>$so->id));
// If we have issued the max number of invoices this round, finish.
if ($max AND (++$max_count > $max))
break;
// Start a new invoice.
$io = Invoice::instance();
$aid = $so->account_id;
}
$io->add_service($so);
// Record what we have updated.
array_push($action,$so->id);
}
// Save our invoice.
if ($io AND ! $io->saved() AND ! $io->save())
throw new Kohana_Exception('Failed to save invoice :invoice for service :service',array(':invoice'=>$io->id,':service'=>$so->id));
return _('Services Invoiced: ').join('|',$action);
}
}
?>

View File

@ -4,7 +4,6 @@
* OSB Configuration - Invoice Driver
*
* @package OSB
* @subpackage Invoice
* @category Configuration
* @author Deon George
* @copyright (c) 2010 Open Source Billing

View File

@ -29,7 +29,7 @@
<dd><?php echo $o->display('due_date'); ?></dd>
<dt>Current Charges</dt>
<dd><?php echo $o->total_charges(TRUE); ?></dd>
<dt>Payments Recieved</dt>
<dt>Payments Received</dt>
<dd><?php echo $o->payments_total(TRUE); ?></dd>
<dt>Credits Applied</dt>
<dd><?php echo $o->total_credits(TRUE); ?></dd>
@ -45,55 +45,7 @@
<div class="span11">
<h4>Charge Details</h4>
<table class="table table-striped table-condensed table-hover" id="list-table" border="0">
<tbody>
<?php foreach ($o->items_periods() as $rs) : ?>
<tr><th colspan="5"><?php echo StaticList_RecurSchedule::get($rs); ?></th></tr>
<?php foreach ($o->items_periods($rs) as $iio) : ?>
<?php if ($iio->service_id) : ?>
<tr>
<th>&nbsp;</th>
<!-- @todo product->title() should be changed to show the service_name() for the invoice product item -->
<th colspan="2"><?php echo HTML::anchor(URL::link('user','service/view/'.$iio->service_id),$iio->service->id()).' '.$iio->product->title(); ?></th>
<th>&nbsp;</th>
<th><div class="text-right"><?php echo $o->service_items_total($iio,TRUE); ?></div></th>
</tr>
<tr>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td><?php printf('%s (%s)',$iio->invoice_line(),$iio->display('id')); ?></td>
<td><div class="text-right"><?php echo $iio->subtotal(TRUE); ?></div></td>
<td>&nbsp;</td>
</tr>
<?php if ($x=$o->service_items_extra($iio)) : ?>
<?php foreach ($x as $eiio) : ?>
<tr>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td><?php printf('%s (%s)',$eiio->invoice_line(),$eiio->display('id')); ?></td>
<td><div class="text-right"><?php echo $eiio->subtotal(TRUE); ?></div></td>
<td>&nbsp;</td>
</tr>
<?php endforeach ?>
<?php endif ?>
<tr>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td><?php echo 'Taxes'; ?></td>
<td><div class="text-right"><?php echo $o->service_items_tax($iio,TRUE); ?></div></td>
<td>&nbsp;</td>
</tr>
<?php else : ?>
<?php endif ?>
<?php endforeach ?>
<?php endforeach ?>
</tbody>
</table>
<?php echo Invoice::instance($o)->render('html','body'); ?>
</div> <!-- /span -->
</div>

View File

@ -7,22 +7,28 @@
<?php if ($iio->service_id) : ?>
<tr>
<th>&nbsp;</th>
<th colspan="2"><?php echo $iio->service->service_name(); ?></th>
<th colspan="2"><?php echo ($show_id ? HTML::anchor(URL::link('user','service/view/'.$iio->service_id),$iio->service->id()).' ' : '').$iio->service->service_name(); ?></th>
<th>&nbsp;</th>
<th><div class="text-right"><?php echo $o->service_items_total($iio,TRUE); ?></div></th>
</tr>
<tr>
<td colspan="2">&nbsp;</td>
<td><?php echo $iio->invoice_line(); ?></td>
<td><div class="text-right"><?php echo $iio->total(TRUE); ?></div></td>
<td>&nbsp;</td>
</tr>
<?php if ($x=$o->service_items($iio)) : ?>
<?php foreach ($x as $eiio) : ?>
<tr>
<td colspan="1">&nbsp;</td>
<td colspan="1">&nbsp;</td>
<td><?php echo $eiio->invoice_line().($show_id ? " (<small>$eiio->id</small>)" : ''); ?></td>
<td><div class="text-right"><?php echo $eiio->total(TRUE); ?></div></td>
<td>&nbsp;</td>
</tr>
<?php endforeach ?>
<?php endif ?>
<?php if ($x=$o->service_items_extra($iio)) : ?>
<?php foreach ($x as $eiio) : ?>
<tr>
<td colspan="2">&nbsp;</td>
<td><?php echo $eiio->invoice_line(); ?></td>
<td><?php echo $eiio->invoice_line().($show_id ? " (<small>$eiio->id</small>)" : ''); ?></td>
<td><div class="text-right"><?php echo $eiio->total(TRUE); ?></div></td>
<td>&nbsp;</td>
</tr>

@ -1 +1 @@
Subproject commit d3627ace8fc427abe493044f387a802dfc7f09d1
Subproject commit 9e191ab0be69f296ae3cd2f079aafa80210f6928

View File

@ -4,7 +4,6 @@
* OAuth Configuration - System Default Configurable Items.
*
* @package OAuth
* @subpackage Facebook
* @category Configuration
* @author Deon George
* @copyright (c) 2010 Open Source Billing

View File

@ -131,7 +131,7 @@ $(document).ready(function() {
$("input[name=account_id_label]").typeahead({
minLength: 2,
source: function (query,process) {
search("a/payment/ajaxlist",query,process);
search("'.URL::link('admin','payment/ajaxlist').'",query,process);
},
matcher: function () { return true; },

View File

@ -39,34 +39,23 @@ class Model_Payment extends ORM_OSB {
),
);
// Items belonging to an invoice
private $payment_items = array();
private $payment_items_unsorted = TRUE;
/**
* Intercept our object load, so that we can load our subitems
*/
protected function _load_values(array $values) {
parent::_load_values($values);
if ($this->_loaded)
$this->payment_items = $this->payment_item->find_all()->as_array();
return $this;
}
// Items belonging to a payment
protected $_sub_items_load = array(
'payment_item'=>FALSE,
);
/**
* Add an item to an payment
*/
public function add_item(Model_Payment_Item $p=NULL) {
$this->payment_items_unsorted = TRUE;
$this->_sub_items_sorted = FALSE;
// Make sure this payment item for an invoice is not already in the list
foreach ($this->payment_items as $pio)
foreach ($this->_sub_items as $pio)
if ($pio->invoice_id == $p->invoice_id)
return $pio;
array_push($this->payment_items,$p);
array_push($this->_sub_items,$p);
return $p;
}
@ -101,7 +90,7 @@ class Model_Payment extends ORM_OSB {
public function invoices() {
$result = array();
foreach ($this->payment_items as $pio)
foreach ($this->_sub_items as $pio)
array_push($result,$pio->invoice);
return $result;
@ -117,14 +106,14 @@ class Model_Payment extends ORM_OSB {
* @see payment_items
*/
public function items($type='ALL') {
if ($this->payment_items_unsorted) {
Sort::MAsort($this->payment_items,'invoice_id');
$this->payment_items_unsorted = FALSE;
if (! $this->_sub_items_sorted) {
Sort::MAsort($this->_sub_items,'invoice_id');
$this->_sub_items_sorted = TRUE;
}
$result = array();
foreach ($this->payment_items as $pio) {
foreach ($this->_sub_items as $pio) {
$return = FALSE;
switch ($type) {

View File

@ -11,7 +11,6 @@
*/
class Controller_Admin_Service extends Controller_Service {
protected $secure_actions = array(
'ajaxlist'=>TRUE,
'ajaxjson_traffic'=>TRUE,
'adslstat'=>TRUE,
'listadslbilling'=>TRUE,
@ -25,22 +24,6 @@ class Controller_Admin_Service extends Controller_Service {
'view'=>TRUE,
);
public function action_ajaxlist() {
$result = array();
$result += ORM::factory('Service')->list_autocomplete(
isset($_REQUEST['term']) ? $_REQUEST['term'] : '',
'id',
'id',
array('SVC %s: %s'=>array('id','service_name(TRUE)')),
isset($_REQUEST['aid']) ? array(array('account_id','=',$_REQUEST['aid'])) : array()
);
$this->auto_render = FALSE;
$this->response->headers('Content-Type','application/json');
$this->response->body(json_encode(array_values($result)));
}
public function action_ajaxjson_traffic() {
$result = array();
$svs = ORM::factory('Service')->list_bylistgroup('ADSL');
@ -54,7 +37,6 @@ class Controller_Admin_Service extends Controller_Service {
$google->sdata(array('yr'=>'services'),array('services'=>$data['svs']));
$this->auto_render = FALSE;
$this->response->headers('Content-Type','application/json');
$this->response->body($google->json());
}
@ -538,7 +520,7 @@ class Controller_Admin_Service extends Controller_Service {
if (is_null($bt))
$bt = $iio->date_start;
$pdata = Period::details($iio->recurring_schedule,$a ? NULL : $iio->product->price_recurr_weekday,$bt,TRUE);
$pdata = Period::details($iio->recurring_schedule,$a ? NULL : $iio->product->price_recurr_weekday,$bt,TRUE,TRUE);
switch ($iio->recurring_schedule) {
case 1:

View File

@ -46,6 +46,8 @@ class Model_Service extends ORM_OSB {
),
);
protected $_form = array('id'=>'id','value'=>'service_name()');
/**
* Get the additional charges associated with this service
*/
@ -63,11 +65,11 @@ class Model_Service extends ORM_OSB {
if ($unprocessed)
$x->where_open()
->where('processed','IS',NULL)
->or_where('processed','=',FALSE)
->where_close();
->where('processed','IS',NULL)
->or_where('processed','=',FALSE)
->where_close();
return $x->find_all();
return $x->and_where('void','IS',NULL)->find_all();
}
/**
@ -132,9 +134,24 @@ class Model_Service extends ORM_OSB {
* Show the date we are invoiced to
*/
public function invoiced_to($format=FALSE) {
$x = $this->invoice_item->order_by('date_stop','DESC')->limit(1)->find();
$iio = $this->last_invoice_item()
->limit(1);
return $format ? $x->display('date_stop') : $x->date_stop;
$iio->find();
$x = (! $iio->loaded() AND $this->date_next_invoice) ? $this->date_next_invoice-86400 : $iio->date_stop;
return $format ? Config::date($x) : $x;
}
private function last_invoice_item() {
return ORM::factory('Invoice_Item')->join('invoice')
->on('invoice.id','=','invoice_item.invoice_id')
->on('invoice.status','=',1)
->on('invoice_item.service_id','=',$this)
->on('invoice_item.item_type','=',0)
->on('invoice_item.void','is','null')
->order_by('date_stop','DESC');
}
/**
@ -150,7 +167,7 @@ class Model_Service extends ORM_OSB {
public function paid_to($format=FALSE) {
$x = NULL;
foreach ($this->invoice_item->order_by('date_stop','DESC')->order_by('date_orig','DESC')->find_all() as $iio)
foreach ($this->last_invoice_item()->order_by('date_orig','DESC')->find_all() as $iio)
if ($iio->invoice->due() == 0) {
$x = $iio;
break;
@ -203,7 +220,7 @@ class Model_Service extends ORM_OSB {
$x = $this->product->keyget('price_group',$this->recur_schedule);
// @todo This index shouldnt be hard coded.
$p = $this->price ? $this->price : $x[$this->price_group]['price_base'];
$p = ! is_null($this->price) ? $this->price : $x[$this->price_group]['price_base'];
if ($tax)
$p = Tax::add($p);
@ -250,7 +267,7 @@ class Model_Service extends ORM_OSB {
*/
public function list_autocomplete($term,$index,$value,array $label,array $limit=array(),array $options=NULL) {
// We only show service numbers.
if (! is_numeric($term))
if (! is_numeric($term) AND (! $limit))
return array();
$ao = Auth::instance()->get_user();

View File

@ -47,7 +47,7 @@ abstract class Model_Service_Plugin extends ORM_OSB {
abstract public function password_value();
public function manage_button() {
if (! $this->service->status)
if (! $this->service->status OR $this->service->expiring())
return FALSE;
static $k = '';

View File

@ -90,6 +90,6 @@
<fieldset class="span5">
<legend>Next Invoice Charges</legend>
<?php echo Invoice::instance()->add_service($o)->render('html','body'); ?>
<?php echo Invoice::instance()->add_service($o)->render('html','body',array('noid'=>TRUE)); ?>
</fieldset>
</div> <!-- /row -->

View File

@ -10,6 +10,10 @@
* @license http://dev.osbill.net/license.html
*/
class Task_SSL_Renew extends Minion_Task {
protected $_options = array(
'id'=>NULL,
);
/**
* Renew a certificate
*/

View File

@ -4,7 +4,6 @@
* OSB Configuration - SSL Config Items
*
* @package OSB
* @subpackage System
* @category Configuration
* @author Deon George
* @copyright (c) 2010 Open Source Billing

View File

@ -4,7 +4,6 @@
* SSL Certificate validation messages.
*
* @package OSB
* @subpackage SSL
* @category Validation
* @author Deon George
* @copyright (c) 2010 Open Source Billing

View File

@ -4,7 +4,6 @@
* This class provides static pages
*
* @package OSB
* @subpackage Page
* @category Controllers
* @author Deon George
* @copyright (c) 2010 Deon George

View File

@ -4,7 +4,6 @@
* This class provides static menu categories
*
* @package OSB
* @subpackage Page
* @category Controllers
* @author Deon George
* @copyright (c) 2010 Deon George

View File

@ -4,7 +4,6 @@
* This class provides static pages.
*
* @package OSB
* @subpackage Static Page
* @category Models
* @author Deon George
* @copyright (c) 2010 Open Source Billing

View File

@ -4,7 +4,6 @@
* This class provides static page categories
*
* @package OSB
* @subpackage Static Page
* @category Models
* @author Deon George
* @copyright (c) 2010 Open Source Billing

View File

@ -4,7 +4,6 @@
* This class provides Static Page Translation
*
* @package OSB
* @subpackage Static Page
* @category Models
* @author Deon George
* @copyright (c) 2010 Open Source Billing

View File

@ -10,6 +10,11 @@
* @license http://dev.osbill.net/license.html
*/
class Task_Task_Run extends Minion_Task {
protected $_options = array(
'id'=>NULL,
'force'=>FALSE,
);
protected function _execute(array $params) {
if ($params['id']) {
$to = ORM::factory('Task',$params['id']);