494 lines
15 KiB
PHP
494 lines
15 KiB
PHP
<?php defined('SYSPATH') or die('No direct access allowed.');
|
|
|
|
/**
|
|
* This class provides OSB service admin capabilities.
|
|
*
|
|
* @package OSB
|
|
* @subpackage Service
|
|
* @category Controllers/Admin
|
|
* @author Deon George
|
|
* @copyright (c) 2010 Open Source Billing
|
|
* @license http://dev.osbill.net/license.html
|
|
*/
|
|
class Controller_Admin_Service extends Controller_TemplateDefault_Admin {
|
|
protected $control = array('Services'=>'services');
|
|
|
|
protected $secure_actions = array(
|
|
'list'=>TRUE,
|
|
'listbycheckout'=>TRUE,
|
|
'listadslbilling'=>TRUE,
|
|
'listadslservices'=>TRUE,
|
|
'listhspaservices'=>TRUE,
|
|
'update'=>TRUE,
|
|
);
|
|
|
|
/**
|
|
* Show a list of services
|
|
*/
|
|
public function action_list() {
|
|
$so = ORM::factory('service');
|
|
|
|
Block::add(array(
|
|
'title'=>_('System Customer Services'),
|
|
'body'=>Table::display(
|
|
$so->find_all(),
|
|
25,
|
|
array(
|
|
'id'=>array('label'=>'ID','url'=>'user/service/view/'),
|
|
'type'=>array('label'=>'Type'),
|
|
'name()'=>array('label'=>'Details'),
|
|
'recur_schedule'=>array('label'=>'Billing'),
|
|
'price'=>array('label'=>'Price','class'=>'right'),
|
|
'active'=>array('label'=>'Active'),
|
|
'account->accnum()'=>array('label'=>'Cust ID'),
|
|
'account->name()'=>array('label'=>'Customer'),
|
|
),
|
|
array(
|
|
'page'=>TRUE,
|
|
'type'=>'select',
|
|
'form'=>'user/email/view',
|
|
)),
|
|
));
|
|
}
|
|
|
|
/**
|
|
* List all services by their default checkout method
|
|
*/
|
|
public function action_listbycheckout() {
|
|
// @todo need to add the DB prefix here
|
|
$services = DB::query(Database::SELECT,'
|
|
SELECT c.id AS cid,c.name as checkout_plugin_name,s.id AS sid,a.company,a.first_name,a.last_name,a.id as aid
|
|
FROM ab_service s LEFT JOIN ab_account_billing ab ON (s.account_billing_id=ab.id) LEFT JOIN ab_checkout c ON (ab.checkout_plugin_id=c.id),ab_account a, ab_account_group ag
|
|
WHERE s.active=1 AND s.price > 0 AND s.account_id=a.id AND a.id=ag.account_id AND ((s.account_billing_id IS NOT NULL AND ag.group_id IS NOT NULL) OR (a.id=ag.account_id and ag.group_id=1003))
|
|
ORDER BY c.id,s.recur_schedule,c.name,a.company,a.last_name,a.first_name
|
|
')
|
|
->execute();
|
|
|
|
// @todo If no items, show a nice message. This is not correct for ORM.
|
|
if (! count($services)) {
|
|
echo Kohana::debug('No services with account_billing');
|
|
die();
|
|
}
|
|
|
|
$last_checkout = '';
|
|
$last_account = '';
|
|
$i = 0;
|
|
$sc = $st = 0;
|
|
$output = '<table class="box-left">';
|
|
foreach ($services as $service) {
|
|
$so = ORM::factory('service',$service['sid']);
|
|
$si = $so->account_id.$so->recur_schedule;
|
|
|
|
if (($si != $last_account) AND $last_account) {
|
|
if ($sc > 1)
|
|
$output .= View::factory('service/admin/list/bycheckout_subtotal')
|
|
->set('subtotal',Currency::display($st))
|
|
->set('i',$i++%2);
|
|
$sc = $st = 0;
|
|
}
|
|
|
|
if (($service['cid'] != $last_checkout) OR (! is_null($last_checkout) AND ! $last_checkout)) {
|
|
$output .= View::factory('service/admin/list/bycheckout_header')
|
|
->set('checkout_name',$service['checkout_plugin_name'])
|
|
->set('last_checkout',$last_checkout);
|
|
}
|
|
|
|
$last_checkout = $service['cid'];
|
|
$last_account = $si;
|
|
// @todo This rounding should be a system default
|
|
$st += round($so->price+$so->tax(),2);
|
|
$sc++;
|
|
|
|
$output .= View::factory('service/admin/list/bycheckout_body')
|
|
->set('service',$so)
|
|
->set('i',$i++%2);
|
|
}
|
|
|
|
// Last subtotal
|
|
if ($sc > 1)
|
|
$output .= View::factory('service/admin/list/bycheckout_subtotal')
|
|
->set('subtotal',$st)
|
|
->set('i',$i++%2);
|
|
|
|
$output .= '</table>';
|
|
|
|
Block::add(array(
|
|
'title'=>_('List all Services by Default Payment Method'),
|
|
'body'=>$output,
|
|
));
|
|
|
|
Style::add(array(
|
|
'type'=>'file',
|
|
'data'=>'css/list.css',
|
|
));
|
|
}
|
|
|
|
//@todo this should really be in a different class, since adsl wont be part of the main app
|
|
public function action_listadslservices() {
|
|
// @todo need to add the DB prefix here
|
|
$services = DB::query(Database::SELECT,'
|
|
SELECT A.service_id
|
|
FROM ab_service__adsl A,ab_service B,ab_account C,ab_service D,ab_product E
|
|
WHERE B.active=1 AND A.service_id=B.id AND A.site_id=B.site_id
|
|
AND B.account_id=C.id AND B.site_id=C.site_id
|
|
AND A.service_id=D.id AND A.site_id=D.site_id
|
|
AND D.product_id=E.id AND D.site_id=E.site_id
|
|
AND E.sku like "%ADSL%"
|
|
ORDER BY C.last_name,B.account_id,A.service_number
|
|
')
|
|
->execute();
|
|
|
|
// @todo If no items, show a nice message. This is not correct for ORM.
|
|
if (! count($services)) {
|
|
echo Kohana::debug('No services for ADSL');
|
|
die();
|
|
}
|
|
|
|
$last_account = '';
|
|
$i = 0;
|
|
$output = '<table class="box-left">';
|
|
foreach ($services as $service) {
|
|
$so = ORM::factory('service',$service['service_id']);
|
|
|
|
if ($last_account != $so->account_id) {
|
|
if ($i)
|
|
$output .= '<tr><td colspan="10"> </td></tr>';
|
|
|
|
$output .= View::factory('service/admin/list/adslservices_header')
|
|
->set('service',$so);
|
|
|
|
$last_account = $so->account_id;
|
|
}
|
|
|
|
$output .= View::factory('service/admin/list/adslservices_body')
|
|
->set('service',$so)
|
|
->set('i',$i++%2);
|
|
}
|
|
$output .= '</table>';
|
|
|
|
// Chart the traffic for the last 12 months.
|
|
// @todo need to add the DB prefix here
|
|
$traffic = DB::query(Database::SELECT,sprintf('
|
|
SELECT DATE_FORMAT(DATE,"%%y-%%m") AS MONTH,SID,MAX(NUM) AS NUM,SUM(DOWN_PEAK) AS DOWN_PEAK,SUM(IFNULL(DOWN_OFFPEAK,0)+IFNULL(PEER,0)+IFNULL(INTERNAL,0)) AS DOWN_OFFPEAK
|
|
FROM ab_view_traffic_adsl_daily
|
|
WHERE SID in (%s) AND DATE>"%s"
|
|
GROUP BY DATE_FORMAT(DATE,"%%Y-%%m"),SID
|
|
','1,2',date('Y-m',time()-365*86400)))
|
|
->execute();
|
|
|
|
$peak = $offpeak = $services = array();
|
|
|
|
foreach ($traffic as $a => $v) {
|
|
$peak[$v['SID']]['Peak'][$v['MONTH']] = $v['DOWN_PEAK'];
|
|
$peak[$v['SID']]['OffPeak'][$v['MONTH']] = $v['DOWN_OFFPEAK'];
|
|
$peak[$v['SID']]['Services'][$v['MONTH']] = $v['NUM'];
|
|
}
|
|
|
|
$google = GoogleChart::factory('vertical_bar');
|
|
$google->title = sprintf('ADSL traffic as at %s',date('Y-m-d',strtotime('yesterday')));
|
|
$google->series(array(
|
|
'title'=>array('Exetel-Peak','Exetel-Offpeak'),
|
|
'axis'=>'x',
|
|
'data'=>array('Exetel-Peak'=>$peak[1]['Peak'],'Exetel-OffPeak'=>$peak[1]['OffPeak'])));
|
|
$google->series(array(
|
|
'title'=>array('People-Peak','People-Offpeak'),
|
|
'axis'=>'x',
|
|
'data'=>array('People-Peak'=>$peak[2]['Peak'],'People-OffPeak'=>$peak[2]['OffPeak'])));
|
|
$google->series(array(
|
|
'title'=>'Exetel-Services',
|
|
'axis'=>'r',
|
|
'data'=>array('Exetel-Services'=>$peak[1]['Services'])));
|
|
$google->series(array(
|
|
'title'=>'People-Services',
|
|
'axis'=>'r',
|
|
'data'=>array('People-Services'=>$peak[2]['Services'])));
|
|
|
|
Block::add(array(
|
|
'body'=>$google,
|
|
));
|
|
|
|
Block::add(array(
|
|
'title'=>_('List all ADSL Services'),
|
|
'body'=>$output,
|
|
));
|
|
|
|
Style::add(array(
|
|
'type'=>'file',
|
|
'data'=>'css/list.css',
|
|
));
|
|
}
|
|
|
|
public function action_listhspaservices() {
|
|
// @todo need to add the DB prefix here
|
|
$services = DB::query(Database::SELECT,'
|
|
SELECT A.service_id
|
|
FROM ab_service__adsl A,ab_service B,ab_account C,ab_service D,ab_product E
|
|
WHERE B.active=1 AND A.service_id=B.id AND A.site_id=B.site_id
|
|
AND B.account_id=C.id AND B.site_id=C.site_id
|
|
AND A.service_id=D.id AND A.site_id=D.site_id
|
|
AND D.product_id=E.id AND D.site_id=E.site_id
|
|
AND E.sku like "%HSPA%"
|
|
ORDER BY C.last_name,B.account_id,A.service_number
|
|
')
|
|
->execute();
|
|
|
|
// @todo If no items, show a nice message. This is not correct for ORM.
|
|
if (! count($services)) {
|
|
echo Kohana::debug('No services for HSPA');
|
|
die();
|
|
}
|
|
|
|
$last_account = '';
|
|
$i = 0;
|
|
$output = '<table class="box-left">';
|
|
foreach ($services as $service) {
|
|
$so = ORM::factory('service',$service['service_id']);
|
|
|
|
if ($last_account != $so->account_id) {
|
|
if ($i)
|
|
$output .= '<tr><td colspan="10"> </td></tr>';
|
|
|
|
$output .= View::factory('service/admin/list/adslservices_header')
|
|
->set('service',$so);
|
|
|
|
$last_account = $so->account_id;
|
|
}
|
|
|
|
$output .= View::factory('service/admin/list/adslservices_body')
|
|
->set('service',$so)
|
|
->set('i',$i++%2);
|
|
}
|
|
$output .= '</table>';
|
|
|
|
// Chart the traffic for the last 12 months.
|
|
// @todo need to add the DB prefix here
|
|
$traffic = DB::query(Database::SELECT,sprintf('
|
|
SELECT DATE_FORMAT(DATE,"%%y-%%m") AS MONTH,SID,MAX(NUM) AS NUM,SUM(DOWN_PEAK)*1000 AS DOWN_PEAK,SUM(IFNULL(DOWN_OFFPEAK,0)+IFNULL(PEER,0)+IFNULL(INTERNAL,0))*1000 AS DOWN_OFFPEAK
|
|
FROM ab_view_traffic_adsl_daily
|
|
WHERE SID=%s AND DATE>"%s"
|
|
GROUP BY DATE_FORMAT(DATE,"%%Y-%%m"),SID
|
|
',3,date('Y-m',time()-365*86400)))
|
|
->execute();
|
|
|
|
$peak = $offpeak = $services = array();
|
|
|
|
foreach ($traffic as $a => $v) {
|
|
$peak['Peak'][$v['MONTH']] = $v['DOWN_PEAK'];
|
|
$peak['OffPeak'][$v['MONTH']] = $v['DOWN_OFFPEAK'];
|
|
$peak['Services'][$v['MONTH']] = $v['NUM'];
|
|
}
|
|
|
|
$google = GoogleChart::factory('vertical_bar');
|
|
$google->title = sprintf('HSPA traffic as at %s',date('Y-m-d',strtotime('yesterday')));
|
|
$google->series(array('title'=>array('Peak','Offpeak'),'axis'=>'x','data'=>array('Peak'=>$peak['Peak'],'OffPeak'=>$peak['OffPeak'])));
|
|
$google->series(array('title'=>'Services','axis'=>'r','data'=>array('Services'=>$peak['Services'])));
|
|
|
|
Block::add(array(
|
|
'body'=>$google,
|
|
));
|
|
|
|
Block::add(array(
|
|
'title'=>_('List all HSPA Services'),
|
|
'body'=>$output,
|
|
));
|
|
|
|
Style::add(array(
|
|
'type'=>'file',
|
|
'data'=>'css/list.css',
|
|
));
|
|
}
|
|
|
|
/**
|
|
* Reconcile billing for an ADSL supplier
|
|
*
|
|
* @todo this should really be in a different class, since adsl wont be part of the main app
|
|
*/
|
|
public function action_listadslbilling($id) {
|
|
$aso = ORM::factory('adsl_supplier',$id);
|
|
|
|
// Process upload
|
|
// @todo This should be separated out by supplier in case each supplier has a different format
|
|
if ($_FILES) {
|
|
$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 as $file)
|
|
$csv = $this->process($file);
|
|
}
|
|
|
|
// @todo add a display if there are no items
|
|
$i = $j = 0;
|
|
$total = 0;
|
|
$summary = '';
|
|
$output = View::factory('service/admin/list/adslbilling_head');
|
|
$output .= '<table class="box-left">';
|
|
foreach ($aso->services(TRUE) as $so) {
|
|
// Reset our uploaded data
|
|
$uploaded = array();
|
|
// If our uploaded file has some cost data.
|
|
if (! empty($csv[$so->service_adsl->service_number])) {
|
|
$uploaded['amount'] =
|
|
(empty($csv[$so->service_adsl->service_number]['cost']) ? 0 : $csv[$so->service_adsl->service_number]['cost']) +
|
|
(empty($csv[$so->service_adsl->service_number]['credit']) ? 0 : $csv[$so->service_adsl->service_number]['credit']);
|
|
|
|
// Record the the exception if the cost is not expected
|
|
if (round($so->service_adsl->adsl_plan->adsl_supplier_plan->base_cost+$so->service_adsl->adsl_plan->adsl_supplier_plan->tax(),2) != $uploaded['amount']) {
|
|
$summary .= View::factory('service/admin/list/adslbilling_summary')
|
|
->set('service',$so)
|
|
->set('amount',$uploaded['amount'])
|
|
->set('i',$j++%2);
|
|
|
|
$uploaded['checked'] = '';
|
|
} else {
|
|
$uploaded['checked'] = 'checked="checked"';
|
|
}
|
|
|
|
unset($csv[$so->service_adsl->service_number]);
|
|
|
|
} else {
|
|
$uploaded['checked'] = '';
|
|
$uploaded['amount'] = 0;
|
|
}
|
|
$total += $uploaded['amount'];
|
|
|
|
$output .= View::factory('service/admin/list/adslbilling_body')
|
|
->set('service',$so)
|
|
->set('checked',$uploaded['checked'])
|
|
->set('amount',$uploaded['amount'])
|
|
->set('adsl',$so->service_adsl)
|
|
->set('i',$i++%2);
|
|
}
|
|
|
|
$output .= View::factory('service/admin/list/adslbilling_foot')
|
|
->set('total',$total);
|
|
|
|
$output .= '</table>';
|
|
|
|
// Summary Report of remaining CSV items.
|
|
if (! empty($csv))
|
|
foreach ($csv as $service => $item) {
|
|
$summary .= View::factory('service/admin/list/adslbilling_summary_exception')
|
|
->set('service',$service)
|
|
->set('item',$item)
|
|
->set('i',$j++%2);
|
|
}
|
|
|
|
$output .= Form::open(NULL,array('enctype'=>'multipart/form-data'));
|
|
$output .= '<div>';
|
|
$output .= Form::file('csv');
|
|
$output .= Form::submit('submit','upload',array('class'=>'form_button'));
|
|
$output .= '</div>';
|
|
$output .= Form::close();
|
|
|
|
Block::add(array(
|
|
'title'=>_('ADSL Services'),
|
|
'body'=>$output,
|
|
));
|
|
|
|
if ($summary)
|
|
Block::add(array(
|
|
'title'=>_('Exception Charges'),
|
|
'body'=>'<table class="box-left">'.$summary.'</table>',
|
|
));
|
|
|
|
Style::add(array(
|
|
'type'=>'file',
|
|
'data'=>'css/list.css',
|
|
));
|
|
}
|
|
|
|
private function process(array $file) {
|
|
$data = file_get_contents($file['tmp_name']);
|
|
|
|
if (! $data)
|
|
return;
|
|
|
|
$start = $end = FALSE;
|
|
$return = 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;
|
|
}
|
|
|
|
list($id,$ref,$unknown,$linedata,$q,$cost,$total) = explode(',',$line);
|
|
|
|
// Extract the phone number from the $linedata
|
|
@list($service,$description) = explode(':',(preg_replace('/([0-9]+)\s+-\s+(.*)$/',"$1:$2",$linedata)));
|
|
|
|
// If the description says Monthly Charge, we know its the monthly fee.
|
|
if (preg_match('/^Monthly Charge/',$description))
|
|
$return[$service]['cost'] = preg_replace('/\$/','',$total);
|
|
// If the description says VISP credit, we know this is commission.
|
|
elseif (preg_match('/^VISP Credit/',$description))
|
|
$return[$service]['credit'] = preg_replace('/\$/','',$total);
|
|
// If the description says Excess, we know this is commission.
|
|
elseif (preg_match('/^Excess usage/',$description))
|
|
$return[$service]['excess'] = preg_replace('/\$/','',$total);
|
|
else
|
|
$return[$service]['info'] = $line;
|
|
}
|
|
|
|
return $return;
|
|
}
|
|
|
|
public function action_update($id) {
|
|
$so = ORM::factory('service',$id);
|
|
|
|
if (! $so->loaded())
|
|
Request::current()->redirect('welcome/index');
|
|
|
|
if ($_POST) {
|
|
if (isset($_POST['plugin']) AND $_POST['plugin'])
|
|
if (! $so->plugin()->values($_POST['plugin'])->update()->saved())
|
|
throw new Kohana_Exception('Failed to save updates to plugin data for record :record',array(':record'=>$so->id()));
|
|
|
|
if (! $so->values($_POST)->update()->saved())
|
|
throw new Kohana_Exception('Failed to save updates to plugin data for record :record',array(':record'=>$so->id()));
|
|
}
|
|
|
|
Block::add(array(
|
|
'title'=>sprintf('%s %s:%s',_('Update Service'),$so->id(),$so->name()),
|
|
'body'=>View::factory($so->viewpath())
|
|
->set('so',$so)
|
|
->set('mediapath',Route::get('default/media'))
|
|
->set('plugin_form',$so->admin_update()),
|
|
));
|
|
|
|
// @todo Investigate a better way of preparing for jscalendar
|
|
Script::add(array(
|
|
'type'=>'file',
|
|
'data'=>'js/dhtml.calendar.js',
|
|
));
|
|
Script::add(array(
|
|
'type'=>'file',
|
|
'data'=>'js/dhtml.calendar-setup.js',
|
|
));
|
|
Script::add(array(
|
|
'type'=>'file',
|
|
'data'=>'js/dhtml.calendar-en.js',
|
|
));
|
|
Script::add(array(
|
|
'type'=>'file',
|
|
'data'=>'js/dhtml.date_selector.js',
|
|
));
|
|
Style::add(array(
|
|
'type'=>'file',
|
|
'data'=>'css/dhtml.calendar.css',
|
|
));
|
|
}
|
|
}
|
|
?>
|