TT#34014 TT#34016 Introduce phonebook entries

* reseller phonebook entries
    * customer phonebook entries
    * subscriber phonebook entries
    * csv download/upload

Change-Id: Ie2c08e1baf8467769a04e6769512417953643811
changes/63/19863/19
Kirill Solomko 7 years ago
parent cb1a3a4cd8
commit 4dbd1e20a4

@ -17,6 +17,7 @@ use NGCP::Panel::Utils::DeviceBootstrap;
use NGCP::Panel::Utils::Voucher;
use NGCP::Panel::Utils::ContractLocations qw();
use NGCP::Panel::Utils::Events qw();
use NGCP::Panel::Utils::Phonebook;
use Template;
=head1 NAME
@ -438,6 +439,12 @@ sub base :Chained('list_customer') :PathPart('') :CaptureArgs(1) {
NGCP::Panel::Utils::ContractLocations::get_datatable_cols($c),
]);
$c->stash->{phonebook_dt_columns} = NGCP::Panel::Utils::Datatables::set_columns($c, [
{ name => "id", search => 1, title => $c->loc("#") },
{ name => "name", search => 1, title => $c->loc("Name") },
{ name => "number", search => 1, title => $c->loc("Number") },
]);
my ($is_timely,$timely_start,$timely_end) = NGCP::Panel::Utils::ProfilePackages::get_timely_range(
package => $contract_first->profile_package,
contract => $contract_first,
@ -466,6 +473,7 @@ sub base :Chained('list_customer') :PathPart('') :CaptureArgs(1) {
$c->stash(billing_mappings_ordered_result => $billing_mappings_ordered );
$c->stash(future_billing_mappings => $future_billing_mappings );
$c->stash(locations => $locations );
$c->stash(phonebook => $contract_first->phonebook );
}
sub base_restricted :Chained('base') :PathPart('') :CaptureArgs(0) :Does(ACL) :ACLDetachTo('/denied_page') :AllowedRole(admin) :AllowedRole(reseller) {
@ -2305,6 +2313,190 @@ sub is_valid_contact {
}
}
sub phonebook_ajax :Chained('base') :PathPart('phonebook/ajax') :Args(0) {
my ($self, $c) = @_;
NGCP::Panel::Utils::Datatables::process($c,
@{$c->stash}{qw(phonebook phonebook_dt_columns)});
$c->detach( $c->view("JSON") );
}
sub phonebook_create :Chained('base_restricted') :PathPart('phonebook/create') :Args(0) {
my ($self, $c) = @_;
my $contract = $c->stash->{contract};
my $posted = ($c->request->method eq 'POST');
my $form = NGCP::Panel::Form::get("NGCP::Panel::Form::Phonebook::Customer", $c);
my $params = {};
$params = merge($params, $c->session->{created_objects});
$form->process(
posted => $posted,
params => $c->request->params,
item => $params,
);
NGCP::Panel::Utils::Navigation::check_form_buttons(
c => $c,
form => $form,
back_uri => $c->req->uri,
);
if($posted && $form->validated) {
try {
$c->model('DB')->schema->txn_do( sub {
$c->model('DB')->resultset('contract_phonebook')->create({
contract_id => $contract->id,
name => $form->values->{name},
number => $form->values->{number},
});
});
NGCP::Panel::Utils::Message::info(
c => $c,
desc => $c->loc('Phonebook entry successfully created'),
);
} catch ($e) {
NGCP::Panel::Utils::Message::error(
c => $c,
error => $e,
desc => $c->loc('Failed to create phonebook entry.'),
);
}
NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for_action("/customer/details", [$contract->id]));
}
$c->stash(
close_target => $c->uri_for_action("/customer/details", [$contract->id]),
create_flag => 1,
form => $form
);
}
sub phonebook_base :Chained('base_restricted') :PathPart('phonebook') :CaptureArgs(1) {
my ($self, $c, $phonebook_id) = @_;
unless($phonebook_id && is_int($phonebook_id)) {
$phonebook_id //= '';
NGCP::Panel::Utils::Message::error(
c => $c,
data => { id => $phonebook_id },
desc => $c->loc('Invalid phonebook id detected'),
);
$c->response->redirect($c->uri_for());
$c->detach;
return;
}
my $res = $c->stash->{contract}->phonebook->find($phonebook_id);
unless(defined($res)) {
NGCP::Panel::Utils::Message::error(
c => $c,
desc => $c->loc('Phonebook entry does not exist'),
);
$c->response->redirect($c->uri_for());
$c->detach;
return;
}
$c->stash(phonebook => {$res->get_inflated_columns},
phonebook_result => $res);
}
sub phonebook_edit :Chained('phonebook_base') :PathPart('edit') :Args(0) {
my ($self, $c) = @_;
my $contract = $c->stash->{contract};
my $posted = ($c->request->method eq 'POST');
my $form = NGCP::Panel::Form::get("NGCP::Panel::Form::Phonebook::Customer", $c);
my $params = $c->stash->{phonebook};
$params = merge($params, $c->session->{created_objects});
$form->process(
posted => $posted,
params => $c->request->params,
item => $params,
);
if($posted && $form->validated) {
try {
$c->model('DB')->schema->txn_do( sub {
$c->stash->{'phonebook_result'}->update({
name => $form->values->{name},
number => $form->values->{number},
});
});
NGCP::Panel::Utils::Message::info(
c => $c,
desc => $c->loc('Phonebook entry successfully updated'),
);
} catch ($e) {
NGCP::Panel::Utils::Message::error(
c => $c,
error => $e,
desc => $c->loc('Failed to update phonebook entry'),
);
}
NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for_action("/customer/details", [$contract->id]));
}
$c->stash(
close_target => $c->uri_for_action("/customer/details", [$contract->id]),
edit_flag => 1,
form => $form
);
}
sub phonebook_delete :Chained('phonebook_base') :PathPart('delete') :Args(0) {
my ($self, $c) = @_;
my $contract = $c->stash->{contract};
my $phonebook = $c->stash->{phonebook_result};
try {
$phonebook->delete;
NGCP::Panel::Utils::Message::info(
c => $c,
data => $c->stash->{phonebook},
desc => $c->loc('Phonebook entry successfully deleted'),
);
} catch ($e) {
NGCP::Panel::Utils::Message::error(
c => $c,
error => $e,
data => $c->stash->{phonebook},
desc => $c->loc('Failed to delete phonebook entry'),
);
};
NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for_action("/customer/details", [$contract->id]));
}
sub phonebook_upload_csv :Chained('base') :PathPart('phonebook_upload_csv') :Args(0) {
my ($self, $c) = @_;
my $contract = $c->stash->{contract};
my $form = NGCP::Panel::Form::get("NGCP::Panel::Form::Phonebook::Upload", $c);
NGCP::Panel::Utils::Phonebook::ui_upload_csv(
$c, $c->stash->{phonebook}, $form, 'contract', $contract->id,
$c->uri_for_action('/customer/phonebook_upload_csv',[$contract->id]),
$c->uri_for_action('/customer/details',[$contract->id])
);
$c->stash(create_flag => 1);
$c->stash(form => $form);
return;
}
sub phonebook_download_csv :Chained('base') :PathPart('phonebook_download_csv') :Args(0) {
my ($self, $c) = @_;
my $contract = $c->stash->{contract};
$c->response->header ('Content-Disposition' => 'attachment; filename="customer_phonebook_entries.csv"');
$c->response->content_type('text/csv');
$c->response->status(200);
NGCP::Panel::Utils::Phonebook::download_csv(
$c, $c->stash->{phonebook}, 'contract', $contract->id
);
return;
}
=head1 AUTHOR
Andreas Granig,,,

@ -0,0 +1,227 @@
package NGCP::Panel::Controller::Phonebook;
use NGCP::Panel::Utils::Generic qw(:all);
use Sipwise::Base;
use parent 'Catalyst::Controller';
use NGCP::Panel::Form;
use NGCP::Panel::Utils::Message;
use NGCP::Panel::Utils::Navigation;
use NGCP::Panel::Utils::Phonebook;
sub auto :Does(ACL) :ACLDetachTo('/denied_page') :AllowedRole(admin) :AllowedRole(reseller) {
my ($self, $c) = @_;
$c->log->debug(__PACKAGE__ . '::auto');
NGCP::Panel::Utils::Navigation::check_redirect_chain(c => $c);
return 1;
}
sub list :Chained('/') :PathPart('phonebook') :CaptureArgs(0) {
my ( $self, $c ) = @_;
$c->stash->{phonebook_rs} = $c->model('DB')->resultset('reseller_phonebook');
unless($c->user->roles eq "admin") {
$c->stash->{phonebook_rs} = $c->stash->{phonebook_rs}->search({
reseller_id => $c->user->reseller_id
});
}
$c->stash->{phonebook_dt_columns} = NGCP::Panel::Utils::Datatables::set_columns($c, [
{ name => 'id', search => 1, title => $c->loc('#') },
$c->user->roles eq "admin"
? { name => 'reseller.name', search => 1, title => $c->loc('Reseller') }
: (),
{ name => 'name', search => 1, title => $c->loc('Name') },
{ name => 'number', search => 1, title => $c->loc('Number') },
]);
$c->stash(template => 'phonebook/list.tt');
}
sub root :Chained('list') :PathPart('') :Args(0) {
my ($self, $c) = @_;
}
sub ajax :Chained('list') :PathPart('ajax') :Args(0) {
my ($self, $c) = @_;
my $rs = $c->stash->{phonebook_rs};
NGCP::Panel::Utils::Datatables::process($c, $rs, $c->stash->{phonebook_dt_columns});
$c->detach( $c->view("JSON") );
}
sub base :Chained('list') :PathPart('') :CaptureArgs(1) {
my ($self, $c, $pb_id) = @_;
unless($pb_id && is_int($pb_id)) {
NGCP::Panel::Utils::Message::error(
c => $c,
log => 'Invalid phonebook enry id detected',
desc => $c->loc('Invalid phonebook entry id detected'),
);
NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for('/phonebook'));
}
my $res = $c->stash->{phonebook_rs}->find($pb_id);
unless(defined($res)) {
NGCP::Panel::Utils::Message::error(
c => $c,
log => 'Phonebook entry does not exist',
desc => $c->loc('Phonebook entry does not exist'),
);
NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for('/phonebook'));
}
$c->stash(phonebook_result => $res);
}
sub create :Chained('list') :PathPart('create') :Args(0) {
my ($self, $c) = @_;
my $posted = ($c->request->method eq 'POST');
my $params = {};
$params = merge($params, $c->session->{created_objects});
my $form;
if($c->user->roles eq "admin") {
$form = NGCP::Panel::Form::get("NGCP::Panel::Form::Phonebook::Admin", $c);
} else {
$form = NGCP::Panel::Form::get("NGCP::Panel::Form::Phonebook::Reseller", $c);
}
$form->process(
posted => $posted,
params => $c->request->params,
item => $params,
);
NGCP::Panel::Utils::Navigation::check_form_buttons(
c => $c,
form => $form,
fields => {
'reseller.create' => $c->uri_for('/reseller/create'),
},
back_uri => $c->req->uri,
);
if($posted && $form->validated) {
try {
if($c->user->roles eq "admin") {
$form->values->{reseller_id} = $form->values->{reseller}{id};
delete $form->values->{reseller};
} else {
$form->values->{reseller_id} = $c->user->reseller_id;
}
$c->stash->{phonebook_rs}->create($form->values);
delete $c->session->{created_objects}->{reseller};
NGCP::Panel::Utils::Message::info(
c => $c,
desc => $c->loc('Phonebook entry successfully created'),
);
} catch($e) {
NGCP::Panel::Utils::Message::error(
c => $c,
error => $e,
desc => $c->loc('Failed to create phonebook entry'),
);
}
NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for('/phonebook'));
}
$c->stash(form => $form);
$c->stash(create_flag => 1);
}
sub edit :Chained('base') :PathPart('edit') {
my ($self, $c) = @_;
my $posted = ($c->request->method eq 'POST');
my $params = { $c->stash->{phonebook_result}->get_inflated_columns };
$params->{reseller}{id} = delete $params->{reseller_id};
$params = merge($params, $c->session->{created_objects});
my $form;
if($c->user->roles eq "admin") {
$form = NGCP::Panel::Form::get("NGCP::Panel::Form::Phonebook::Admin", $c);
} else {
$form = NGCP::Panel::Form::get("NGCP::Panel::Form::Phonebook::Reseller", $c);
}
$form->process(
posted => $posted,
params => $c->request->params,
item => $params,
);
NGCP::Panel::Utils::Navigation::check_form_buttons(
c => $c,
form => $form,
fields => {
'reseller.create' => $c->uri_for('/reseller/create'),
},
back_uri => $c->req->uri,
);
if($posted && $form->validated) {
try {
if($c->user->roles eq "admin") {
$form->values->{reseller_id} = $form->values->{reseller}{id};
delete $form->values->{reseller};
}
$c->stash->{phonebook_result}->update($form->values);
delete $c->session->{created_objects}->{reseller};
NGCP::Panel::Utils::Message::info(
c => $c,
desc => $c->loc('Phonebook entry successfully updated'),
);
} catch($e) {
NGCP::Panel::Utils::Message::error(
c => $c,
error => $e,
desc => $c->loc('Failed to update phonebook entry'),
);
}
NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for('/phonebook'));
}
$c->stash(form => $form);
$c->stash(edit_flag => 1);
}
sub delete :Chained('base') :PathPart('delete') {
my ($self, $c) = @_;
try {
$c->stash->{phonebook_result}->delete;
NGCP::Panel::Utils::Message::info(
c => $c,
data => { $c->stash->{phonebook_result}->get_inflated_columns },
desc => $c->loc('Phonebook entry successfully deleted'),
);
} catch($e) {
NGCP::Panel::Utils::Message::error(
c => $c,
error => $e,
desc => $c->loc('Failed to delete phonebook entry'),
);
}
NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for('/phonebook'));
}
sub upload_csv :Chained('list') :PathPart('upload_csv') :Args(0) {
my ($self, $c) = @_;
my $form = NGCP::Panel::Form::get("NGCP::Panel::Form::Phonebook::Upload", $c);
NGCP::Panel::Utils::Phonebook::ui_upload_csv(
$c, $c->stash->{phonebook_rs}, $form, 'reseller', $c->user->reseller_id,
$c->uri_for('/phonebook/upload_csv'), $c->uri_for('/phonebook')
);
$c->stash(create_flag => 1);
$c->stash(form => $form);
return;
}
sub download_csv :Chained('list') :PathPart('download_csv') :Args(0) {
my ($self, $c) = @_;
$c->response->header ('Content-Disposition' => 'attachment; filename="reseller_phonebook_entries.csv"');
$c->response->content_type('text/csv');
$c->response->status(200);
NGCP::Panel::Utils::Phonebook::download_csv(
$c, $c->stash->{phonebook_rs}, 'reseller', $c->user->reseller_id
);
return;
}
1;

@ -16,6 +16,7 @@ use NGCP::Panel::Utils::BillingNetworks qw();
use NGCP::Panel::Utils::ProfilePackages qw();
use NGCP::Panel::Utils::Billing qw();
use NGCP::Panel::Utils::Admin;
use NGCP::Panel::Utils::Phonebook;
sub auto :Private {
my ($self, $c) = @_;
@ -218,6 +219,12 @@ sub base :Chained('list_reseller') :PathPart('') :CaptureArgs(1) {
{ name => 'name', search => 1, title => $c->loc('Name') },
NGCP::Panel::Utils::ProfilePackages::get_datatable_cols($c),
]);
$c->stash->{phonebook_dt_columns} = NGCP::Panel::Utils::Datatables::set_columns($c, [
{ name => "id", search => 1, title => $c->loc("#") },
{ name => "name", search => 1, title => $c->loc("Name") },
{ name => "number", search => 1, title => $c->loc("Number") },
]);
$c->stash(reseller => $c->stash->{resellers}->search_rs({ id => $reseller_id }));
unless($c->stash->{reseller}->first) {
@ -229,6 +236,7 @@ sub base :Chained('list_reseller') :PathPart('') :CaptureArgs(1) {
NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for('/reseller'));
}
$c->stash->{branding} = $c->stash->{reseller}->first->branding;
$c->stash->{phonebook} = $c->stash->{reseller}->first->phonebook;
}
sub reseller_contacts :Chained('base') :PathPart('contacts/ajax') :Args(0) :Does(ACL) :ACLDetachTo('/denied_page') :AllowedRole(admin) {
@ -672,8 +680,192 @@ sub get_branding_css :Chained('base') :PathPart('css/download') :Args(0) {
$c->response->body($branding->css);
}
1;
sub phonebook_ajax :Chained('base') :PathPart('phonebook/ajax') :Args(0) :Does(ACL) :ACLDetachTo('/denied_page') :AllowedRole(admin) :AllowedRole(reseller) {
my ($self, $c) = @_;
NGCP::Panel::Utils::Datatables::process($c,
@{$c->stash}{qw(phonebook phonebook_dt_columns)});
$c->detach( $c->view("JSON") );
}
sub phonebook_create :Chained('base') :PathPart('phonebook/create') :Args(0) :Does(ACL) :ACLDetachTo('/denied_page') :AllowedRole(admin) :AllowedRole(reseller) {
my ($self, $c) = @_;
my $reseller = $c->stash->{reseller}->first;
my $posted = ($c->request->method eq 'POST');
my $form = NGCP::Panel::Form::get("NGCP::Panel::Form::Phonebook::Reseller", $c);
my $params = {};
$params = merge($params, $c->session->{created_objects});
$form->process(
posted => $posted,
params => $c->request->params,
item => $params,
);
NGCP::Panel::Utils::Navigation::check_form_buttons(
c => $c,
form => $form,
back_uri => $c->req->uri,
);
if($posted && $form->validated) {
try {
$c->model('DB')->schema->txn_do( sub {
$c->model('DB')->resultset('reseller_phonebook')->create({
reseller_id => $reseller->id,
name => $form->values->{name},
number => $form->values->{number},
});
});
NGCP::Panel::Utils::Message::info(
c => $c,
desc => $c->loc('Phonebook entry successfully created'),
);
} catch ($e) {
NGCP::Panel::Utils::Message::error(
c => $c,
error => $e,
desc => $c->loc('Failed to create phonebook entry.'),
);
}
NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for_action("/reseller/details", [$reseller->id]));
}
$c->stash(
close_target => $c->uri_for_action("/reseller/details", [$reseller->id]),
create_flag => 1,
form => $form
);
}
sub phonebook_base :Chained('base') :PathPart('phonebook') :CaptureArgs(1) {
my ($self, $c, $phonebook_id) = @_;
unless($phonebook_id && is_int($phonebook_id)) {
$phonebook_id //= '';
NGCP::Panel::Utils::Message::error(
c => $c,
data => { id => $phonebook_id },
desc => $c->loc('Invalid phonebook id detected'),
);
$c->response->redirect($c->uri_for());
$c->detach;
return;
}
my $res = $c->stash->{reseller}->first->phonebook->find($phonebook_id);
unless(defined($res)) {
NGCP::Panel::Utils::Message::error(
c => $c,
desc => $c->loc('Phonebook entry does not exist'),
);
$c->response->redirect($c->uri_for());
$c->detach;
return;
}
$c->stash(phonebook => {$res->get_inflated_columns},
phonebook_result => $res);
}
sub phonebook_edit :Chained('phonebook_base') :PathPart('edit') :Args(0) :Does(ACL) :ACLDetachTo('/denied_page') :AllowedRole(admin) :AllowedRole(reseller) {
my ($self, $c) = @_;
my $reseller = $c->stash->{reseller}->first;
my $posted = ($c->request->method eq 'POST');
my $form = NGCP::Panel::Form::get("NGCP::Panel::Form::Phonebook::Reseller", $c);
my $params = $c->stash->{phonebook};
$params = merge($params, $c->session->{created_objects});
$form->process(
posted => $posted,
params => $c->request->params,
item => $params,
);
if($posted && $form->validated) {
try {
$c->model('DB')->schema->txn_do( sub {
$c->stash->{'phonebook_result'}->update({
name => $form->values->{name},
number => $form->values->{number},
});
});
NGCP::Panel::Utils::Message::info(
c => $c,
desc => $c->loc('Phonebook entry successfully updated'),
);
} catch ($e) {
NGCP::Panel::Utils::Message::error(
c => $c,
error => $e,
desc => $c->loc('Failed to update phonebook entry'),
);
}
NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for_action("/reseller/details", [$reseller->id]));
}
$c->stash(
close_target => $c->uri_for_action("/reseller/details", [$reseller->id]),
edit_flag => 1,
form => $form
);
}
sub phonebook_delete :Chained('phonebook_base') :PathPart('delete') :Args(0) :Does(ACL) :ACLDetachTo('/denied_page') :AllowedRole(admin) :AllowedRole(reseller) {
my ($self, $c) = @_;
my $reseller = $c->stash->{reseller}->first;
my $phonebook = $c->stash->{phonebook_result};
try {
$phonebook->delete;
NGCP::Panel::Utils::Message::info(
c => $c,
data => $c->stash->{phonebook},
desc => $c->loc('Phonebook entry successfully deleted'),
);
} catch ($e) {
NGCP::Panel::Utils::Message::error(
c => $c,
error => $e,
data => $c->stash->{phonebook},
desc => $c->loc('Failed to delete phonebook entry'),
);
};
NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for_action("/reseller/details", [$reseller->id]));
}
sub phonebook_upload_csv :Chained('base') :PathPart('phonebook_upload_csv') :Args(0) {
my ($self, $c) = @_;
my $reseller = $c->stash->{reseller}->first;
my $form = NGCP::Panel::Form::get("NGCP::Panel::Form::Phonebook::Upload", $c);
NGCP::Panel::Utils::Phonebook::ui_upload_csv(
$c, $c->stash->{phonebook}, $form, 'reseller', $reseller->id,
$c->uri_for_action('/reseller/phonebook_upload_csv',[$reseller->id]),
$c->uri_for_action('/reseller/details',[$reseller->id])
);
$c->stash(create_flag => 1);
$c->stash(form => $form);
return;
}
sub phonebook_download_csv :Chained('base') :PathPart('phonebook_download_csv') :Args(0) {
my ($self, $c) = @_;
my $reseller = $c->stash->{reseller}->first;
$c->response->header ('Content-Disposition' => 'attachment; filename="reseller_phonebook_entries.csv"');
$c->response->content_type('text/csv');
$c->response->status(200);
NGCP::Panel::Utils::Phonebook::download_csv(
$c, $c->stash->{phonebook}, 'reseller', $reseller->id
);
return;
}
1;
__END__

@ -25,6 +25,7 @@ use NGCP::Panel::Utils::Fax;
use NGCP::Panel::Utils::Kamailio;
use NGCP::Panel::Utils::Events;
use NGCP::Panel::Utils::ProfilePackages qw();
use NGCP::Panel::Utils::Phonebook;
use UUID;
@ -182,6 +183,13 @@ sub base :Chained('sub_list') :PathPart('') :CaptureArgs(1) {
{ name => "source_uuid", search => 1, title => $c->loc('Source UUID') },
]);
$c->stash->{phonebook_dt_columns} = NGCP::Panel::Utils::Datatables::set_columns($c, [
{ name => "id", search => 1, title => $c->loc("#") },
{ name => "name", search => 1, title => $c->loc("Name") },
{ name => "number", search => 1, title => $c->loc("Number") },
{ name => "shared", search => 1, title => $c->loc("Shared") },
]);
if($c->stash->{billing_mapping}->product->class eq "pbxaccount") {
$c->stash->{pbx_groups} = NGCP::Panel::Utils::Subscriber::get_pbx_subscribers_rs(
c => $c,
@ -201,6 +209,8 @@ sub base :Chained('sub_list') :PathPart('') :CaptureArgs(1) {
},{
join => 'group',
});
$c->stash->{phonebook} = $c->stash->{subscriber}->phonebook;
}
sub webfax :Chained('base') :PathPart('webfax') :Args(0) {
@ -4855,6 +4865,192 @@ sub get_packet :Chained('callflow_base') :PathPart('packet') :Args() {
}
sub phonebook_ajax :Chained('base') :PathPart('phonebook/ajax') :Args(0) {
my ($self, $c) = @_;
NGCP::Panel::Utils::Datatables::process($c,
@{$c->stash}{qw(phonebook phonebook_dt_columns)});
$c->detach( $c->view("JSON") );
}
sub phonebook_create :Chained('base') :PathPart('phonebook/create') :Args(0) {
my ($self, $c) = @_;
my $subscriber = $c->stash->{subscriber};
my $posted = ($c->request->method eq 'POST');
my $form = NGCP::Panel::Form::get("NGCP::Panel::Form::Phonebook::Subscriber", $c);
my $params = {};
$params = merge($params, $c->session->{created_objects});
$form->process(
posted => $posted,
params => $c->request->params,
item => $params,
);
NGCP::Panel::Utils::Navigation::check_form_buttons(
c => $c,
form => $form,
back_uri => $c->req->uri,
);
if($posted && $form->validated) {
try {
$c->model('DB')->schema->txn_do( sub {
$c->model('DB')->resultset('subscriber_phonebook')->create({
subscriber_id => $subscriber->id,
name => $form->values->{name},
number => $form->values->{number},
shared => $form->values->{shared},
});
});
NGCP::Panel::Utils::Message::info(
c => $c,
desc => $c->loc('Phonebook entry successfully created'),
);
} catch ($e) {
NGCP::Panel::Utils::Message::error(
c => $c,
error => $e,
desc => $c->loc('Failed to create phonebook entry.'),
);
}
NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for_action("/subscriber/details", [$subscriber->id]));
}
$c->stash(
close_target => $c->uri_for_action("/subscriber/details", [$subscriber->id]),
create_flag => 1,
form => $form
);
}
sub phonebook_base :Chained('base') :PathPart('phonebook') :CaptureArgs(1) {
my ($self, $c, $phonebook_id) = @_;
unless($phonebook_id && is_int($phonebook_id)) {
$phonebook_id //= '';
NGCP::Panel::Utils::Message::error(
c => $c,
data => { id => $phonebook_id },
desc => $c->loc('Invalid phonebook id detected'),
);
$c->response->redirect($c->uri_for());
$c->detach;
return;
}
my $res = $c->stash->{subscriber}->phonebook->find($phonebook_id);
unless(defined($res)) {
NGCP::Panel::Utils::Message::error(
c => $c,
desc => $c->loc('Phonebook entry does not exist'),
);
$c->response->redirect($c->uri_for());
$c->detach;
return;
}
$c->stash(phonebook => {$res->get_inflated_columns},
phonebook_result => $res);
}
sub phonebook_edit :Chained('phonebook_base') :PathPart('edit') :Args(0) {
my ($self, $c) = @_;
my $subscriber = $c->stash->{subscriber};
my $posted = ($c->request->method eq 'POST');
my $form = NGCP::Panel::Form::get("NGCP::Panel::Form::Phonebook::Subscriber", $c);
my $params = $c->stash->{phonebook};
$params = merge($params, $c->session->{created_objects});
$form->process(
posted => $posted,
params => $c->request->params,
item => $params,
);
if($posted && $form->validated) {
try {
$c->model('DB')->schema->txn_do( sub {
$c->stash->{'phonebook_result'}->update({
name => $form->values->{name},
number => $form->values->{number},
shared => $form->values->{shared},
});
});
NGCP::Panel::Utils::Message::info(
c => $c,
desc => $c->loc('Phonebook entry successfully updated'),
);
} catch ($e) {
NGCP::Panel::Utils::Message::error(
c => $c,
error => $e,
desc => $c->loc('Failed to update phonebook entry'),
);
}
NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for_action("/subscriber/details", [$subscriber->id]));
}
$c->stash(
close_target => $c->uri_for_action("/subscriber/details", [$subscriber->id]),
edit_flag => 1,
form => $form
);
}
sub phonebook_delete :Chained('phonebook_base') :PathPart('delete') :Args(0) {
my ($self, $c) = @_;
my $subscriber = $c->stash->{subscriber};
my $phonebook = $c->stash->{phonebook_result};
try {
$phonebook->delete;
NGCP::Panel::Utils::Message::info(
c => $c,
data => $c->stash->{phonebook},
desc => $c->loc('Phonebook entry successfully deleted'),
);
} catch ($e) {
NGCP::Panel::Utils::Message::error(
c => $c,
error => $e,
data => $c->stash->{phonebook},
desc => $c->loc('Failed to delete phonebook entry'),
);
};
NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for_action("/subscriber/details", [$subscriber->id]));
}
sub phonebook_upload_csv :Chained('base') :PathPart('phonebook_upload_csv') :Args(0) {
my ($self, $c) = @_;
my $subscriber = $c->stash->{subscriber};
my $form = NGCP::Panel::Form::get("NGCP::Panel::Form::Phonebook::Upload", $c);
NGCP::Panel::Utils::Phonebook::ui_upload_csv(
$c, $c->stash->{phonebook}, $form, 'subscriber', $subscriber->id,
$c->uri_for_action('/subscriber/phonebook_upload_csv',[$subscriber->id]),
$c->uri_for_action('/subscriber/details',[$subscriber->id])
);
$c->stash(create_flag => 1);
$c->stash(form => $form);
return;
}
sub phonebook_download_csv :Chained('base') :PathPart('phonebook_download_csv') :Args(0) {
my ($self, $c) = @_;
my $subscriber = $c->stash->{subscriber};
$c->response->header ('Content-Disposition' => 'attachment; filename="subscriber_phonebook_entries.csv"');
$c->response->content_type('text/csv');
$c->response->status(200);
NGCP::Panel::Utils::Phonebook::download_csv(
$c, $c->stash->{phonebook}, 'subscriber', $subscriber->id
);
return;
}
=head1 AUTHOR
Andreas Granig,,,

@ -0,0 +1,22 @@
package NGCP::Panel::Form::Phonebook::Admin;
use HTML::FormHandler::Moose;
extends 'NGCP::Panel::Form::Phonebook::Reseller';
has_field 'reseller' => (
type => '+NGCP::Panel::Field::Reseller',
validate_when_empty => 1,
element_attr => {
rel => ['tooltip'],
title => ['The reseller id to assign this phonebook entry to.']
},
);
has_block 'fields' => (
tag => 'div',
class => [qw/modal-body/],
render_list => [qw/id reseller name number/],
);
1;
# vim: set tabstop=4 expandtab:

@ -0,0 +1,61 @@
package NGCP::Panel::Form::Phonebook::Customer;
use HTML::FormHandler::Moose;
extends 'HTML::FormHandler';
use HTML::FormHandler::Widget::Block::Bootstrap;
has '+widget_wrapper' => ( default => 'Bootstrap' );
has_field 'submitid' => ( type => 'Hidden' );
sub build_render_list {[qw/submitid fields actions/]}
sub build_form_element_class {[qw(form-horizontal)]}
has_field 'id' => (
type => 'Hidden',
);
has_field 'contract_id' => (
type => 'Hidden',
);
has_field 'name' => (
type => 'Text',
required => 1,
label => 'Name',
element_attr => {
rel => ['tooltip'],
title => ['The full entry name "e.g. John Smith".'],
},
);
has_field 'number' => (
type => 'Text',
required => 1,
label => 'Number',
element_attr => {
rel => ['tooltip'],
title => ['The phonebook number, can be either as a numeric or a SIP number.' ],
},
);
has_field 'save' => (
type => 'Submit',
value => 'Save',
element_class => [qw/btn btn-primary/],
label => '',
);
has_block 'fields' => (
tag => 'div',
class => [qw/modal-body/],
render_list => [qw/id contract_id name number/],
);
has_block 'actions' => (
tag => 'div',
class => [qw/modal-footer/],
render_list => [qw/save/],
);
1;
# vim: set tabstop=4 expandtab:

@ -0,0 +1,61 @@
package NGCP::Panel::Form::Phonebook::Reseller;
use HTML::FormHandler::Moose;
extends 'HTML::FormHandler';
use HTML::FormHandler::Widget::Block::Bootstrap;
has '+widget_wrapper' => ( default => 'Bootstrap' );
has_field 'submitid' => ( type => 'Hidden' );
sub build_render_list {[qw/submitid fields actions/]}
sub build_form_element_class {[qw(form-horizontal)]}
has_field 'id' => (
type => 'Hidden',
);
has_field 'reseller_id' => (
type => 'Hidden',
);
has_field 'name' => (
type => 'Text',
required => 1,
label => 'Name',
element_attr => {
rel => ['tooltip'],
title => ['The full entry name "e.g. John Smith".'],
},
);
has_field 'number' => (
type => 'Text',
required => 1,
label => 'Number',
element_attr => {
rel => ['tooltip'],
title => ['The phonebook number, can be either as a numeric or a SIP number.' ],
},
);
has_field 'save' => (
type => 'Submit',
value => 'Save',
element_class => [qw/btn btn-primary/],
label => '',
);
has_block 'fields' => (
tag => 'div',
class => [qw/modal-body/],
render_list => [qw/id reseller_id name number/],
);
has_block 'actions' => (
tag => 'div',
class => [qw/modal-footer/],
render_list => [qw/save/],
);
1;
# vim: set tabstop=4 expandtab:

@ -0,0 +1,71 @@
package NGCP::Panel::Form::Phonebook::Subscriber;
use HTML::FormHandler::Moose;
extends 'HTML::FormHandler';
use HTML::FormHandler::Widget::Block::Bootstrap;
has '+widget_wrapper' => ( default => 'Bootstrap' );
has_field 'submitid' => ( type => 'Hidden' );
sub build_render_list {[qw/submitid fields actions/]}
sub build_form_element_class {[qw(form-horizontal)]}
has_field 'id' => (
type => 'Hidden',
);
has_field 'subscriber_id' => (
type => 'Hidden',
);
has_field 'name' => (
type => 'Text',
required => 1,
label => 'Name',
element_attr => {
rel => ['tooltip'],
title => ['The full entry name "e.g. John Smith".'],
},
);
has_field 'number' => (
type => 'Text',
required => 1,
label => 'Number',
element_attr => {
rel => ['tooltip'],
title => ['The phonebook number, can be either as a numeric or a SIP number.' ],
},
);
has_field 'shared' => (
type => 'Boolean',
required => 0,
label => 'Shared',
element_attr => {
rel => ['tooltip'],
title => ['Share the entry to other subscribers of the same customer.' ],
},
);
has_field 'save' => (
type => 'Submit',
value => 'Save',
element_class => [qw/btn btn-primary/],
label => '',
);
has_block 'fields' => (
tag => 'div',
class => [qw/modal-body/],
render_list => [qw/id subscriber_id name number shared/],
);
has_block 'actions' => (
tag => 'div',
class => [qw/modal-footer/],
render_list => [qw/save/],
);
1;
# vim: set tabstop=4 expandtab:

@ -0,0 +1,44 @@
package NGCP::Panel::Form::Phonebook::Upload;
use Sipwise::Base;
use HTML::FormHandler::Moose;
extends 'HTML::FormHandler';
use HTML::FormHandler::Widget::Block::Bootstrap;
has '+widget_wrapper' => ( default => 'Bootstrap' );
has '+enctype' => ( default => 'multipart/form-data');
has_field 'submitid' => ( type => 'Hidden' );
sub build_render_list {[qw/submitid fields actions/]}
sub build_form_element_class { [qw/form-horizontal/] }
has_field 'upload_phonebook' => (
type => 'Upload',
max_size => '2097152000', # 2GB
);
has_field 'purge_existing' => (
type => 'Boolean',
);
has_field 'save' => (
type => 'Submit',
value => 'Upload',
element_class => [qw/btn btn-primary/],
do_label => 0,
);
has_block 'fields' => (
tag => 'div',
class => [qw/modal-body/],
render_list => [qw/upload_phonebook purge_existing/],
);
has_block 'actions' => (
tag => 'div',
class => [qw/modal-footer/],
render_list => [qw/save/],
);
1;
# vim: set tabstop=4 expandtab:

@ -0,0 +1,277 @@
package NGCP::Panel::Utils::Phonebook;
use strict;
use warnings;
use Sipwise::Base;
use English;
use NGCP::Panel::Utils::Generic qw(:all);
use NGCP::Panel::Utils::Message;
sub get_reseller_phonebook {
my ($c, $reseller_id) = @_;
my @pb;
my $r_pb_rs = $c->model('DB')->resultset('reseller_phonebook')->search({
reseller_id => $reseller_id,
});
for my $r ($r_pb_rs->all) {
push @pb, { name => $r->name, number => $r->number };
}
return [ sort { $a->{name} cmp $b->{name} } @pb ];
}
sub get_contract_phonebook {
my ($c, $contract_id) = @_;
my @pb;
my %c_numbers;
my $contract = $c->model('DB')->resultset('contracts')->search({
id => $contract_id,
})->first;
my $r_pb_rs = $c->model('DB')->resultset('reseller_phonebook')->search({
reseller_id => $contract->contact->reseller->id,
});
my $c_pb_rs = $c->model('DB')->resultset('contract_phonebook')->search({
contract_id => $contract_id,
});
for my $r ($c_pb_rs->all) {
push @pb, { name => $r->name, number => $r->number };
$c_numbers{$r->number} = $r->name;
}
for my $r ($r_pb_rs->all) {
unless (exists $c_numbers{$r->number}) {
push @pb, { name => $r->name, number => $r->number };
}
}
return [ sort { $a->{name} cmp $b->{name} } @pb ];
}
sub get_subscriber_phonebook {
my ($c, $subscriber_id) = @_;
my @pb;
my %c_numbers;
my $sub = $c->model('DB')->resultset('voip_subscribers')->search({
id => $subscriber_id,
})->first;
my $r_pb_rs = $c->model('DB')->resultset('reseller_phonebook')->search({
reseller_id => $sub->contract->contact->reseller->id,
});
my $c_pb_rs = $c->model('DB')->resultset('contract_phonebook')->search({
contract_id => $sub->contract_id,
});
my $a_pb_rs = $c->model('DB')->resultset('subscriber_phonebook')->search({
shared => 1,
'contract.id' => $sub->contract_id,
},{
join => { 'subscriber' => 'contract' },
});
my $s_pb_rs = $c->model('DB')->resultset('subscriber_phonebook')->search({
subscriber_id => $subscriber_id,
});
for my $r ($s_pb_rs->all) {
push @pb, { name => $r->name, number => $r->number };
$c_numbers{$r->number} = $r->name;
}
for my $r ($c_pb_rs->all) {
unless (exists $c_numbers{$r->number}) {
push @pb, { name => $r->name, number => $r->number };
$c_numbers{$r->number} = $r->name;
}
}
for my $r ($a_pb_rs->all) {
unless (exists $c_numbers{$r->number}) {
push @pb, { name => $r->name, number => $r->number };
$c_numbers{$r->number} = $r->name;
}
}
for my $r ($r_pb_rs->all) {
unless (exists $c_numbers{$r->number}) {
push @pb, { name => $r->name, number => $r->number };
}
}
return [ sort { $a->{name} cmp $b->{name} } @pb ];
}
sub ui_upload_csv {
my ($c, $rs, $form, $owner, $owner_id, $action, $back) = @_;
my $upload = $c->req->upload('upload_phonebook');
my $posted = $c->req->method eq 'POST';
my @params = ( upload_phonebook => $posted ? $upload : undef, );
$form->process(
posted => $posted,
params => { @params },
action => $action,
);
if($form->validated) {
unless($upload) {
NGCP::Panel::Utils::Message::error(
c => $c,
desc => $c->loc('No phonebook entries file specified!'),
);
$c->response->redirect($back);
return;
}
my $data = $upload->slurp;
my($entries, $fails, $text_success);
try {
my ($entries, $fails, $text) =
upload_csv($c, $rs, $owner, $owner_id,
$c->req->params->{purge_existing}, \$data);
NGCP::Panel::Utils::Message::info(
c => $c,
desc => $$text,
);
} catch($e) {
NGCP::Panel::Utils::Message::error(
c => $c,
error => $e,
desc => $c->loc('Failed to upload Phonebook entries'),
);
}
$c->response->redirect($back);
}
return;
}
sub upload_csv {
my ($c, $rs, $owner, $owner_id, $purge, $data) = @_;
my $csv = Text::CSV_XS->new({ allow_whitespace => 1, binary => 1, keep_meta_info => 1 });
my @cols = qw/name number/;
my @fields ;
my @fails = ();
my $linenum = 0;
my @entries;
$c->model('DB')->txn_do(sub {
open(my $fh, '<:encoding(utf8)', $data);
while ( my $line = <$fh> ){
++$linenum;
next unless length $line;
unless($csv->parse($line)) {
push @fails, $linenum;
next;
}
@fields = $csv->fields();
my $row = {};
# name,number
if (scalar @fields == 2) {
@{$row}{@cols} = @fields;
# name,number,reseller_id (in case of admin uploads)
} elsif ($c->user->roles eq "admin" &&
$owner eq 'reseller' && scalar @fields == 3) {
@{$row}{@cols,'reseller_id'} = @fields;
# name,number,shared
} elsif ($owner eq 'subscriber' && scalar @fields == 3) {
@{$row}{@cols,'shared'} = @fields;
# hmmm
} else {
push @fails, $linenum;
next;
}
$row->{$owner.'_id'} //= $owner_id;
push @entries, $row;
unless ($purge) {
$rs->update_or_create($row,{key=>'rel_u_idx'});
}
}
if ($purge) {
my ($start, $end);
$start = time;
$rs->delete;
$end = time;
$c->log->debug("Purging phonebook entries took " . ($end - $start) . "s");
$start = time;
$rs->populate(\@entries);
$end = time;
$c->log->debug("Populating phonebook entries took " . ($end - $start) . "s");
}
});
my $text = $c->loc('Phonebook entries successfully uploaded');
if (@fails) {
$text .= $c->loc(", but skipped the following line numbers: ") . (join ", ", @fails);
}
return ( \@entries, \@fails, \$text );
}
sub download_csv {
my ($c, $rs, $owner, $owner_id) = @_;
my @cols = qw/name number/;
if ($owner eq 'admin') {
push @cols, 'reseller_id';
} elsif ($owner eq 'subscriber') {
push @cols, 'shared';
}
my ($start, $end);
$start = time;
foreach my $row ($rs->all) {
my %entry = $row->get_inflated_columns;
delete $entry{id};
$c->res->write_fh->write(join (",", @entry{@cols}) );
$c->res->write_fh->write("\n");
}
$c->res->write_fh->close;
$end = time;
$c->log->debug("Creating phonebook entries CSV for download took " . ($end - $start) . "s");
return 1;
}
1;
=head1 NAME
NGCP::Panel::Utils::Phonebook
=head1 DESCRIPTION
A helper to manipulate the phonebook data
=head1 METHODS
=head2 get_reseller_phonebook
=head2 get_contract_phonebook
=head2 get_subscriber_phonebook
=head1 AUTHOR
Sipwise Development Team
=head1 LICENSE
This library is free software. You can redistribute it and/or modify
it under the same terms as Perl itself.
=cut
# vim: set tabstop=4 expandtab:

@ -721,6 +721,43 @@ $(function() {
</div>
[% END -%]
[% IF (c.user.roles == "subscriberadmin" && product.class == "pbxaccount") || c.user.roles == "admin" || c.user.roles == "reseller" -%]
<div class="accordion-group">
<div class="accordion-heading">
<a class="accordion-toggle" data-toggle="collapse" data-parent="#customer_details" href="#collapse_phonebook">[% c.loc('Phonebook') %]</a>
</div>
<div class="accordion-body collapse" id="collapse_phonebook">
<div class="accordion-inner">
[%
helper.name = c.loc('Phonebook');
helper.identifier = 'phonebook';
helper.messages = messages;
helper.length_change = 1;
helper.dt_columns = phonebook_dt_columns;
helper.ajax_uri = c.uri_for_action('/customer/phonebook_ajax', [ c.req.captures.0 ]);
helper.tmpuri = c.uri_for(contract.id, 'phonebook');
UNLESS c.user.read_only;
helper.dt_buttons = [
{ name = c.loc('Edit'), uri = helper.tmpuri _ "/'+full.id+'/edit", class = 'btn-small btn-primary', icon = 'icon-edit' },
{ name = c.loc('Delete'), uri = helper.tmpuri _ "/'+full.id+'/delete", class = 'btn-small btn-secondary', icon = 'icon-remove' },
];
helper.top_buttons = [
{ name = c.loc('Create Phonebook Entry'), uri = c.uri_for_action('/customer/phonebook_create', c.req.captures), class = 'btn-small btn-primary', icon = 'icon-star' },
{ name = c.loc('Download CSV'), uri = c.uri_for_action('/customer/phonebook_download_csv', c.req.captures), class = 'btn-small btn-primary', icon = 'icon-star' },
{ name = c.loc('Upload CSV'), uri = c.uri_for_action('/customer/phonebook_upload_csv', c.req.captures), class = 'btn-small btn-primary', icon = 'icon-star' },
];
END;
PROCESS 'helpers/datatables.tt';
%]
</div>
</div>
</div>
[% END -%]
</div>
[% IF create_flag == 1 -%]

@ -0,0 +1,29 @@
[% site_config.title = c.loc('Phonebook') -%]
[%
helper.name = c.loc('Phonebook');
helper.identifier = 'phonebook';
helper.messages = messages;
helper.dt_columns = phonebook_dt_columns;
helper.length_change = 1;
helper.close_target = close_target;
helper.create_flag = create_flag;
helper.edit_flag = edit_flag;
helper.form_object = form;
helper.ajax_uri = c.uri_for_action( "/phonebook/ajax" );
UNLESS c.user.read_only;
helper.dt_buttons = [
{ name = c.loc('Edit'), uri = "/phonebook/'+full.id+'/edit", class = 'btn-small btn-primary', icon = 'icon-edit' },
{ name = c.loc('Delete'), uri = "/phonebook/'+full.id+'/delete", class = 'btn-small btn-secondary', icon = 'icon-trash' },
];
helper.top_buttons = [
{ name = c.loc('Create Phonebook Entry'), uri = c.uri_for('/phonebook/create'), icon = 'icon-star' },
{ name = c.loc('Download CSV'), uri = c.uri_for('/phonebook/download_csv'), icon = 'icon-star' },
{ name = c.loc('Upload CSV'), uri = c.uri_for('/phonebook/upload_csv'), icon = 'icon-star' },
];
END;
PROCESS 'helpers/datatables.tt';
-%]
[% # vim: set tabstop=4 syntax=html expandtab: -%]

@ -365,6 +365,7 @@
</div>
</div>
</div>
<div class="accordion-group">
<div class="accordion-heading">
<a class="accordion-toggle" data-toggle="collapse" data-parent="#reseller_details" href="#collapse_intemplate">[% c.loc('Invoice Templates') %]</a>
@ -402,6 +403,44 @@
</div>
</div>
</div>
[% IF c.user.roles == "admin" || c.user.roles == "reseller" -%]
<div class="accordion-group">
<div class="accordion-heading">
<a class="accordion-toggle" data-toggle="collapse" data-parent="#reseller_details" href="#collapse_phonebook">[% c.loc('Phonebook') %]</a>
</div>
<div class="accordion-body collapse" id="collapse_phonebook">
<div class="accordion-inner">
[%
helper.name = c.loc('Phonebook');
helper.identifier = 'phonebook';
helper.messages = messages;
helper.length_change = 1;
helper.dt_columns = phonebook_dt_columns;
helper.ajax_uri = c.uri_for_action('/reseller/phonebook_ajax', [ c.req.captures.0 ]);
helper.tmpuri = c.uri_for(reseller.first.id, 'phonebook');
UNLESS c.user.read_only;
helper.dt_buttons = [
{ name = c.loc('Edit'), uri = helper.tmpuri _ "/'+full.id+'/edit", class = 'btn-small btn-primary', icon = 'icon-edit' },
{ name = c.loc('Delete'), uri = helper.tmpuri _ "/'+full.id+'/delete", class = 'btn-small btn-secondary', icon = 'icon-remove' },
];
helper.top_buttons = [
{ name = c.loc('Create Phonebook Entry'), uri = c.uri_for_action('/reseller/phonebook_create', c.req.captures), class = 'btn-small btn-primary', icon = 'icon-star' },
{ name = c.loc('Download CSV'), uri = c.uri_for_action('/reseller/phonebook_download_csv', c.req.captures), class = 'btn-small btn-primary', icon = 'icon-star' },
{ name = c.loc('Upload CSV'), uri = c.uri_for_action('/reseller/phonebook_upload_csv', c.req.captures), class = 'btn-small btn-primary', icon = 'icon-star' },
];
END;
PROCESS 'helpers/datatables.tt';
%]
</div>
</div>
</div>
[% END -%]
</div>
[% IF edit_flag || create_flag -%]

@ -331,6 +331,42 @@ function process_pbx_items(moveId,direction){
</div>
[% END -%]
[% IF (c.user.roles == "subscriberadmin" && product.class == "pbxaccount") || c.user.roles == "admin" || c.user.roles == "reseller" -%]
<div class="accordion-group">
<div class="accordion-heading">
<a class="accordion-toggle" data-toggle="collapse" data-parent="#subscriber_data" href="#collapse_phonebook">[% c.loc('Phonebook') %]</a>
</div>
<div class="accordion-body collapse" id="collapse_phonebook">
<div class="accordion-inner">
[%
helper.name = c.loc('Phonebook');
helper.identifier = 'phonebook';
helper.messages = messages;
helper.length_change = 1;
helper.dt_columns = phonebook_dt_columns;
helper.ajax_uri = c.uri_for_action('/subscriber/phonebook_ajax', [ c.req.captures.0 ]);
helper.tmpuri = c.uri_for(subscriber.id, 'phonebook');
UNLESS c.user.read_only;
helper.dt_buttons = [
{ name = c.loc('Edit'), uri = helper.tmpuri _ "/'+full.id+'/edit", class = 'btn-small btn-primary', icon = 'icon-edit' },
{ name = c.loc('Delete'), uri = helper.tmpuri _ "/'+full.id+'/delete", class = 'btn-small btn-secondary', icon = 'icon-remove' },
];
helper.top_buttons = [
{ name = c.loc('Create Phonebook Entry'), uri = c.uri_for_action('/subscriber/phonebook_create', c.req.captures), class = 'btn-small btn-primary', icon = 'icon-star' },
{ name = c.loc('Download CSV'), uri = c.uri_for_action('/subscriber/phonebook_download_csv', c.req.captures), class = 'btn-small btn-primary', icon = 'icon-star' },
{ name = c.loc('Upload CSV'), uri = c.uri_for_action('/subscriber/phonebook_upload_csv', c.req.captures), class = 'btn-small btn-primary', icon = 'icon-star' },
];
END;
PROCESS 'helpers/datatables.tt';
%]
</div>
</div>
</div>
[% END -%]
</div>

@ -81,6 +81,7 @@
[% END -%]
<li><a href="[% c.uri_for('/lnp') %]">[% c.loc('Number Porting') %]</a></li>
<li><a href="[% c.uri_for('/emergencymapping') %]">[% c.loc('Emergency Mappings') %]</a></li>
<li><a href="[% c.uri_for('/phonebook') %]">[% c.loc('Phonebook') %]</a></li>
</ul>
</li>
[% # vim: set tabstop=4 syntax=html expandtab: -%]

@ -42,6 +42,7 @@
<li><a href="[% c.uri_for('/maliciouscall') %]">[% c.loc('Malicious Calls') %]</a></li>
[% END -%]
<li><a href="[% c.uri_for('/emergencymapping') %]">[% c.loc('Emergency Mappings') %]</a></li>
<li><a href="[% c.uri_for('/phonebook') %]">[% c.loc('Phonebook') %]</a></li>
</ul>
</li>
[% # vim: set tabstop=4 syntax=html expandtab: -%]

Loading…
Cancel
Save