Improved ADSL Billing Review

This commit is contained in:
Deon George
2013-10-08 13:34:56 +11:00
parent ab3735914b
commit 638d123739
15 changed files with 367 additions and 248 deletions

View File

@@ -0,0 +1,158 @@
<?php defined('SYSPATH') or die('No direct access allowed.');
/**
* This class provides Exetel VISP ADSL functions
*
* @package ADSL
* @category Helpers
* @author Deon George
* @copyright (c) 2009-2013 Open Source Billing
* @license http://dev.osbill.net/license.html
*/
class ADSL_Billing_Exetelvisp {
private $data = NULL;
private $data_exception = NULL;
private $exception = array();
private $total = 0;
public function excess($service) {
return empty($this->data[$service]['excess']) ? 0 : $this->data[$service]['excess'];
}
public function form() {
$result = '';
$result .= Form::open(URL::link('reseller','adsl/billing'),array('enctype'=>'multipart/form-data','class'=>'form-horizontal'));
$result .= View::factory('adsl/reseller/billing/exetelvisp');
$result .= Form::close();
return $result;
}
public function charge($service) {
return $this->cost($service)+$this->credit($service);
}
public function cost($service) {
return empty($this->data[$service]['cost']) ? 0 : $this->data[$service]['cost'];
}
public function credit($service) {
return empty($this->data[$service]['credit']) ? 0 : $this->data[$service]['credit'];
}
public function exception() {
return Arr::merge($this->data_exception,$this->exception);
}
/**
* This function is key to parsing an invoice and calculating totals, exceptions and missed items
*/
private function haveService($service,$cost) {
if (isset($this->data[$service])) {
if (isset($this->data_exception[$service]))
unset($this->data_exception[$service]);
if ($cost != $this->charge($service))
$this->exception[$service]['info'] = 'Charging difference: '.Currency::display($cost-$this->charge($service));
$this->total += $this->charge($service);
}
}
/**
* Process a CSV Invoice
*/
public function process(Model_ADSL_Supplier $aso,array $file) {
$data = file_get_contents($file['tmp_name']);
if (! $data)
return NULL;
$start = $end = FALSE;
$result = array();
foreach (preg_split("/\n/",$data) as $line) {
// Items start after "Item ID"
if (! $start && preg_match('/^Item ID,/',$line)) {
$start = true;
continue;
// Items end after "Subtotal"
} elseif ($start && ! $end && preg_match('/^Subtotal:,/',$line)) {
$end = true;
continue;
// If we havent started or not ended, continue
} elseif (! $start || $end) {
continue;
}
$record = explode(',',$line);
// 1 = Item ID (ignore)
// 2 = Reference ID (ignore - its not useful for us)
// 3 = Category (always appears blank)
// 4 = Item Description (has our service number, rental and excess charges description)
// 0nnnnnnnnn - Monthly Internet Charge On Plan XXXXX For billing period (dd/mm/yyyy - dd/mm/yyyy) (7 FIELDED LINES)
// 0nnnnnnnnn - Excess usage charges for March 2013 (8 FIELDED LINES)
// 5 = Quantity
// Always 1 for Plan Fees
// 0nnnnnnnnn@graytech.net.au Excess Usage y GB (for excess charges)
// 6 = Unit Price
// Always 1 for Excess Usage (probably quantity)
// 7 = Total Price
// Unit price for Excess Usage
// 8 = Total Price for Excess Usage
if (! count($record) >= 7)
throw HTTP_Exception::factory(501,'Format of CSV file changed? (:record)',array(':record'=>$record));
if (preg_match('/Monthly Internet Charge On Plan /',$record[3])) {
list($service,$description) = explode(':',(preg_replace('/([0-9]+)\s+-\s+(.*)$/',"$1:$2",$record[3])));
$result[$service]['cost'] = str_replace('$','',$record[6]);
} elseif (preg_match('/Monthly Charge On Plan /',$record[3])) {
list($service,$description) = explode(':',(preg_replace('/([0-9]+)\s+-\s+(.*)$/',"$1:$2",$record[3])));
$result[$service]['cost'] = str_replace('$','',$record[6]);
$result[$service]['info'] = 'Other Service';
} elseif (preg_match('/VOIP Monthly Charges /',$record[3])) {
list($service,$description) = explode(':',(preg_replace('/([0-9]+)\s+-\s+(.*)$/',"$1:$2",$record[3])));
$result[$service]['cost'] = str_replace('$','',$record[6]);
$result[$service]['info'] = 'VOIP Service';
} elseif (preg_match('/VISP Credit/',$record[3])) {
list($service,$description) = explode(':',(preg_replace('/([0-9]+)\s+-\s+(.*)$/',"$1:$2",$record[3])));
$result[$service]['credit'] = str_replace('$','',$record[6]);
} elseif (preg_match('/Excess usage charges for /',$record[3])) {
list($service,$description) = explode(':',(preg_replace('/([0-9]+)\s+-\s+(.*)$/',"$1:$2",$record[3])));
$result[$service]['excess'] = str_replace('$','',$record[7]);
// Ignore Payment For Invoice lines
} elseif (preg_match('/Payment For Invoice:/',$record[3])) {
} else {
try {
list($service,$description) = explode(':',(preg_replace('/([0-9]+)\s+-\s+(.*)$/',"$1:$2",$record[3])));
$result[$service]['info'] = $line;
} catch (Exception $e) {
$result['000']['info'] = $line;
}
}
}
$this->data_exception = $this->data = $result;
// @todo This could be optimised better.
foreach ($aso->services(TRUE) as $so)
$this->haveService($so->plugin()->service_number,$so->plugin()->product()->adsl_supplier_plan->total());
return $this;
}
public function total($format=FALSE) {
return $format ? Currency::display($this->total) : $this->total;
}
}
?>

View File

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

View File

@@ -0,0 +1,89 @@
<?php defined('SYSPATH') or die('No direct access allowed.');
/**
* This class provides Reseller ADSL functions
*
* @package ADSL
* @category Controllers/Reseller
* @author Deon George
* @copyright (c) 2009-2013 Open Source Billing
* @license http://dev.osbill.net/license.html
*/
class Controller_Reseller_Adsl extends Controller_Adsl {
protected $secure_actions = array(
'billing'=>TRUE,
'index'=>TRUE,
);
/**
* Reconcile billing for an ADSL supplier
*/
public function action_billing() {
if (empty($_POST['sid']) OR ! $_FILES)
HTTP::redirect(URL::link('reseller','adsl/index'));
$aso = ORM::factory('ADSL_Supplier',$_POST['sid']);
// Process upload
$files = Validation::factory($_FILES)
->rule('csv','Upload::valid')
->rule('csv','Upload::not_empty')
->rule('csv','Upload::type',array(':value',array('csv')))
->rule('csv','Upload::size',array(':value','10M'));
if ($files->check())
foreach ($files->data() as $file) {
$csv = $aso->billing()->process($aso,$file);
// We should only have 1 file
break;
}
if (! $csv)
throw HTTP_Exception::factory(501,'No CSV data ?');
Block::factory()
->title('ADSL Service Invoicing')
->title_icon('icon-th-list')
->body(View::factory('adsl/reseller/billing')->set('o',$csv)->set('aso',$aso));
Block::factory()
->title('ADSL Service Exception Charges')
->title_icon('icon-th-list')
->body(View::factory('adsl/reseller/billingexception')->set('o',$csv));
}
/**
* Select our primary export target
*/
public function action_index() {
$output = '';
if ($_POST and isset($_POST['sid'])) {
$aso = ORM::factory('ADSL_Supplier',$_POST['sid']);
if (! $aso->loaded())
HTTP::redirect('adsl/index');
$c = Kohana::classname('Adsl_Billing_'.$aso->name);
$o = new $c();
$output .= $o->form();
Block::factory()
->title('Upload ADSL Supplier Invoice')
->title_icon('icon-share')
->body($output);
} else {
$output .= Form::open();
$output .= Form::select('sid',ORM::factory('ADSL_Supplier')->list_select());
$output .= Form::button('submit','Submit',array('class'=>'btn btn-primary'));
$output .= Form::close();
Block::factory()
->title('Select ADSL Supplier')
->title_icon('icon-share')
->body($output);
}
}
}
?>

View File

@@ -17,27 +17,41 @@ class Model_ADSL_Supplier extends ORM_OSB {
'adsl_supplier_plan'=>array('model'=>'ADSL_Supplier_Plan','foreign_key'=>'supplier_id','far_key'=>'id'),
);
/**
* Return a list of plans that this supplier makes available
*/
public function plans($active=TRUE) {
return $active ? $this->adsl_supplier_plan->where_active() : $this->adsl_supplier_plan;
}
protected $_form = array('id'=>'id','value'=>'name');
/**
* Return a list of plans that we provide by this supplier
* @deprecated
*/
public function adsl_plans($active=TRUE) {
public function plans($active=TRUE) {
$result = array();
foreach ($this->plans($active)->find_all() as $po)
foreach ($this->find_plans($active)->find_all() as $po)
foreach ($po->adsl_plan->find_all() as $apo)
$result[$apo->id] = $apo;
return $result;
}
/**
* Return the class that takes care of processing invoices
*/
public function billing() {
$b = Kohana::classname('ADSL_Billing_'.$this->name);
if (! class_exists($b))
throw HTTP_Exception::factory(501,'Billing class doesnt exist for :name',array(':name'=>$this->name));
return new $b;
}
/**
* Return a list of plans that this supplier makes available
*/
public function find_plans($active=TRUE) {
return $active ? $this->adsl_supplier_plan->where_active() : $this->adsl_supplier_plan;
}
/**
* Return a list of services for this supplier
*
@@ -46,7 +60,7 @@ class Model_ADSL_Supplier extends ORM_OSB {
public function services($active=TRUE) {
$result = array();
foreach ($this->plans(FALSE)->find_all() as $aspo) {
foreach ($this->find_plans(FALSE)->find_all() as $aspo) {
foreach ($aspo->adsl_plan->find_all() as $apo) {
foreach ($apo->products(FALSE)->find_all() as $po) {
foreach ($po->services($active)->find_all() as $so) {

View File

@@ -25,14 +25,16 @@ class Model_ADSL_Supplier_Plan extends ORM_OSB {
return sprintf('%s/%s',$this->base_down_peak+$this->base_up_peak,$this->base_down_offpeak+$this->base_up_offpeak);
}
public function tax() {
// @todo This should be taken from the users session
// @todo rounding should be a system default
return round($this->base_cost*.1,2);
}
public function name() {
return $this->product_id;
}
public function tax() {
return Tax::amount($this->base_cost);
}
public function total($format=FALSE) {
return $format ? Currency::display($this->base_cost+$this->tax()) : Currency::round($this->base_cost+$this->tax());
}
}
?>