From 394595cd4830cb09ff3c6a0733f43b7f6ea1a17d Mon Sep 17 00:00:00 2001 From: Andreas Granig Date: Tue, 9 Jul 2013 14:53:06 +0200 Subject: [PATCH] Implement subscriber creation. --- Build.PL | 2 +- lib/NGCP/Panel/Controller/Contract.pm | 4 +- lib/NGCP/Panel/Controller/Customer.pm | 4 - lib/NGCP/Panel/Controller/Subscriber.pm | 149 +++++++++++++++++- lib/NGCP/Panel/Field/CustomerContract.pm | 26 +++ lib/NGCP/Panel/Field/NumberStatusSelect.pm | 18 +++ .../Panel/Field/SubscriberStatusSelect.pm | 17 ++ lib/NGCP/Panel/Form/Subscriber.pm | 35 +++- share/templates/customer/details.tt | 4 +- share/templates/domain/list.tt | 1 - share/templates/subscriber/list.tt | 25 +++ 11 files changed, 267 insertions(+), 18 deletions(-) create mode 100644 lib/NGCP/Panel/Field/CustomerContract.pm create mode 100644 lib/NGCP/Panel/Field/NumberStatusSelect.pm create mode 100644 lib/NGCP/Panel/Field/SubscriberStatusSelect.pm create mode 100644 share/templates/subscriber/list.tt diff --git a/Build.PL b/Build.PL index 9236ead0f2..3fb481dca5 100644 --- a/Build.PL +++ b/Build.PL @@ -43,7 +43,7 @@ my $builder = Local::Module::Build->new( 'Moose::Util::TypeConstraints' => 0, 'MooseX::Object::Pluggable' => 0, 'namespace::autoclean' => 0, - 'NGCP::Schema' => '1.003', + 'NGCP::Schema' => '1.004', 'Scalar::Util' => 0, 'strict' => 0, 'Template' => 0, diff --git a/lib/NGCP/Panel/Controller/Contract.pm b/lib/NGCP/Panel/Controller/Contract.pm index 60a9a49060..a944a870e5 100644 --- a/lib/NGCP/Panel/Controller/Contract.pm +++ b/lib/NGCP/Panel/Controller/Contract.pm @@ -311,8 +311,8 @@ sub customer_ajax :Chained('customer_list') :PathPart('ajax') :Args(0) { }); $c->forward( "/ajax_process_resultset", [$rs, - ["id","contact_id", "billing_profile_id", "billing_profile_name","status"], - ["billing_profile.name", "status"]]); + ["id","contact_id", "external_id", "billing_profile_id", "billing_profile_name","status"], + ["external_id", "billing_profile.name", "status"]]); $c->detach( $c->view("JSON") ); } diff --git a/lib/NGCP/Panel/Controller/Customer.pm b/lib/NGCP/Panel/Controller/Customer.pm index db0d4f5304..fcb8e00ce1 100644 --- a/lib/NGCP/Panel/Controller/Customer.pm +++ b/lib/NGCP/Panel/Controller/Customer.pm @@ -101,10 +101,6 @@ sub base :Chained('list_customer') :PathPart('') :CaptureArgs(1) { sub details :Chained('base') :PathPart('details') :Args(0) { my ($self, $c) = @_; - - $c->stash( - subscribers => $c->stash->{contract}->voip_subscribers, - ); } sub edit_fraud :Chained('base') :PathPart('fraud/edit') :Args(1) { diff --git a/lib/NGCP/Panel/Controller/Subscriber.pm b/lib/NGCP/Panel/Controller/Subscriber.pm index 6db0027241..755bfcf0aa 100644 --- a/lib/NGCP/Panel/Controller/Subscriber.pm +++ b/lib/NGCP/Panel/Controller/Subscriber.pm @@ -4,6 +4,7 @@ use namespace::sweep; BEGIN { extends 'Catalyst::Controller'; } use NGCP::Panel::Utils::Contract; use NGCP::Panel::Form::Subscriber; +use UUID; use Data::Printer; @@ -25,17 +26,157 @@ sub auto :Does(ACL) :ACLDetachTo('/denied_page') :AllowedRole(admin) :AllowedRol return 1; } -sub master :Chained('/') :PathPart('subscriber') { +sub sub_list :Chained('/') :PathPart('subscriber') :CaptureArgs(0) { + my ($self, $c) = @_; + + $c->stash( + template => 'subscriber/list.tt', + ); + +} + +sub root :Chained('sub_list') :PathPart('') :Args(0) { + my ($self, $c) = @_; +} + + +sub create_list :Chained('sub_list') :PathPart('create') :Args(0) { + my ($self, $c) = @_; + + my $form = NGCP::Panel::Form::Subscriber->new; + $form->process( + posted => ($c->request->method eq 'POST'), + params => $c->request->params, + action => $c->uri_for('/subscriber/create'), + ); + return if NGCP::Panel::Utils::check_form_buttons( + c => $c, + form => $form, + fields => [qw/domain.create/], + back_uri => $c->uri_for('/subscriber/create'), + ); + if($form->validated) { + # TODO: save subscriber + + $c->log->debug(">>>>>>>>>>>>>>>>>>>>>>>>>> subscriber validated"); + + # TODO: use transaction + my $schema = $c->model('DB'); + try { + $schema->txn_do(sub { + my ($uuid_bin, $uuid_string); + UUID::generate($uuid_bin); + UUID::unparse($uuid_bin, $uuid_string); + + # TODO: check if we find a reseller and contract and domains + my $reseller = $c->model('DB')->resultset('resellers') + ->find($c->request->params->{'reseller.id'}); + my $contract = $c->model('DB')->resultset('contracts') + ->find($c->request->params->{'contract.id'}); + my $prov_domain = $c->model('DB')->resultset('voip_domains') + ->find($c->request->params->{'domain.id'}); + my $billing_domain = $c->model('DB')->resultset('domains') + ->find({domain => $prov_domain->domain}); + + my $number; + if(defined $c->request->params->{'e164.cc'} && $c->request->params->{'e164.cc'} ne '') { + # TODO: must have sn set! + $number = $reseller->voip_numbers->create({ + cc => $c->request->params->{'e164.cc'}, + ac => $c->request->params->{'e164.ac'} || '', + sn => $c->request->params->{'e164.sn'} || '', + status => 'active', + }); + } + my $billing_subscriber = $contract->voip_subscribers->create({ + uuid => $uuid_string, + username => $c->request->params->{username}, + domain_id => $billing_domain->id, + status => $c->request->params->{status}, + primary_number_id => defined $number ? $number->id : undef, + }); + if(defined $number) { + $number->update({ subscriber_id => $billing_subscriber->id }); + } + + $c->model('DB')->resultset('provisioning_voip_subscribers')->create({ + uuid => $uuid_string, + username => $c->request->params->{username}, + password => $c->request->params->{password}, + webusername => $c->request->params->{webusername}, + webpassword => $c->request->params->{webpassword}, + admin => $c->request->params->{administrative} || 0, + account_id => $contract->id, + domain_id => $prov_domain->id, + }); + }); + $c->flash(messages => [{type => 'success', text => 'Subscriber successfully created!'}]); + $c->response->redirect($c->uri_for('/subscriber')); + return; + } catch($e) { + $c->log->error("Failed to create subscriber: $e"); + $c->flash(messages => [{type => 'error', text => 'Creating subscriber failed!'}]); + $c->response->redirect($c->uri_for('/subscriber')); + return; + } + } + + $c->log->debug(">>>>>>>>>>>>>>>>>>>>>>>>>> subscriber NOT validated"); + $c->stash(close_target => $c->uri_for()); + $c->stash(create_flag => 1); + $c->stash(form => $form) +} + +sub base :Chained('/subscriber/sub_list') :PathPart('') :CaptureArgs(1) { + my ($self, $c, $subscriber_id) = @_; + + unless($subscriber_id && $subscriber_id->is_integer) { + $c->flash(messages => [{type => 'error', text => 'Invalid subscriber id detected!'}]); + $c->response->redirect($c->uri_for()); + return; + } + + my $res = $c->model('DB')->resultset('voip_subscribers')->find($subscriber_id); + unless(defined($res)) { + $c->flash(messages => [{type => 'error', text => 'Subscriber does not exist!'}]); + $c->response->redirect($c->uri_for()); + return; + } + + $c->stash(subscriber => {$res->get_columns}); + $c->stash(subscriber_result => $res); +} + +sub ajax :Chained('sub_list') :PathPart('ajax') :Args(0) { + my ($self, $c) = @_; + my $dispatch_to = '_ajax_resultset_' . $c->user->auth_realm; + my $resultset = $self->$dispatch_to($c); + $c->forward( "/ajax_process_resultset", [$resultset, + ["id", "username", "domain_id"], + ["username", "domain_id"]]); + $c->detach( $c->view("JSON") ); +} + +sub _ajax_resultset_admin { + my ($self, $c) = @_; + return $c->model('DB')->resultset('voip_subscribers'); +} + +sub _ajax_resultset_reseller { + my ($self, $c) = @_; + + # TODO: filter for reseller + return $c->model('DB')->resultset('voip_subscribers'); +} + +sub master :Chained('/') :PathPart('subscriber') :Args(1) { my ($self, $c, $subscriber_id) = @_; $c->stash( template => 'subscriber/master.tt', - edit_flag => 1, - form => NGCP::Panel::Form::Subscriber->new, ); } - =head1 AUTHOR Andreas Granig,,, diff --git a/lib/NGCP/Panel/Field/CustomerContract.pm b/lib/NGCP/Panel/Field/CustomerContract.pm new file mode 100644 index 0000000000..351e3261a7 --- /dev/null +++ b/lib/NGCP/Panel/Field/CustomerContract.pm @@ -0,0 +1,26 @@ +package NGCP::Panel::Field::CustomerContract; +use HTML::FormHandler::Moose; +extends 'HTML::FormHandler::Field::Compound'; + +has_field 'id' => ( + type => '+NGCP::Panel::Field::DataTable', + label => 'Customer', + do_label => 0, + do_wrapper => 0, + required => 1, + template => 'share/templates/helpers/datatables_field.tt', + ajax_src => '/contract/customer/ajax', + table_titles => ['#', 'Contact #', 'External #', 'Status'], + table_fields => ['id', 'contact_id', 'external_id', 'status'], +); + +has_field 'create' => ( + type => 'Button', + do_label => 0, + value => 'Create Contract', + element_class => [qw/btn btn-tertiary pull-right/], +); + +1; + +# vim: set tabstop=4 expandtab: diff --git a/lib/NGCP/Panel/Field/NumberStatusSelect.pm b/lib/NGCP/Panel/Field/NumberStatusSelect.pm new file mode 100644 index 0000000000..3915aab788 --- /dev/null +++ b/lib/NGCP/Panel/Field/NumberStatusSelect.pm @@ -0,0 +1,18 @@ +package NGCP::Panel::Field::NumberStatusSelect; +use Moose; +extends 'HTML::FormHandler::Field::Select'; + +sub build_options { + my ($self) = @_; + + return [ + { label => 'active', value => 'active' }, + { label => 'reserved', value => 'reserved' }, + { label => 'locked', value => 'locked' }, + { label => 'deported', value => 'deported' }, + ]; +} + +1; + +# vim: set tabstop=4 expandtab: diff --git a/lib/NGCP/Panel/Field/SubscriberStatusSelect.pm b/lib/NGCP/Panel/Field/SubscriberStatusSelect.pm new file mode 100644 index 0000000000..840af603ab --- /dev/null +++ b/lib/NGCP/Panel/Field/SubscriberStatusSelect.pm @@ -0,0 +1,17 @@ +package NGCP::Panel::Field::SubscriberStatusSelect; +use Moose; +extends 'HTML::FormHandler::Field::Select'; + +sub build_options { + my ($self) = @_; + + return [ + { label => 'active', value => 'active' }, + { label => 'locked', value => 'locked' }, + { label => 'terminated', value => 'terminated' }, + ]; +} + +1; + +# vim: set tabstop=4 expandtab: diff --git a/lib/NGCP/Panel/Form/Subscriber.pm b/lib/NGCP/Panel/Form/Subscriber.pm index 9244cf26c0..5bb5537700 100644 --- a/lib/NGCP/Panel/Form/Subscriber.pm +++ b/lib/NGCP/Panel/Form/Subscriber.pm @@ -7,14 +7,29 @@ use Moose::Util::TypeConstraints; use HTML::FormHandler::Widget::Block::Bootstrap; use NGCP::Panel::Field::Domain; +use NGCP::Panel::Field::CustomerContract; +use NGCP::Panel::Field::Reseller; has '+widget_wrapper' => ( default => 'Bootstrap' ); sub build_render_list {[qw/fields actions/]} sub build_form_element_class { [qw/form-horizontal/] } +has_field 'reseller' => ( + type => '+NGCP::Panel::Field::Reseller', + label => 'Reseller', + not_nullable => 1, +); + +has_field 'contract' => ( + type => '+NGCP::Panel::Field::CustomerContract', + label => 'Customer', + not_nullable => 1, +); + has_field 'webusername' => ( type => 'Text', label => 'Web Username', + required => 0, element_attr => { rel => ['tooltip'], title => ['The username to log into the CSC Panel'] @@ -24,6 +39,7 @@ has_field 'webusername' => ( has_field 'webpassword' => ( type => 'Password', label => 'Web Password', + required => 0, element_attr => { rel => ['tooltip'], title => ['The password to log into the CSC Panel'] @@ -33,6 +49,7 @@ has_field 'webpassword' => ( has_field 'e164' => ( type => 'Compound', order => 99, + required => 0, label => 'E164 Number', do_label => 1, do_wrapper => 1, @@ -74,9 +91,10 @@ has_field 'e164.sn' => ( do_wrapper => 0, ); -has_field 'sipusername' => ( +has_field 'username' => ( type => 'Text', label => 'SIP Username', + required => 1, noupdate => 1, element_attr => { rel => ['tooltip'], @@ -84,24 +102,32 @@ has_field 'sipusername' => ( }, ); -has_field 'sipdomain' => ( +has_field 'domain' => ( type => '+NGCP::Panel::Field::Domain', label => 'SIP Domain', not_nullable => 1, ); -has_field 'sippassword' => ( +has_field 'password' => ( type => 'Password', label => 'SIP Password', + required => 1, element_attr => { rel => ['tooltip'], title => ['The SIP password for the User-Agents'] }, ); +has_field 'status' => ( + type => '+NGCP::Panel::Field::SubscriberStatusSelect', + label => 'Status', + not_nullable => 1, +); + has_field 'administrative' => ( type => 'Boolean', label => 'Administrative', + required => 0, element_attr => { rel => ['tooltip'], title => ['Subscriber can configure other subscribers within the Customer Account'] @@ -112,6 +138,7 @@ has_field 'administrative' => ( has_field 'external_id' => ( type => 'Text', label => 'External ID', + required => 0, element_attr => { rel => ['tooltip'], title => ['An external id, e.g. provided by a 3rd party provisioning'] @@ -130,7 +157,7 @@ has_field 'save' => ( has_block 'fields' => ( tag => 'div', class => [qw/modal-body/], - render_list => [qw/webusername webpassword e164 sipusername sipdomain sippassword external_id administrative/ ], + render_list => [qw/reseller contract webusername webpassword e164 username domain password status external_id administrative/ ], ); has_block 'actions' => ( diff --git a/share/templates/customer/details.tt b/share/templates/customer/details.tt index 3357bef3b3..fc113092be 100644 --- a/share/templates/customer/details.tt +++ b/share/templates/customer/details.tt @@ -40,11 +40,11 @@ - [% FOR subscriber IN subscribers -%] + [% FOR subscriber IN contract.voip_subscribers -%] [% subscriber.username %]@[% subscriber.domain.domain %] [% subscriber.primary_number.cc %] [% subscriber.primary_number.ac %] [% subscriber.primary_number.sn %] - TODO[% # subscriber.provisioning_voip_subscriber.password %] + [% subscriber.provisioning_voip_subscriber.password %] [% END -%] diff --git a/share/templates/domain/list.tt b/share/templates/domain/list.tt index 2d8e2dff3e..722a69e972 100644 --- a/share/templates/domain/list.tt +++ b/share/templates/domain/list.tt @@ -1,7 +1,6 @@ [% META title = 'Domains' -%] [% helper.name = 'Domain'; - helper.show_create_button = 1; helper.data = domains; helper.messages = messages; helper.column_titles = [ '#', 'Domain' ]; diff --git a/share/templates/subscriber/list.tt b/share/templates/subscriber/list.tt new file mode 100644 index 0000000000..73676bff09 --- /dev/null +++ b/share/templates/subscriber/list.tt @@ -0,0 +1,25 @@ +[% META title = 'Subscribers' -%] +[% + helper.name = 'Subscriber'; + helper.data = subscribers; + helper.messages = messages; + helper.column_titles = [ '#', 'Username', 'Domain #' ]; + helper.column_fields = [ 'id', 'username', 'domain_id' ]; + + 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( c.controller.action_for('ajax') ); + + helper.dt_buttons = [ + { name = 'Delete', uri = "/subscriber/'+full[\"id\"]+'/delete", class = 'btn-small btn-secondary', icon = 'icon-trash' }, + { name = 'Preferences', uri = "/subscriber/'+full[\"id\"]+'/preferences", class = 'btn-small btn-tertiary', icon = 'icon-list' }, + ]; + helper.top_buttons = [ + { name = 'Create Subscriber', uri = c.uri_for_action('/subscriber/create_list'), icon = 'icon-star' }, + ]; + + PROCESS 'helpers/datatables.tt'; +-%] +[% # vim: set tabstop=4 syntax=html expandtab: -%]