package NGCP::Panel::Controller::Customer; use NGCP::Panel::Utils::Generic qw(:all); use Sipwise::Base; use parent 'Catalyst::Controller'; use NGCP::Panel::Form; use JSON qw(decode_json encode_json); use IPC::System::Simple qw/capturex EXIT_ANY $EXITVAL/; use NGCP::Panel::Utils::Message; use NGCP::Panel::Utils::Navigation; use NGCP::Panel::Utils::DateTime; use NGCP::Panel::Utils::Subscriber; use NGCP::Panel::Utils::Sounds; use NGCP::Panel::Utils::Contract; use NGCP::Panel::Utils::ProfilePackages; use NGCP::Panel::Utils::BillingMappings qw(); 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 NGCP::Panel::Utils::Reseller; use Template; =head1 NAME NGCP::Panel::Controller::Customer - Catalyst Controller =head1 DESCRIPTION Catalyst Controller. =head1 METHODS =cut sub auto :Does(ACL) :ACLDetachTo('/denied_page') :AllowedRole(admin) :AllowedRole(reseller) :AllowedRole(ccareadmin) :AllowedRole(ccare) :AllowedRole(subscriberadmin) { my ($self, $c) = @_; $c->log->debug(__PACKAGE__ . '::auto'); NGCP::Panel::Utils::Navigation::check_redirect_chain(c => $c); return 1; } sub list_customer :Chained('/') :PathPart('customer') :CaptureArgs(0) { my ($self, $c) = @_; my $now = NGCP::Panel::Utils::DateTime::current_local; $c->stash->{contract_dt_columns} = NGCP::Panel::Utils::Datatables::set_columns($c, [ { name => "id", int_search => 1, title => $c->loc("#") }, { name => "external_id", search => 1, title => $c->loc("External #") }, { name => "contact.reseller.name", search => 0, title => $c->loc("Reseller") }, { name => "contact.email", search => 0, title => $c->loc("Contact Email") }, { name => "contact.firstname", search => 0, title => '' }, { name => "contact.lastname", search => 0, title => $c->loc("Name"), custom_renderer => 'function ( data, type, full, opt ) { var sep = (full.contact_firstname && full.contact_lastname) ? " " : ""; return (full.contact_firstname || "") + sep + (full.contact_lastname || ""); }' }, { name => "product.name", search => 0, title => $c->loc("Product") }, { name => 'billing_profile_name', accessor => "billing_profile_name", search => 0, title => $c->loc('Billing Profile'), literal_sql => '""' }, { name => "status", search => 0, title => $c->loc("Status") }, { name => "max_subscribers", search => 0, title => $c->loc("Max. Subscribers") }, { title => $c->loc("Contact Email"), search => 1, no_column => 1 }, ]); my $rs = NGCP::Panel::Utils::Contract::get_customer_rs(c => $c); #, now => $now); my $rs_all = NGCP::Panel::Utils::Contract::get_contract_rs( schema => $c->model('DB'), #now => $now, include_terminated => 1, ); $c->stash( contract_select_rs => $rs, contract_select_all_rs => $rs_all, template => 'customer/list.tt', now => $now ); } sub root :Chained('list_customer') :PathPart('') :Args(0) :Does(ACL) :ACLDetachTo('/denied_page') :AllowedRole(admin) :AllowedRole(reseller) :AllowedRole(ccareadmin) :AllowedRole(ccare) { my ($self, $c) = @_; } sub ajax :Chained('list_customer') :PathPart('ajax') :Args(0) { my ($self, $c) = @_; my $now = NGCP::Panel::Utils::DateTime::current_local; #uniform ts my $res = $c->stash->{contract_select_rs}; NGCP::Panel::Utils::Datatables::process($c, $res, $c->stash->{contract_dt_columns}, sub { my $item = shift; my %contact = $item->contact->get_inflated_columns; my %result = map { (ref $contact{$_}) ? () : ('contact_'.$_ => $contact{$_}); } keys %contact; my $bm_actual = NGCP::Panel::Utils::BillingMappings::get_actual_billing_mapping(c => $c, contract => $item, now => $now, ); $result{'billing_profile_name'} = $bm_actual->billing_profile->name if $bm_actual; return %result; }, { 'count_limit' => 1000, 'extra_or' => [ { 'me.id' => { in => [ map { $_->id } $res->search({ 'contact.email' => { like => [ NGCP::Panel::Utils::Datatables::get_search_string_pattern($c) ]->[0] }, },{ rows => 1001, page => 1, join => 'contact', })->all ] }, }, ], }); $c->detach( $c->view("JSON") ); } sub ajax_reseller_filter :Chained('list_customer') :PathPart('ajax/reseller') :Args(1) { my ($self, $c, $reseller_id) = @_; unless($reseller_id && is_int($reseller_id)) { NGCP::Panel::Utils::Message::error( c => $c, log => 'Invalid reseller id detected', desc => $c->loc('Invalid reseller id detected'), ); $c->response->redirect($c->uri_for()); return; } my $rs = $c->stash->{contract_select_rs}->search_rs({ 'contact.reseller_id' => $reseller_id, },{ join => 'contact', }); my $reseller_customer_columns = NGCP::Panel::Utils::Datatables::set_columns($c, [ { name => "id", search => 1, title => $c->loc("#") }, { name => "external_id", search => 1, title => $c->loc("External #") }, { name => "product.name", search => 1, title => $c->loc("Product") }, { name => "contact.email", search => 1, title => $c->loc("Contact Email") }, { name => "status", search => 1, title => $c->loc("Status") }, ]); NGCP::Panel::Utils::Datatables::process($c, $rs, $reseller_customer_columns); $c->detach( $c->view("JSON") ); } sub ajax_package_filter :Chained('list_customer') :PathPart('ajax/package') :Args(1) { my ($self, $c, $package_id) = @_; unless($package_id && is_int($package_id)) { NGCP::Panel::Utils::Message::error( c => $c, log => 'Invalid profile package id detected', desc => $c->loc('Invalid profile package id detected'), ); $c->response->redirect($c->uri_for()); return; } my $rs = $c->stash->{contract_select_rs}->search_rs({ 'profile_package_id' => $package_id, },undef); my $package_customer_columns = NGCP::Panel::Utils::Datatables::set_columns($c, [ NGCP::Panel::Utils::ProfilePackages::get_customer_datatable_cols($c) ]); NGCP::Panel::Utils::Datatables::process($c, $rs, $package_customer_columns); $c->detach( $c->view("JSON") ); } sub ajax_pbx_only :Chained('list_customer') :PathPart('ajax_pbx_only') :Args(0) { my ($self, $c) = @_; my $now = NGCP::Panel::Utils::DateTime::current_local; #uniform ts my @product_ids = map { $_->id; } $c->model('DB')->resultset('products')->search_rs({ 'class' => ['pbxaccount'] })->all; my $res = $c->stash->{contract_select_rs}->search_rs({ 'product_id' => { -in => [ @product_ids ] }, }); NGCP::Panel::Utils::Datatables::process($c, $res, $c->stash->{contract_dt_columns}, sub { my $item = shift; my %contact = $item->contact->get_inflated_columns; my %result = map { (ref $contact{$_}) ? () : ('contact_'.$_ => $contact{$_}); } keys %contact; my $bm_actual = NGCP::Panel::Utils::BillingMappings::get_actual_billing_mapping(c => $c, contract => $item, now => $now, ); $result{'billing_profile_name'} = $bm_actual->billing_profile->name if $bm_actual; return %result; },); $c->detach( $c->view("JSON") ); } sub create :Chained('list_customer') :PathPart('create') :Args(0) { my ($self, $c) = @_; if($c->user->roles eq "subscriberadmin") { $c->detach('/denied_page'); } my $posted = ($c->request->method eq 'POST'); my $form; my $params = {}; $params = merge($params, $c->session->{created_objects}); unless ($self->is_valid_contact($c, $params->{contact}{id})) { delete $params->{contact}; } if($c->license('pbx') && $c->config->{features}->{cloudpbx}) { $form = NGCP::Panel::Form::get("NGCP::Panel::Form::Contract::ProductSelect", $c); } else { $form = NGCP::Panel::Form::get("NGCP::Panel::Form::Contract::Customer", $c); $c->stash->{type} = 'sipaccount'; } $form->process( posted => $posted, params => $c->request->params, item => $params ); NGCP::Panel::Utils::Navigation::check_form_buttons( c => $c, form => $form, fields => {'contact.create' => $c->uri_for('/contact/create'), 'billing_profile.create' => $c->uri_for('/billing/create'), 'billing_profiles.profile.create' => $c->uri_for('/billing/create'), 'billing_profiles.network.create' => $c->uri_for('/network/create'), 'profile_package.create' => $c->uri_for('/package/create'), 'subscriber_email_template.create' => $c->uri_for('/emailtemplate/create'), 'passreset_email_template.create' => $c->uri_for('/emailtemplate/create'), 'invoice_template.create' => $c->uri_for('/invoicetemplate/create'), }, back_uri => $c->req->uri, ); if($posted && $form->validated) { try { my $schema = $c->model('DB'); $schema->set_transaction_isolation('READ COMMITTED'); $schema->txn_do(sub { foreach(qw/contact billing_profile profile_package product subscriber_email_template passreset_email_template invoice_email_template invoice_template/){ $form->values->{$_.'_id'} = $form->values->{$_}{id} || undef; delete $form->values->{$_}; } #$form->values->{profile_package_id} = undef unless NGCP::Panel::Utils::ProfilePackages::ENABLE_PROFILE_PACKAGES; $form->values->{create_timestamp} = $form->values->{modify_timestamp} = NGCP::Panel::Utils::DateTime::current_local; $form->values->{external_id} = $form->field('external_id')->value; $form->values->{product_id} //= $schema->resultset('products')->search_rs({ class => $c->stash->{type} })->first->id; unless($form->values->{max_subscribers} && length($form->values->{max_subscribers})) { delete $form->values->{max_subscribers}; } my $mappings_to_create = []; NGCP::Panel::Utils::BillingMappings::prepare_billing_mappings( c => $c, resource => $form->values, mappings_to_create => $mappings_to_create, err_code => sub { my ($err,@fields) = @_; die( [$err, "showdetails"] ); }); my $contract = $schema->resultset('contracts')->create($form->values); NGCP::Panel::Utils::BillingMappings::append_billing_mappings(c => $c, contract => $contract, mappings_to_create => $mappings_to_create, ); NGCP::Panel::Utils::ProfilePackages::create_initial_contract_balances(c => $c, contract => $contract, ); $c->session->{created_objects}->{contract} = { id => $contract->id }; delete $c->session->{created_objects}->{contact}; delete $c->session->{created_objects}->{billing_profile}; delete $c->session->{created_objects}->{network}; delete $c->session->{created_objects}->{profile_package}; my $uri = $c->uri_for_action("/customer/details", [$contract->id]); NGCP::Panel::Utils::Message::info( c => $c, cname => 'create', desc => $c->loc('Customer #[_1] successfully created', $contract->id) . ' - ' . $c->loc('Details') . '', ); }); } catch($e) { NGCP::Panel::Utils::Message::error( c => $c, error => $e, desc => $c->loc('Failed to create customer contract'), ); } NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for('/customer')); #/contract? } $c->stash(create_flag => 1); $c->stash(form => $form); } sub base :Chained('list_customer') :PathPart('') :CaptureArgs(1) { my ($self, $c, $contract_id) = @_; unless($contract_id && is_int($contract_id)) { NGCP::Panel::Utils::Message::error( c => $c, error => "customer contract id '$contract_id' is not valid", desc => $c->loc('Invalid customer contract id'), ); NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for('/customer')); return; } my $contract_rs = $c->stash->{contract_select_rs} ->search({ 'me.id' => $contract_id, },undef); my $contract_terminated_rs = $c->stash->{contract_select_all_rs} ->search({ 'me.id' => $contract_id, },undef); if ($c->user->roles eq 'reseller' || $c->user->roles eq 'ccare') { $contract_rs = $contract_rs->search({ 'contact.reseller_id' => $c->user->reseller_id, }, { join => 'contact', }); } elsif($c->user->roles eq 'subscriberadmin') { $contract_rs = $contract_rs->search({ 'me.id' => $c->user->account_id, }); unless($contract_rs->count) { $c->log->error("unauthorized access of subscriber uuid '".$c->qs($c->user->uuid)."' to contract id '$contract_id'"); $c->detach('/denied_page'); } } my $contract_first = $contract_rs->first; unless(defined($contract_first)) { NGCP::Panel::Utils::Message::error( c => $c, log => 'Customer was not found', desc => $c->loc('Customer was not found'), ); NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for('/customer')); } my $now = $c->stash->{now}; my $billing_mapping = NGCP::Panel::Utils::BillingMappings::get_actual_billing_mapping(c => $c, contract => $contract_first, now => $now, ); my $billing_mappings_ordered = NGCP::Panel::Utils::BillingMappings::billing_mappings_ordered($contract_first->billing_mappings,$now,$billing_mapping); my $future_billing_mappings = NGCP::Panel::Utils::BillingMappings::billing_mappings_ordered(NGCP::Panel::Utils::BillingMappings::future_billing_mappings($contract_first->billing_mappings,$now)); my $balance; try { my $schema = $c->model('DB'); $schema->set_transaction_isolation('READ COMMITTED'); $schema->txn_do(sub { $balance = NGCP::Panel::Utils::ProfilePackages::get_contract_balance(c => $c, contract => $contract_rs->first, now => $now); }); } catch($e) { NGCP::Panel::Utils::Message::error( c => $c, error => $e, desc => $c->loc('Failed to get contract balance.'), ); $c->response->redirect($c->uri_for()); return; } $c->stash->{balanceinterval_dt_columns} = NGCP::Panel::Utils::Datatables::set_columns($c, [ NGCP::Panel::Utils::ProfilePackages::get_balanceinterval_datatable_cols($c), ]); $c->stash->{topuplog_dt_columns} = NGCP::Panel::Utils::Datatables::set_columns($c, [ NGCP::Panel::Utils::ProfilePackages::get_topuplog_datatable_cols($c), ]); my $product = $contract_first->product; NGCP::Panel::Utils::Message::error( c => $c, error => "No product for customer contract id $contract_id found", desc => $c->loc('No product for this customer contract found.'), ) unless($product); #my $product = $c->model('DB')->resultset('products')->find($product_id); #NGCP::Panel::Utils::Message::error( # c => $c, # error => "No product with id $product_id for customer contract id $contract_id found", # desc => $c->loc('Invalid product id for this customer contract.'), #) unless($product); # only show the extension if it's a pbx extension. otherwise (and in case of a pilot?) show the # number if($c->license('pbx') && $c->config->{features}->{cloudpbx} && $product->class eq "pbxaccount") { $c->stash->{subscriber_dt_columns} = NGCP::Panel::Utils::Datatables::set_columns($c, [ { name => "id", search => 1, title => $c->loc("#") }, { name => "username", search => 1, title => $c->loc("Name") }, { name => "provisioning_voip_subscriber.pbx_extension", search => 1, title => $c->loc("Extension") }, ]); } else { $c->stash->{subscriber_dt_columns} = NGCP::Panel::Utils::Datatables::set_columns($c, [ { name => "id", search => 1, title => $c->loc("#") }, { name => "username", search => 1, title => $c->loc("Name") }, { name => "domain.domain", search => 1, title => $c->loc('Domain') }, { name => "number", search => 1, title => $c->loc('Number'), literal_sql => "concat(primary_number.cc, primary_number.ac, primary_number.sn)"}, { name => "primary_number.cc", search => 1, title => "" }, #need this to get the relationship ]); } $c->stash->{pbxgroup_dt_columns} = NGCP::Panel::Utils::Datatables::set_columns($c, [ { name => "id", search => 1, title => $c->loc("#") }, { name => "username", search => 1, title => $c->loc("Name") }, { name => "provisioning_voip_subscriber.pbx_extension", search => 1, title => $c->loc("Extension") }, { name => "provisioning_voip_subscriber.pbx_hunt_policy", search => 1, title => $c->loc("Hunt Policy") }, { name => "provisioning_voip_subscriber.pbx_hunt_timeout", search => 1, title => $c->loc("Hunt Timeout") }, { name => "provisioning_voip_subscriber.pbx_hunt_cancel_mode", search => 1, title => $c->loc("Cancel Mode") }, ]); my $subscribers_rs = $c->model('DB')->resultset('voip_subscribers')->search({ contract_id => $contract_id, status => { '!=' => 'terminated' }, }, { alias => 'me', from => [ { 'me' => 'billing.voip_subscribers' }, [ { 'provisioning_voip_subscriber' => 'provisioning.voip_subscribers', '-join_type' => 'left' }, [ { 'provisioning_voip_subscriber.uuid' => { -ident => 'me.uuid' } }, ], ], [ { 'attribute' => 'provisioning.voip_preferences', '-join_type' => 'left' }, [ { '-and' => [ { 'attribute.attribute' => { '-value' => 'display_name'} , }, ], }, ], ], [ { 'voip_usr_preferences' => 'provisioning.voip_usr_preferences', '-join_type' => 'left' }, [ { '-and' => [ { 'voip_usr_preferences.attribute_id' => { '-ident' => 'attribute.id'} , 'voip_usr_preferences.subscriber_id' => { -ident => 'provisioning_voip_subscriber.id' } }, ], }, ], ], ], '+select' => [\'ifnull(voip_usr_preferences.value, me.username)'], '+as' => ['username_combined'], order_by => [\'ifnull(voip_usr_preferences.value, me.username)','pbx_extension'], }); $c->stash->{subscribers} = $subscribers_rs->search_rs({'provisioning_voip_subscriber.is_pbx_group' => 0}); if($c->license('pbx') && $c->config->{features}->{cloudpbx}) { $c->stash->{pbx_groups} = $subscribers_rs->search_rs({'provisioning_voip_subscriber.is_pbx_group' => 1}); } my $field_devs = [ $c->model('DB')->resultset('autoprov_field_devices')->search({ 'contract_id' => $contract_rs->first->id })->all ]; # contents of details page: NGCP::Panel::Utils::Sounds::stash_soundset_list(c => $c, contract => $contract_first); $c->stash->{contact_hash} = { $contract_first->contact->get_inflated_columns }; if(defined $contract_first->max_subscribers) { $c->stash->{subscriber_count} = $contract_first->voip_subscribers ->search({ status => { -not_in => ['terminated'] } }) ->count; } my $locations = $contract_rs->first->voip_contract_locations->search_rs( undef, { join => 'voip_contract_location_blocks', group_by => 'me.id' }); $c->stash->{invoice_dt_columns} = NGCP::Panel::Utils::Datatables::set_columns($c, [ { name => "id", search => 1, title => $c->loc("#") }, { name => "serial", search => 1, title => $c->loc("Serial #") }, { name => "period_start", search => 1, title => $c->loc("Start") }, { name => "period_end", search => 1, title => $c->loc("End") }, { name => "amount_net", search => 1, title => $c->loc("Net Amount") }, { name => "amount_vat", search => 1, title => $c->loc("VAT Amount") }, { name => "amount_total", search => 1, title => $c->loc("Total Amount") }, ]); $c->stash->{location_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 => "description", search => 1, title => $c->loc("Description") }, 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, balance => $balance, now => $now); my $notopup_expiration = NGCP::Panel::Utils::ProfilePackages::get_notopup_expiration( package => $contract_first->profile_package, contract => $contract_first, balance => $balance); $c->stash(pbx_devices => $field_devs); $c->stash(product => $product); $c->stash(balance => $balance); $c->stash(package => $contract_first->profile_package); $c->stash(timely_topup_start => $timely_start); $c->stash(timely_topup_end => $timely_end); $c->stash(notopup_expiration => $notopup_expiration); $c->stash(fraud => $contract_first->contract_fraud_preference); $c->stash(template => 'customer/details.tt'); $c->stash(contract => $contract_first); $c->stash(contract_rs => $contract_rs); $c->stash(contract_terminated_rs => $contract_terminated_rs); $c->stash(billing_mapping => $billing_mapping ); #$c->stash(now => $now ); $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) :AllowedRole(ccareadmin) :AllowedRole(ccare) { my ($self, $c) = @_; } sub edit :Chained('base_restricted') :PathPart('edit') :Args(0) { my ($self, $c) = @_; my $contract = $c->stash->{contract}; my $billing_mapping = $c->stash->{billing_mapping}; my $now = $c->stash->{now}; my $billing_profile = $billing_mapping->billing_profile; my $posted = ($c->request->method eq 'POST'); my $form; my $params = { $contract->get_inflated_columns }; foreach(qw/contact profile_package product subscriber_email_template passreset_email_template invoice_email_template invoice_template/){ $params->{$_}{id} = delete $params->{$_.'_id'}; } $params->{billing_profiles} = [ map { { $_->get_inflated_columns }; } $c->stash->{future_billing_mappings}->all ]; $params->{billing_profile}->{id} = $billing_profile->id; $params = merge($params, $c->session->{created_objects}); # TODO: created billing profiles/networks will not be pre-selected #$c->log->debug('customer/edit'); if($c->license('pbx') && $c->config->{features}->{cloudpbx}) { #$c->log->debug('ProductSelect'); $form = NGCP::Panel::Form::get("NGCP::Panel::Form::Contract::ProductSelect", $c); } else { #$c->log->debug('Basic'); $form = NGCP::Panel::Form::get("NGCP::Panel::Form::Contract::Customer", $c); } $form->process( posted => $posted, params => $c->request->params, item => $params ); NGCP::Panel::Utils::Navigation::check_form_buttons( c => $c, form => $form, fields => {'contact.create' => $c->uri_for('/contact/create'), 'billing_profile.create' => $c->uri_for('/billing/create'), 'billing_profiles.profile.create' => $c->uri_for('/billing/create'), 'billing_profiles.network.create' => $c->uri_for('/network/create'), 'profile_package.create' => $c->uri_for('/package/create'), 'subscriber_email_template.create' => $c->uri_for('/emailtemplate/create'), 'passreset_email_template.create' => $c->uri_for('/emailtemplate/create'), 'invoice_email_template.create' => $c->uri_for('/emailtemplate/create'), 'invoice_template.create' => $c->uri_for('/invoicetemplate/create'), }, back_uri => $c->req->uri, ); if($posted && $form->validated) { try { my $schema = $c->model('DB'); $schema->set_transaction_isolation('READ COMMITTED'); $schema->txn_do(sub { foreach(qw/contact billing_profile profile_package product subscriber_email_template passreset_email_template invoice_email_template invoice_template/){ $form->values->{$_.'_id'} = $form->values->{$_}{id} || undef; delete $form->values->{$_}; } #$form->values->{profile_package_id} = undef unless NGCP::Panel::Utils::ProfilePackages::ENABLE_PROFILE_PACKAGES; $form->values->{modify_timestamp} = $now; #problematic for ON UPDATE current_timestamp columns $form->values->{external_id} = $form->field('external_id')->value; unless($form->values->{max_subscribers} && length($form->values->{max_subscribers})) { $form->values->{max_subscribers} = undef; } my $mappings_to_create = []; my $delete_mappings = 0; my $set_package = ($form->values->{billing_profile_definition} // 'id') eq 'package'; NGCP::Panel::Utils::BillingMappings::prepare_billing_mappings( c => $c, resource => $form->values, old_resource => { $contract->get_inflated_columns }, mappings_to_create => $mappings_to_create, now => $now, delete_mappings => \$delete_mappings, err_code => sub { my ($err,@fields) = @_; die( [$err, "showdetails"] ); }); delete $form->values->{product_id}; my $old_prepaid = $billing_mapping->billing_profile->prepaid; my $old_ext_id = $contract->external_id // ''; my $old_status = $contract->status; my $old_package = $contract->profile_package; $contract->update($form->values); NGCP::Panel::Utils::BillingMappings::append_billing_mappings(c => $c, contract => $contract, mappings_to_create => $mappings_to_create, now => $now, delete_mappings => $delete_mappings, ); my $balance = NGCP::Panel::Utils::ProfilePackages::catchup_contract_balances(c => $c, contract => $contract, old_package => $old_package, now => $now); $balance = NGCP::Panel::Utils::ProfilePackages::resize_actual_contract_balance(c => $c, contract => $contract, old_package => $old_package, balance => $balance, now => $now, profiles_added => ($set_package ? scalar @$mappings_to_create : 0), ); $billing_mapping = NGCP::Panel::Utils::BillingMappings::get_actual_billing_mapping(contract => $contract, schema => $schema, now => $now, ); $billing_profile = $billing_mapping->billing_profile; my $new_ext_id = $contract->external_id // ''; # if status changed, populate it down the chain if($contract->status ne $old_status) { NGCP::Panel::Utils::Contract::recursively_lock_contract( c => $c, contract => $contract, ); } if($old_ext_id ne $new_ext_id) { # undef is '' so we don't bail out here foreach my $sub($contract->voip_subscribers->all) { my $prov_sub = $sub->provisioning_voip_subscriber; next unless($prov_sub); NGCP::Panel::Utils::Subscriber::update_preferences( c => $c, prov_subscriber => $prov_sub, preferences => { ext_contract_id => $contract->external_id } ); } } NGCP::Panel::Utils::Subscriber::switch_prepaid_contract(c => $c, prepaid => $billing_profile->prepaid, contract => $contract, ); if ($contract->status eq 'terminated') { delete $c->stash->{close_target}; } delete $c->session->{created_objects}->{contact}; delete $c->session->{created_objects}->{network}; delete $c->session->{created_objects}->{billing_profile}; delete $c->session->{created_objects}->{profile_package}; }); NGCP::Panel::Utils::Message::info( c => $c, data => { $contract->get_inflated_columns }, desc => $c->loc('Customer #[_1] successfully updated', $contract->id), ); } catch($e) { NGCP::Panel::Utils::Message::error( c => $c, error => $e, data => { $contract->get_inflated_columns }, desc => $c->loc('Failed to update customer contract'), ); } NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for('/customer')); } $c->stash(template => 'customer/list.tt'); $c->stash(edit_flag => 1); $c->stash(form => $form); } sub terminate :Chained('base_restricted') :PathPart('terminate') :Args(0) { my ($self, $c) = @_; my $contract = $c->stash->{contract}; if ($contract->id == 1) { NGCP::Panel::Utils::Message::error( c => $c, desc => $c->loc('Cannot terminate contract with the id 1'), ); NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for('/customer')); #/contract? } try { my $old_status = $contract->status; my $schema = $c->model('DB'); $schema->txn_do(sub { $contract->voip_contract_preferences->delete; $contract->update({ status => 'terminated', terminate_timestamp => NGCP::Panel::Utils::DateTime::current_local, }); $contract = $c->stash->{contract_terminated_rs}->first; # if status changed, populate it down the chain if($contract->status ne $old_status) { NGCP::Panel::Utils::Contract::recursively_lock_contract( c => $c, contract => $contract, schema => $schema, ); } }); NGCP::Panel::Utils::Message::info( c => $c, data => { $contract->get_inflated_columns }, desc => $c->loc('Customer successfully terminated'), ); } catch ($e) { NGCP::Panel::Utils::Message::error( c => $c, error => $e, data => { $contract->get_inflated_columns }, desc => $c->loc('Failed to terminate contract'), ); }; NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for('/customer')); #/contract? } sub details :Chained('base') :PathPart('details') :Args(0) { my ($self, $c) = @_; NGCP::Panel::Utils::Sounds::stash_soundset_list(c => $c, contract => $c->stash->{contract}); $c->stash->{contact_hash} = { $c->stash->{contract}->contact->get_inflated_columns }; if(defined $c->stash->{contract}->max_subscribers) { $c->stash->{subscriber_count} = $c->stash->{contract}->voip_subscribers ->search({ status => { -not_in => ['terminated'] } }) ->count; } } sub subscriber_create :Chained('base') :PathPart('subscriber/create') :Args(0) { my ($self, $c) = @_; my $license_max_subscribers = $c->license_max_subscribers; my $current_subscribers_count = $c->license_current_subscribers; if ($license_max_subscribers >= 0 && $current_subscribers_count >= $license_max_subscribers) { NGCP::Panel::Utils::Message::error( c => $c, error => "tried to exceed max number of license subscribers: $license_max_subscribers current: $current_subscribers_count", desc => $c->loc('Maximum number of subscribers for this platform is reached'), ); NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for_action('/customer/details', [$c->stash->{contract}->id]) ); } my $license_max_pbx_subscribers = $c->license_max_pbx_subscribers; my $current_pbx_subscribers_count = $c->license_current_pbx_subscribers; if ($c->stash->{contract}->product->class eq 'pbxaccount' && $license_max_pbx_subscribers >= 0 && $current_pbx_subscribers_count >= $license_max_pbx_subscribers) { NGCP::Panel::Utils::Message::error( c => $c, error => "tried to exceed max number of license pbx subscribers: $license_max_pbx_subscribers current: $current_pbx_subscribers_count", desc => $c->loc('Maximum number of PBX subscribers for this platform is reached'), ); NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for_action('/customer/details', [$c->stash->{contract}->id]) ); } if (defined $c->stash->{contract}->max_subscribers && $c->stash->{contract}->voip_subscribers ->search({ status => { -not_in => ['terminated'] } }) ->count >= $c->stash->{contract}->max_subscribers) { NGCP::Panel::Utils::Message::error( c => $c, error => "tried to exceed max number of customer subscribers: " . $c->stash->{contract}->max_subscribers, desc => $c->loc('Maximum number of subscribers for this customer is reached'), ); NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for_action('/customer/details', [$c->stash->{contract}->id]) ); } my $reseller = $c->stash->{contract}->contact->reseller; my $reseller_max_subscribers = $reseller->contract->max_subscribers; if (defined $reseller_max_subscribers && NGCP::Panel::Utils::Reseller::get_subscribers_count( $c, $reseller ) >= $reseller_max_subscribers) { NGCP::Panel::Utils::Message::error( c => $c, error => "tried to exceed max number of reseller subscribers: " . $reseller_max_subscribers, desc => $c->loc('Maximum number of subscribers for this reseller is reached'), ); NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for_action('/customer/details', [$c->stash->{contract}->id]) ); } my $pbx = 0; my $pbxadmin = 0; $pbx = 1 if $c->stash->{product}->class eq 'pbxaccount'; my $form; my $posted = ($c->request->method eq 'POST'); $c->stash->{pilot} = $c->stash->{subscribers}->search({ 'provisioning_voip_subscriber.is_pbx_pilot' => 1, })->first; my $params = {}; if($c->license('pbx') && $c->config->{features}->{cloudpbx} && $pbx) { $c->stash(customer_id => $c->stash->{contract}->id); # we need to create a pilot subscriber first unless($c->stash->{pilot}) { $pbxadmin = 1; $form = NGCP::Panel::Form::get("NGCP::Panel::Form::Customer::PbxAdminSubscriber", $c); } else { if($c->user->roles eq "subscriberadmin") { $form = NGCP::Panel::Form::get("NGCP::Panel::Form::Customer::PbxExtensionSubscriberSubadmin", $c); } else { $form = NGCP::Panel::Form::get("NGCP::Panel::Form::Customer::PbxExtensionSubscriber", $c); } NGCP::Panel::Utils::Subscriber::prepare_alias_select( c => $c, subscriber => $c->stash->{pilot}, params => $params, unselect => 1, # no numbers assigned yet, keep selection list empty ); NGCP::Panel::Utils::Subscriber::prepare_group_select( c => $c, subscriber => $c->stash->{pilot}, params => $params, unselect => 1, # no groups assigned yet, keep selection list empty ); } } else { $form = NGCP::Panel::Form::get("NGCP::Panel::Form::Customer::Subscriber", $c); } $params = merge($params, $c->session->{created_objects}); if($c->stash->{pilot} && !$params->{domain}{id}) { $params->{domain}{id} = $c->stash->{pilot}->domain_id; } $form->process( posted => $posted, params => $c->request->params, item => $params, ); my $fields = { 'domain.create' => $c->uri_for('/domain/create'), 'group.create' => $c->uri_for_action('/customer/pbx_group_create', $c->req->captures), }; if($pbxadmin) { $fields->{'domain.create'} = $c->uri_for_action('/domain/create', $c->stash->{contract}->contact->reseller_id, 'pbx'); } NGCP::Panel::Utils::Navigation::check_form_buttons( c => $c, form => $form, fields => $fields, back_uri => $c->req->uri, ); if($posted && $form->validated) { unless (NGCP::Panel::Utils::Subscriber::check_pbx_extension_range($c->stash->{contract}, $form->values->{pbx_extension})) { NGCP::Panel::Utils::Message::error( c => $c, error => "tried to input extension out of customer's extension range", desc => $c->loc("Subscriber's PBX extension is out of customer's extension range."), ); NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for_action('/customer/details', [$c->stash->{contract}->id]) ); } my $billing_subscriber; try { my $schema = $c->model('DB'); $schema->set_transaction_isolation('READ COMMITTED'); $schema->txn_do(sub { NGCP::Panel::Utils::Contract::acquire_contract_rowlocks( c => $c, schema => $schema, contract_id => $c->stash->{contract}->id) if $c->stash->{contract}; my $preferences = {}; my $pbx_group_ids = []; if($pbx && !$pbxadmin) { my $pilot = $c->stash->{pilot}; $form->values->{domain}{id} ||= $pilot->domain_id; if ($form->values->{group_select}) { $pbx_group_ids = decode_json($form->values->{group_select}); } my $base_number = $pilot->primary_number; if($base_number) { $preferences->{cloud_pbx_base_cli} = $base_number->cc . $base_number->ac . $base_number->sn; if(defined $form->values->{pbx_extension}) { $form->values->{e164}{cc} = $base_number->cc; $form->values->{e164}{ac} = $base_number->ac; $form->values->{e164}{sn} = $base_number->sn . $form->values->{pbx_extension}; } } } if($pbx) { $form->values->{is_pbx_pilot} = 1 if $pbxadmin; $preferences->{cloud_pbx} = 1; $preferences->{cloud_pbx_ext} = $form->values->{pbx_extension}; if($pbxadmin && $form->values->{e164}{cc} && $form->values->{e164}{sn}) { $preferences->{cloud_pbx_base_cli} = $form->values->{e164}{cc} . ($form->values->{e164}{ac} // '') . $form->values->{e164}{sn}; } if($c->stash->{pilot}) { my $profile_set = $c->stash->{pilot}->provisioning_voip_subscriber->voip_subscriber_profile_set; if($profile_set) { $form->values->{profile_set}{id} = $profile_set->id; } } # TODO: if number changes, also update cloud_pbx_base_cli # TODO: only if it's not a fax/conf extension: $preferences->{shared_buddylist_visibility} = 1; $preferences->{display_name} = $form->values->{display_name} if($form->values->{display_name}); } if($c->stash->{contract}->external_id) { $preferences->{ext_contract_id} = $c->stash->{contract}->external_id; } if(defined $form->values->{external_id}) { $preferences->{ext_subscriber_id} = $form->values->{external_id}; } my @events_to_create = (); my $event_context = { events_to_create => \@events_to_create }; if($form->values->{lock} && ( $form->values->{lock} > 0 ) ){ $form->values->{status} = 'locked'; } $billing_subscriber = NGCP::Panel::Utils::Subscriber::create_subscriber( c => $c, schema => $schema, contract => $c->stash->{contract}, params => $form->values, admin_default => 0, preferences => $preferences, event_context => $event_context, ); if($billing_subscriber->status eq 'locked') { $form->values->{lock} ||= 4; } else { $form->values->{lock} = 0; } my $provisioning_voip_subscriber = $billing_subscriber->provisioning_voip_subscriber; my $default_contract_sound_set_row = $c->stash->{contract}->voip_sound_sets->search( { contract_default => 1 })->first; if ($default_contract_sound_set_row) { NGCP::Panel::Utils::Sounds::subcriber_sound_set_update_or_create( $c, $provisioning_voip_subscriber, $default_contract_sound_set_row->id); } NGCP::Panel::Utils::Subscriber::lock_provisoning_voip_subscriber( c => $c, prov_subscriber => $provisioning_voip_subscriber, level => $form->values->{lock}, ) if ($provisioning_voip_subscriber); NGCP::Panel::Utils::ProfilePackages::underrun_lock_subscriber(c => $c, subscriber => $billing_subscriber); if($pbx && !$pbxadmin && $form->value->{alias_select}) { NGCP::Panel::Utils::Subscriber::update_subadmin_sub_aliases( c => $c, schema => $schema, subscriber => $billing_subscriber, contract_id => $billing_subscriber->contract_id, alias_selected => decode_json($form->value->{alias_select}), sadmin => $c->stash->{pilot}, ); NGCP::Panel::Utils::Subscriber::manage_pbx_groups( c => $c, schema => $schema, group_ids => $pbx_group_ids, customer => $c->stash->{contract}, subscriber => $billing_subscriber, ); } NGCP::Panel::Utils::Events::insert_deferred( c => $c, schema => $schema, events_to_create => \@events_to_create, ); }); delete $c->session->{created_objects}->{domain}; delete $c->session->{created_objects}->{group}; NGCP::Panel::Utils::Message::info( c => $c, desc => $c->loc('Subscriber successfully created'), ); } catch($e) { NGCP::Panel::Utils::Message::error( c => $c, error => $e, desc => $c->loc('Failed to create subscriber'), ); } NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for_action('/customer/details', [$c->stash->{contract}->id]) ); } $c->stash(create_flag => 1); $c->stash(form => $form); return; } sub edit_fraud :Chained('base_restricted') :PathPart('fraud/edit') :Args(1) { my ($self, $c, $type) = @_; my $posted = ($c->request->method eq 'POST'); my $form; if($type eq "month") { $form = NGCP::Panel::Form::get("NGCP::Panel::Form::CustomerFraudPreferences::CustomerMonthlyFraud", $c); } elsif($type eq "day") { $form = NGCP::Panel::Form::get("NGCP::Panel::Form::CustomerFraudPreferences::CustomerDailyFraud", $c); } else { NGCP::Panel::Utils::Message::error( c => $c, log => "Invalid fraud interval '$type'!", desc => $c->loc("Invalid fraud interval '[_1]'!",$type), ); $c->response->redirect($c->uri_for_action("/customer/details", [$c->stash->{contract}->id])); return; } my $fraud_prefs = $c->stash->{fraud} || $c->model('DB')->resultset('contract_fraud_preferences') ->new_result({ contract_id => $c->stash->{contract}->id}); $form->process( posted => $posted, params => $c->request->params, action => $c->uri_for_action("/customer/edit_fraud", $c->stash->{contract}->id, $type), item => $fraud_prefs, ); if($posted && $form->validated) { NGCP::Panel::Utils::Message::info( c => $c, data => { $fraud_prefs->get_inflated_columns }, desc => $c->loc('Fraud settings successfully changed!'), ); $c->response->redirect($c->uri_for_action("/customer/details", [$c->stash->{contract}->id])); return; } $c->stash(close_target => $c->uri_for_action("/customer/details", [$c->stash->{contract}->id])); $c->stash(form => $form); $c->stash(edit_flag => 1); } sub delete_fraud :Chained('base_restricted') :PathPart('fraud/delete') :Args(1) { my ($self, $c, $type) = @_; if($type eq "month") { $type = "interval"; } elsif($type eq "day") { $type = "daily"; } else { NGCP::Panel::Utils::Message::error( c => $c, log => "Invalid fraud interval '$type'!", desc => $c->loc("Invalid fraud interval '[_1]'!",$type), ); $c->response->redirect($c->uri_for_action("/customer/details", [$c->stash->{contract}->id])); return; } my $fraud_prefs = $c->stash->{fraud}; if($fraud_prefs) { try { $fraud_prefs->update({ "fraud_${type}_limit" => undef, "fraud_${type}_lock" => undef, "fraud_${type}_notify" => undef, }); } catch($e) { NGCP::Panel::Utils::Message::error( c => $c, error => $e, data => { $fraud_prefs->get_inflated_columns }, desc => $c->loc('Failed to clear fraud interval'), ); $c->response->redirect($c->uri_for_action("/customer/details", [$c->stash->{contract}->id])); return; } } NGCP::Panel::Utils::Message::info( c => $c, data => { $fraud_prefs->get_inflated_columns }, desc => $c->loc('Successfully cleared fraud interval!'), ); $c->response->redirect($c->uri_for_action("/customer/details", [$c->stash->{contract}->id])); return; } sub edit_balance :Chained('base_restricted') :PathPart('balance/edit') :Args(0) { my ($self, $c) = @_; my $balance = $c->stash->{balance}; my $contract = $c->stash->{contract}; my $now = $c->stash->{now}; my $posted = ($c->request->method eq 'POST'); my $form = NGCP::Panel::Form::get("NGCP::Panel::Form::Balance::CustomerBalance", $c); my $params = { $balance->get_inflated_columns }; # cash_balance => $balance->cash_balance, # free_time_balance => $balance->free_time_balance, # }; $form->process( posted => $posted, params => $c->request->params, item => $params, ); NGCP::Panel::Utils::Navigation::check_form_buttons( c => $c, form => $form, fields => {}, back_uri => $c->req->uri, ); if($posted && $form->validated) { my $entities = { contract => $contract, }; my $log_vals = {}; try { my $schema = $c->model('DB'); $schema->set_transaction_isolation('READ COMMITTED'); $schema->txn_do(sub { $balance = NGCP::Panel::Utils::ProfilePackages::get_contract_balance(c => $c, contract => $contract, now => $now); $balance = NGCP::Panel::Utils::ProfilePackages::set_contract_balance( c => $c, balance => $balance, cash_balance => $form->values->{cash_balance}, free_time_balance => $form->values->{free_time_balance}, now => $now, log_vals => $log_vals); my $topup_log = NGCP::Panel::Utils::ProfilePackages::create_topup_log_record( c => $c, now => $now, entities => $entities, log_vals => $log_vals, request_token => NGCP::Panel::Utils::ProfilePackages::PANEL_TOPUP_REQUEST_TOKEN, ); }); NGCP::Panel::Utils::Message::info( c => $c, desc => $c->loc('Account balance successfully changed!'), ); } catch($e) { NGCP::Panel::Utils::Message::error( c => $c, error => $e, desc => $c->loc('Failed to change account balance!'), ); } $c->response->redirect($c->uri_for_action("/customer/details", [$contract->id])); return; } $c->stash(close_target => $c->uri_for_action("/customer/details", [$contract->id])); $c->stash(form => $form); $c->stash(edit_flag => 1); } sub topup_cash :Chained('base_restricted') :PathPart('balance/topupcash') :Args(0) { my ($self, $c) = @_; my $contract = $c->stash->{contract}; my $now = $c->stash->{now}; my $posted = ($c->request->method eq 'POST'); my $form = NGCP::Panel::Form::get("NGCP::Panel::Form::Topup::Cash", $c); my $params = {}; $form->process( posted => $posted, params => $c->request->params, item => $params, ); NGCP::Panel::Utils::Navigation::check_form_buttons( c => $c, form => $form, fields => { 'package.create' => $c->uri_for('/package/create'), }, back_uri => $c->req->uri, ); if($posted && $form->validated) { my $success = 0; my $entities = {}; my $log_vals = {}; try { my $schema = $c->model('DB'); $schema->set_transaction_isolation('READ COMMITTED'); $schema->txn_do(sub { NGCP::Panel::Utils::Voucher::check_topup(c => $c, now => $now, contract => $contract, package_id => $form->values->{package}{id}, resource => $form->values, entities => $entities, err_code => sub { my ($err) = @_; die([$err, "showdetails"]); }, ); my $balance = NGCP::Panel::Utils::ProfilePackages::topup_contract_balance(c => $c, contract => $contract, package => $entities->{package}, log_vals => $log_vals, #old_package => $customer->profile_package, amount => $form->values->{amount}, now => $now, request_token => NGCP::Panel::Utils::ProfilePackages::PANEL_TOPUP_REQUEST_TOKEN, ); delete $c->session->{created_objects}->{package}; }); $success = 1; NGCP::Panel::Utils::Message::info( c => $c, desc => $c->loc('Top-up using cash performed successfully!'), ); } catch($e) { NGCP::Panel::Utils::Message::error( c => $c, error => $e, desc => $c->loc('Failed to top-up using cash!'), ); } try { $c->model('DB')->txn_do(sub { my $topup_log = NGCP::Panel::Utils::ProfilePackages::create_topup_log_record( c => $c, is_cash => 1, now => $now, entities => $entities, log_vals => $log_vals, resource => $form->values, is_success => $success, request_token => NGCP::Panel::Utils::ProfilePackages::PANEL_TOPUP_REQUEST_TOKEN, ); }); } catch($e) { $c->log->error("failed to create topup log record: $e"); } $c->response->redirect($c->uri_for_action("/customer/details", [$contract->id])); return; } $c->stash(close_target => $c->uri_for_action("/customer/details", [$contract->id])); $c->stash(form => $form); $c->stash(edit_flag => 1); } sub topup_voucher :Chained('base_restricted') :PathPart('balance/topupvoucher') :Args(0) { my ($self, $c) = @_; $c->detach('/denied_page') unless($c->config->{features}->{voucher}); my $contract = $c->stash->{contract}; my $now = $c->stash->{now}; my $posted = ($c->request->method eq 'POST'); my $form = NGCP::Panel::Form::get("NGCP::Panel::Form::Topup::Voucher", $c); my $params = {}; $form->process( posted => $posted, params => $c->request->params, item => $params, ); NGCP::Panel::Utils::Navigation::check_form_buttons( c => $c, form => $form, fields => {}, back_uri => $c->req->uri, ); if($posted && $form->validated) { my $success = 0; my $entities = {}; my $log_vals = {}; try { my $schema = $c->model('DB'); $schema->set_transaction_isolation('READ COMMITTED'); $schema->txn_do(sub { NGCP::Panel::Utils::Voucher::check_topup(c => $c, now => $now, contract => $contract, voucher_id => $form->values->{voucher}{id}, resource => $form->values, entities => $entities, err_code => sub { my ($err) = @_; die([$err, "showdetails"]); }, ); my $balance = NGCP::Panel::Utils::ProfilePackages::topup_contract_balance(c => $c, contract => $contract, voucher => $entities->{voucher}, log_vals => $log_vals, now => $now, request_token => NGCP::Panel::Utils::ProfilePackages::PANEL_TOPUP_REQUEST_TOKEN, ); $entities->{voucher}->update({ #used_by_subscriber_id => $resource->{subscriber_id}, used_at => $now, }); }); $success = 1; NGCP::Panel::Utils::Message::info( c => $c, desc => $c->loc('Top-up using voucher performed successfully!'), ); } catch($e) { NGCP::Panel::Utils::Message::error( c => $c, error => $e, desc => $c->loc('Failed to top-up using voucher!'), ); } try { $c->model('DB')->txn_do(sub { my $topup_log = NGCP::Panel::Utils::ProfilePackages::create_topup_log_record( c => $c, is_cash => 0, now => $now, entities => $entities, log_vals => $log_vals, resource => $form->values, is_success => $success, request_token => NGCP::Panel::Utils::ProfilePackages::PANEL_TOPUP_REQUEST_TOKEN, ); }); } catch($e) { $c->log->error("failed to create topup log record: $e"); } $c->response->redirect($c->uri_for_action("/customer/details", [$contract->id])); return; } $c->stash(close_target => $c->uri_for_action("/customer/details", [$contract->id])); $c->stash(form => $form); $c->stash(edit_flag => 1); } sub billingmappings_ajax :Chained('base') :PathPart('billingmappings/ajax') :Args(0) { my ($self, $c) = @_; $c->stash(timeline_data => { contract => { $c->stash->{contract}->get_columns }, events => NGCP::Panel::Utils::BillingMappings::get_billingmappings_timeline_data($c,$c->stash->{contract}), }); $c->detach( $c->view("JSON") ); } sub balanceinterval_ajax :Chained('base') :PathPart('balanceinterval/ajax') :Args(0) { my ($self, $c) = @_; my $res = $c->stash->{contract}->contract_balances; NGCP::Panel::Utils::Datatables::process($c, $res, $c->stash->{balanceinterval_dt_columns}); $c->detach( $c->view("JSON") ); } sub topuplog_ajax :Chained('base') :PathPart('topuplog/ajax') :Args(0) { my ($self, $c) = @_; my $res = $c->stash->{contract}->topup_log; NGCP::Panel::Utils::Datatables::process($c, $res, $c->stash->{topuplog_dt_columns}); $c->detach( $c->view("JSON") ); } sub subscriber_ajax :Chained('base') :PathPart('subscriber/ajax') :Args(0) { my ($self, $c) = @_; my $res = $c->stash->{contract}->voip_subscribers->search({ 'provisioning_voip_subscriber.is_pbx_group' => 0, 'me.status' => { '!=' => 'terminated' }, },{ join => 'provisioning_voip_subscriber', }); NGCP::Panel::Utils::Datatables::process($c, $res, $c->stash->{subscriber_dt_columns}); $c->detach( $c->view("JSON") ); } sub pbx_group_ajax :Chained('base') :PathPart('pbx/group/ajax') :Args(0) { my ($self, $c) = @_; my $subscriber_id = $c->req->params->{subscriber_id} // 0; my $subscriber; if($subscriber_id && is_int($subscriber_id)) { $subscriber = $c->model('DB')->resultset('voip_subscribers')->search({ 'me.status' => { '!=' => 'terminated' }, })->find( { id => $subscriber_id } ); } my $res = $c->stash->{contract}->voip_subscribers->search({ 'provisioning_voip_subscriber.is_pbx_group' => 1, },{ 'join' => 'provisioning_voip_subscriber', ( defined $subscriber ? ( '+select' => [ {'' => \['select voip_pbx_groups.id from provisioning.voip_pbx_groups where voip_pbx_groups.group_id=provisioning_voip_subscriber.id and subscriber_id=?', [ {} => $subscriber->provisioning_voip_subscriber->id ] ], '-as' => 'sort_field' }, {'' => \['(select voip_pbx_groups.id from provisioning.voip_pbx_groups where voip_pbx_groups.group_id=provisioning_voip_subscriber.id and subscriber_id=?) is null', [ {} => $subscriber->provisioning_voip_subscriber->id ] ], '-as' => 'sort_field_is_null' }, ], '+as' => ['sort_field','sort_field_is_null'], 'order_by' => ['sort_field_is_null','sort_field'], ) : (), ), }); NGCP::Panel::Utils::Datatables::process($c, $res, $c->stash->{pbxgroup_dt_columns}); $c->detach( $c->view("JSON") ); } sub pbx_group_create :Chained('base') :PathPart('pbx/group/create') :Args(0) { my ($self, $c) = @_; my $license_max_pbx_groups = $c->license_max_pbx_groups; my $current_pbx_groups_count = $c->license_current_pbx_groups; if ($license_max_pbx_groups >= 0 && $current_pbx_groups_count >= $license_max_pbx_groups) { NGCP::Panel::Utils::Message::error( c => $c, error => "tried to exceed max number of license pbx groups: $license_max_pbx_groups current: $current_pbx_groups_count", desc => $c->loc('Maximum number of pbx groups for this platform is reached'), ); NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for_action('/customer/details', [$c->stash->{contract}->id]) ); } if(defined $c->stash->{contract}->max_subscribers && $c->stash->{contract}->voip_subscribers ->search({ status => { -not_in => ['terminated'] } }) ->count >= $c->stash->{contract}->max_subscribers) { NGCP::Panel::Utils::Message::error( c => $c, error => "tried to exceed max number of subscribers of " . $c->stash->{contract}->max_subscribers, desc => $c->loc('Maximum number of subscribers for this customer reached'), ); NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for_action('/customer/details', [$c->stash->{contract}->id]) ); } my $posted = ($c->request->method eq 'POST'); my $pilot = $c->stash->{subscribers}->search({ 'provisioning_voip_subscriber.is_pbx_pilot' => 1, })->first; unless($pilot) { NGCP::Panel::Utils::Message::error( c => $c, error => 'cannot create pbx group without having a pilot subscriber', desc => $c->loc("Can't create a PBX group without having a pilot subscriber."), ); NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for_action('/customer/details', [$c->stash->{contract}->id]) ); } my $form; $form = NGCP::Panel::Form::get("NGCP::Panel::Form::Customer::PbxGroup", $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, fields => {}, back_uri => $c->req->uri, ); if($posted && $form->validated) { try { my $schema = $c->model('DB'); $schema->set_transaction_isolation('READ COMMITTED'); $schema->txn_do( sub { NGCP::Panel::Utils::Contract::acquire_contract_rowlocks( c => $c, schema => $schema, contract_id => $c->stash->{contract}->id) if $c->stash->{contract}; my $preferences = {}; my $base_number = $pilot->primary_number; if($base_number) { $preferences->{cloud_pbx_base_cli} = $base_number->cc . $base_number->ac . $base_number->sn; if(defined $form->values->{pbx_extension}) { $form->values->{e164}{cc} = $base_number->cc; $form->values->{e164}{ac} = $base_number->ac; $form->values->{e164}{sn} = $base_number->sn . $form->values->{pbx_extension}; } } $form->values->{is_pbx_pilot} = 0; $form->values->{is_pbx_group} = 1; $form->values->{domain}{id} = $pilot->domain_id; $form->values->{status} = 'active'; $preferences->{cloud_pbx} = 1; $preferences->{cloud_pbx_hunt_policy} = $form->values->{pbx_hunt_policy}; $preferences->{cloud_pbx_hunt_timeout} = $form->values->{pbx_hunt_timeout}; $preferences->{cloud_pbx_hunt_cancel_mode} = $form->values->{pbx_hunt_cancel_mode}; $preferences->{cloud_pbx_ext} = $form->values->{pbx_extension}; $preferences->{display_name} = ucfirst($form->values->{username}); my @events_to_create = (); my $event_context = { events_to_create => \@events_to_create }; my $billing_subscriber = NGCP::Panel::Utils::Subscriber::create_subscriber( c => $c, schema => $schema, contract => $c->stash->{contract}, params => $form->values, admin_default => 0, preferences => $preferences, event_context => $event_context, ); NGCP::Panel::Utils::ProfilePackages::underrun_lock_subscriber(c => $c, subscriber => $billing_subscriber); NGCP::Panel::Utils::Events::insert_deferred( c => $c, schema => $schema, events_to_create => \@events_to_create, ); $c->session->{created_objects}->{group} = { id => $billing_subscriber->id }; }); NGCP::Panel::Utils::Message::info( c => $c, desc => $c->loc('PBX group successfully created'), ); } catch ($e) { NGCP::Panel::Utils::Message::error( c => $c, error => $e, desc => $c->loc('Failed to create PBX group'), ); } NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for_action('/customer/details', [$c->stash->{contract}->id]) ); } $c->stash( create_flag => 1, form => $form, description => $c->loc('PBX Group'), ); } sub pbx_group_base :Chained('base') :PathPart('pbx/group') :CaptureArgs(1) { my ($self, $c, $group_id) = @_; my $group = $c->stash->{pbx_groups}->find($group_id); unless($group) { NGCP::Panel::Utils::Message::error( c => $c, error => "invalid voip pbx group id $group_id", desc => $c->loc('PBX group with id [_1] does not exist.',$group_id), ); NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for_action('/customer/details', [$c->stash->{contract}->id]) ); } $c->stash->{pilot} = $c->stash->{subscribers}->search({ 'provisioning_voip_subscriber.is_pbx_pilot' => 1, })->first; $c->stash( pbx_group => $group, ); } sub pbx_group_edit :Chained('pbx_group_base') :PathPart('edit') :Args(0) { my ($self, $c) = @_; my $posted = ($c->request->method eq 'POST'); my $form; $form = NGCP::Panel::Form::get("NGCP::Panel::Form::Customer::PbxGroupEdit", $c); my $params = { $c->stash->{pbx_group}->provisioning_voip_subscriber->get_inflated_columns }; $params = merge($params, $c->session->{created_objects}); unless ($posted) { NGCP::Panel::Utils::Subscriber::prepare_alias_select( c => $c, subscriber => $c->stash->{pbx_group}, params => $params, ); } $form->process( posted => $posted, params => $c->request->params, item => $params, ); NGCP::Panel::Utils::Navigation::check_form_buttons( c => $c, form => $form, fields => {}, back_uri => $c->req->uri, ); if($posted && $form->validated) { try { my $schema = $c->model('DB'); $schema->txn_do(sub { my $old_extension = $c->stash->{pbx_group}->provisioning_voip_subscriber->pbx_extension; $c->stash->{pbx_group}->provisioning_voip_subscriber->update({ pbx_extension => $form->values->{pbx_extension}, pbx_hunt_policy => $form->values->{pbx_hunt_policy}, pbx_hunt_timeout => $form->values->{pbx_hunt_timeout}, pbx_hunt_cancel_mode => $form->values->{pbx_hunt_cancel_mode}, }); NGCP::Panel::Utils::Subscriber::update_preferences( c => $c, prov_subscriber => $c->stash->{pbx_group}->provisioning_voip_subscriber, 'preferences' => { cloud_pbx_hunt_policy => $form->values->{pbx_hunt_policy}, cloud_pbx_hunt_timeout => $form->values->{pbx_hunt_timeout}, cloud_pbx_hunt_cancel_mode => $form->values->{pbx_hunt_cancel_mode}, } ); my $e164; my $sub = $c->stash->{pbx_group}; my $base_number = $c->stash->{pilot}->primary_number; if(defined $form->values->{pbx_extension} && $form->values->{pbx_extension} ne $old_extension && $base_number) { $e164 = { cc => $base_number->cc, ac => $base_number->ac, sn => $base_number->sn . $form->values->{pbx_extension}, }; } my $aliases_before = NGCP::Panel::Utils::Events::get_aliases_snapshot( c => $c, schema => $schema, subscriber => $sub, ); NGCP::Panel::Utils::Subscriber::update_subscriber_numbers( c => $c, schema => $schema, subscriber_id => $sub->id, reseller_id => $sub->contract->contact->reseller_id, $e164 ? (primary_number => $e164) : (), $c->user->roles eq 'subscriberadmin' ? () : (alias_numbers => $form->values->{alias_number}), ); if(exists $form->values->{alias_select} && $c->stash->{pilot}) { NGCP::Panel::Utils::Subscriber::update_subadmin_sub_aliases( c => $c, schema => $schema, subscriber => $sub, contract_id => $sub->contract_id, alias_selected => decode_json($form->values->{alias_select}), sadmin => $c->stash->{pilot}, ); } #ready for number change events here }); NGCP::Panel::Utils::Message::info( c => $c, desc => $c->loc('PBX group successfully updated'), ); } catch ($e) { NGCP::Panel::Utils::Message::error( c => $c, error => $e, desc => $c->loc('Failed to update PBX group'), ); } NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for_action('/customer/details', [$c->stash->{contract}->id]) ); } $c->stash( edit_flag => 1, form => $form ); } sub pbx_device_create :Chained('base') :PathPart('pbx/device/create') :Args(0) { my ($self, $c) = @_; my $posted = ($c->request->method eq 'POST'); $c->stash->{autoprov_profile_rs} = $c->model('DB')->resultset('autoprov_profiles') ->search({ 'device.reseller_id' => $c->stash->{contract}->contact->reseller_id, },{ order_by => { -asc => 'name' }, join => { 'config' => 'device' }, }); my $form = NGCP::Panel::Form::get("NGCP::Panel::Form::Customer::PbxFieldDevice", $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, fields => {}, back_uri => $c->req->uri, ); if($posted && $form->validated) { try { my $err; my $schema = $c->model('DB'); $schema->txn_do( sub { my $station_name = $form->values->{station_name}; my $identifier = lc $form->values->{identifier}; if($identifier =~ /^([a-f0-9]{2}:){5}[a-f0-9]{2}$/) { $identifier =~ s/\://g; } my $profile_id = $form->values->{profile_id}; my $fdev = $c->stash->{contract}->autoprov_field_devices->create({ profile_id => $profile_id, identifier => $identifier, station_name => $station_name, }); if($fdev->profile->config->device->bootstrap_method eq "redirect_yealink") { my @chars = ("A".."Z", "a".."z", "0".."9"); my $device_key = ""; $device_key .= $chars[rand @chars] for 0 .. 15; $fdev->update({ encryption_key => $device_key }); } $err = NGCP::Panel::Utils::DeviceBootstrap::dispatch( $c, 'register', $fdev); unless($err) { $err = $c->forward('pbx_device_lines_update', [$schema, $fdev, [$form->field('line')->fields]]); } else { die $err; } }); unless($err) { NGCP::Panel::Utils::Message::info( c => $c, desc => $c->loc('PBX device successfully created'), ); } else { die $err; } } catch ($e) { NGCP::Panel::Utils::Message::error( c => $c, error => $e, desc => $c->loc('Failed to create PBX device'), ); } NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for_action('/customer/details', [$c->stash->{contract}->id]) ); } $c->stash( device_flag => 1, create_flag => 1, form => $form, description => $c->loc('PBX Device'), ); } sub pbx_device_base :Chained('base') :PathPart('pbx/device') :CaptureArgs(1) { my ($self, $c, $dev_id) = @_; my $dev = $c->model('DB')->resultset('autoprov_field_devices')->find($dev_id); unless($dev) { NGCP::Panel::Utils::Message::error( c => $c, error => "invalid voip pbx device id $dev_id", desc => $c->loc('PBX device with id [_1] does not exist.',$dev_id), ); NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for_action('/customer/details', [$c->stash->{contract}->id]) ); } if($dev->contract->id != $c->stash->{contract}->id) { NGCP::Panel::Utils::Message::error( c => $c, error => "invalid voip pbx device id $dev_id for customer id '".$c->stash->{contract}->id."'", desc => $c->loc('PBX device with id [_1] does not exist for this customer.',$dev_id), ); NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for_action('/customer/details', [$c->stash->{contract}->id]) ); } $c->stash( pbx_device => $dev, ); } sub pbx_device_edit :Chained('pbx_device_base') :PathPart('edit') :Args(0) { my ($self, $c) = @_; my $posted = ($c->request->method eq 'POST'); $c->stash->{autoprov_profile_rs} = $c->model('DB')->resultset('autoprov_profiles') ->search({ 'device.reseller_id' => $c->stash->{contract}->contact->reseller_id, },{ join => { 'config' => 'device' }, }); my $form = NGCP::Panel::Form::get("NGCP::Panel::Form::Customer::PbxFieldDevice", $c); my $params = { $c->stash->{pbx_device}->get_inflated_columns }; my @lines = (); foreach my $line($c->stash->{pbx_device}->autoprov_field_device_lines->all) { push @lines, { subscriber_id => $line->subscriber_id, line => $line->linerange_id . '.' . $line->key_num, type => $line->line_type, target_number => $line->target_number, }; } $params->{line} = \@lines; $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, fields => {}, back_uri => $c->req->uri, ); if($posted && $form->validated) { try { my $err = 0; my $schema = $c->model('DB'); $schema->txn_do( sub { my $fdev = $c->stash->{pbx_device}; my $station_name = $form->values->{station_name}; my $identifier = lc $form->values->{identifier}; if($identifier =~ /^([a-f0-9]{2}:){5}[a-f0-9]{2}$/) { $identifier =~ s/\://g; } my $old_identifier = $fdev->identifier; my $profile_id = $form->values->{profile_id}; $fdev->update({ profile_id => $profile_id, identifier => $identifier, station_name => $station_name, }); unless($fdev->identifier eq $old_identifier) { $err = NGCP::Panel::Utils::DeviceBootstrap::dispatch( $c, 'register', $fdev, $old_identifier); } unless($err) { $fdev->autoprov_field_device_lines->delete_all; $err = $c->forward('pbx_device_lines_update', [$schema, $fdev, [$form->field('line')->fields]]); } }); unless($err) { NGCP::Panel::Utils::Message::info( c => $c, desc => $c->loc('PBX device successfully updated'), ); } else { die $err; } } catch ($e) { NGCP::Panel::Utils::Message::error( c => $c, error => $e, desc => $c->loc('Failed to update PBX device'), ); } NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for_action('/customer/details', [$c->stash->{contract}->id]) ); return; } $c->stash( device_flag => 1, edit_flag => 1, form => $form, description => $c->loc('PBX Device'), ); } sub pbx_device_lines_update :Private{ my($self, $c, $schema, $fdev, $lines) = @_; my $err = 0; foreach my $line(@$lines) { my $is_custom_number = 0; my $target_number; if ($line->field('switch') && $line->field('switch')->value) { $is_custom_number = 1; $target_number = $line->field('target_number')->value // next; } else { next unless $line->field('subscriber_id')->value; } my $prov_subscriber = $schema->resultset('provisioning_voip_subscribers')->find({ $is_custom_number ? (is_pbx_pilot => 1) : (id => $line->field('subscriber_id')->value), account_id => $c->stash->{contract}->id, }); unless($prov_subscriber) { NGCP::Panel::Utils::Message::error( c => $c, error => "invalid provisioning subscriber_id '".$line->field('subscriber_id')->value. "' for contract id '".$c->stash->{contract}->id."'", desc => $c->loc('Invalid provisioning subscriber id detected.'), ); # TODO: throw exception here! $err = 1; last; } else { my ($range_id, $key_num, $unit_short) = split /\./, $line->field('line')->value; my $type = $line->field('type')->value; my $unit = $line->field('extension_unit')->value || $unit_short || 0; $fdev->autoprov_field_device_lines->create({ subscriber_id => $prov_subscriber->id, linerange_id => $range_id, key_num => $key_num, line_type => $type, extension_unit => $unit, target_number => $target_number, }); } } return $err; } sub pbx_device_delete :Chained('pbx_device_base') :PathPart('delete') :Args(0) { my ($self, $c) = @_; try { my $fdev = $c->stash->{pbx_device}; NGCP::Panel::Utils::DeviceBootstrap::dispatch( $c, 'unregister', $fdev, $fdev->identifier ); $fdev->delete; NGCP::Panel::Utils::Message::info( c => $c, data => { $c->stash->{pbx_device}->get_inflated_columns }, desc => $c->loc('PBX Device successfully deleted'), ); } catch($e) { NGCP::Panel::Utils::Message::error( c => $c, error => "failed to delete PBX device with id '".$c->stash->{pbx_device}->id."': $e", data => { $c->stash->{pbx_device}->get_inflated_columns }, desc => $c->loc('Failed to delete PBX device'), ); } NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for_action('/customer/details', [$c->stash->{contract}->id]) ); } sub pbx_device_sync :Chained('pbx_device_base') :PathPart('sync') :Args(0) { my ($self, $c) = @_; my $form = NGCP::Panel::Form::get("NGCP::Panel::Form::Customer::PbxFieldDeviceSync", $c); my $posted = ($c->req->method eq 'POST'); my $dev = $c->stash->{pbx_device}; foreach my $line($dev->autoprov_field_device_lines->search({ line_type => 'private', })->all) { my $sub = $line->provisioning_voip_subscriber; next unless($sub); my $reg_rs = NGCP::Panel::Utils::Subscriber::get_subscriber_location_rs( $c, { username => $sub->username, $c->config->{features}->{multidomain} ? (domain => $sub->domain->domain) : (), } ); my $uri = $sub->username . '@' . $sub->domain->domain; if($reg_rs->count) { $c->log->debug("trigger device resync for $uri as it is registered"); my $proxy_rs = $c->model('DB')->resultset('xmlgroups') ->search_rs({name => 'proxy'}) ->search_related('xmlhostgroups')->search_related('host'); my $proxy = $proxy_rs->first; unless($proxy) { NGCP::Panel::Utils::Message::error( c => $c, desc => $c->loc('Failed to trigger config reload via SIP'), error => 'Failed to load proxy from xmlhosts', ); NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for_action('/customer/details', [$c->stash->{contract}->id]) ); return; } my @cmd_args = ($c->config->{cloudpbx}->{sync}, $sub->username, $sub->domain->domain, $sub->password, $proxy->ip . ":" . $proxy->sip_port); my @out = capturex(EXIT_ANY, "/bin/sh", @cmd_args); if($EXITVAL != 0) { use Data::Dumper; NGCP::Panel::Utils::Message::error( c => $c, desc => $c->loc('Failed to trigger config reload via SIP'), error => 'Result: ' . Dumper \@out, ); } else { NGCP::Panel::Utils::Message::info( c => $c, desc => $c->loc('Successfully triggered config reload via SIP'), ); } NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for_action('/customer/details', [$c->stash->{contract}->id]) ); return; } } my $params = {}; $form->process( posted => $posted, params => $c->req->params, item => $params, ); if($posted && $form->validated) { NGCP::Panel::Utils::Message::info( c => $c, desc => $c->loc('Successfully redirected request to device'), ); NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for_action('/customer/details', [$c->stash->{contract}->id]) ); } my $schema = $c->config->{deviceprovisioning}->{secure} ? 'https' : 'http'; my $host = $c->config->{deviceprovisioning}->{host} // $c->req->uri->host; my $port = $c->config->{deviceprovisioning}->{bootstrap_port} // 1445; my $t = Template->new; my $conf = { client => { ip => '__NGCP_CLIENT_IP__', }, server => { uri => $dev->profile->config->device->bootstrap_uri // "$schema://$host:$port/device/autoprov/bootstrap", }, }; my $sync_params_rs = $dev->profile->config->device->autoprov_sync->search_rs({ 'autoprov_sync_parameters.bootstrap_method' => 'http', },{ join => 'autoprov_sync_parameters', select => ['me.parameter_value'], }); my ($sync_uri, $real_sync_uri) = ("", ""); $sync_uri = $sync_params_rs->search({ 'autoprov_sync_parameters.parameter_name' => 'sync_uri', }); if($sync_uri && $sync_uri->first){ $sync_uri = $sync_uri->first->parameter_value; } $t->process(\$sync_uri, $conf, \$real_sync_uri); my ($sync_params_field, $real_sync_params) = ("", ""); $sync_params_field = $sync_params_rs->search({ 'autoprov_sync_parameters.parameter_name' => 'sync_params', }); # TODO: replace server.uri var by custom bootstrap uri if set? if($sync_params_field && $sync_params_field->first){ $sync_params_field = $sync_params_field->first->parameter_value; } my ($sync_method) = ""; $sync_method = $sync_params_rs->search({ 'autoprov_sync_parameters.parameter_name' => 'sync_method', }); if($sync_method && $sync_method->first){ $sync_method = $sync_method->first->parameter_value; } my @sync_params = (); if($sync_params_field) { $t->process(\$sync_params_field, $conf, \$real_sync_params); foreach my $p(split /\s*\,\s*/, $real_sync_params) { my ($k, $v) = split /=/, $p; if(defined $k && defined $v) { push @sync_params, { key => $k, value => $v }; } elsif(defined $k) { push @sync_params, { key => $k, value => 0 }; } } } $c->stash( form => $form, devsync_flag => 1, autoprov_uri => $real_sync_uri, autoprov_method => $sync_method, autoprov_params => \@sync_params, ); } sub pbx_device_preferences_list :Chained('pbx_device_base') :PathPart('preferences') :CaptureArgs(0) { my ($self, $c) = @_; my $fdev = $c->stash->{pbx_device}; my $devmod = $fdev->profile->config->device; $c->stash->{devmod} = $devmod; my $dev_pref_rs = NGCP::Panel::Utils::Preferences::get_preferences_rs( c => $c, type => 'fielddev', id => $fdev->id, ); my $pref_values = get_inflated_columns_all($dev_pref_rs,'hash' => 'attribute', 'column' => 'value', 'force_array' => 1); NGCP::Panel::Utils::Preferences::load_preference_list( c => $c, pref_values => $pref_values, fielddev_pref => 1, search_conditions => [{ 'attribute' => [ -or => { 'like' => 'vnd_'.lc($devmod->vendor).'%' }, {'-not_like' => 'vnd_%' }, ], #relation type is defined by preference flag dev_pref, #so here we select only linked to the current model, or not linked to any model at all '-or' => [ 'voip_preference_relations.autoprov_device_id' => $devmod->id, 'voip_preference_relations.reseller_id' => $devmod->reseller_id, 'voip_preference_relations.voip_preference_id' => undef ], },{ join => {'voip_preferences' => 'voip_preference_relations'}, } ] ); $c->stash(template => 'customer/pbx_fdev_preferences.tt'); return; } sub pbx_device_preferences_root :Chained('pbx_device_preferences_list') :PathPart('') :Args(0) { return; } sub pbx_device_preferences_base :Chained('pbx_device_preferences_list') :PathPart('') :CaptureArgs(1) { my ($self, $c, $pref_id) = @_; $c->stash->{preference_meta} = $c->model('DB') ->resultset('voip_preferences') ->search({ -or => [ 'voip_preferences_enums.fielddev_pref' => 1, 'voip_preferences_enums.fielddev_pref' => undef ], },{ prefetch => 'voip_preferences_enums', }) ->find({id => $pref_id}); $c->stash->{preference} = $c->model('DB') ->resultset('voip_fielddev_preferences') ->search({ 'attribute_id' => $pref_id, 'device_id' => $c->stash->{pbx_device}->id, }); return; } sub pbx_device_preferences_edit :Chained('pbx_device_preferences_base') :PathPart('edit') :Args(0) { my ($self, $c) = @_; $c->stash(edit_preference => 1); my @enums = $c->stash->{preference_meta} ->voip_preferences_enums ->all; my $devmod = $c->stash->{devmod}; my $pref_rs = $c->stash->{pbx_device}->voip_fielddev_preferences->search_rs({ 'attribute' => [ -or => { 'like' => 'vnd_'.lc($devmod->vendor).'%' }, {'-not_like' => 'vnd_%' }, ], #relation type is defined by preference flag dev_pref, #so here we select only linked to the current model, or not linked to any model at all '-or' => [ 'voip_preference_relations.autoprov_device_id' => $devmod->id, 'voip_preference_relations.reseller_id' => $devmod->reseller_id, 'voip_preference_relations.voip_preference_id' => undef ], },{ join => {'attribute' => 'voip_preference_relations'}, }); NGCP::Panel::Utils::Preferences::create_preference_form( c => $c, pref_rs => $pref_rs, enums => \@enums, base_uri => $c->uri_for_action('/customer/pbx_device_preferences_root', [@{ $c->req->captures }[0,1]] ), edit_uri => $c->uri_for_action('/customer/pbx_device_preferences_edit', $c->req->captures ), ); return; } sub location_ajax :Chained('base') :PathPart('location/ajax') :Args(0) { my ($self, $c) = @_; NGCP::Panel::Utils::Datatables::process($c, @{$c->stash}{qw(locations location_dt_columns)}); $c->detach( $c->view("JSON") ); } sub location_create :Chained('base_restricted') :PathPart('location/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::Customer::Location", $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 { my $vcl = $c->model('DB')->resultset('voip_contract_locations')->create({ contract_id => $c->stash->{contract}->id, name => $form->values->{name}, description => $form->values->{description}, }); for my $block (@{$form->values->{blocks}}) { $vcl->create_related("voip_contract_location_blocks", $block); } $c->session->{created_objects}->{network} = { id => $vcl->id }; }); NGCP::Panel::Utils::Message::info( c => $c, desc => $c->loc('Location successfully created'), ); } catch ($e) { NGCP::Panel::Utils::Message::error( c => $c, error => $e, desc => $c->loc('Failed to create location.'), ); } NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for_action("/customer/details", [$contract->id])); } $c->stash( description => $c->loc('Location'), close_target => $c->uri_for_action("/customer/details", [$contract->id]), create_flag => 1, form => $form ); } sub location_base :Chained('base_restricted') :PathPart('location') :CaptureArgs(1) { my ($self, $c, $location_id) = @_; unless($location_id && is_int($location_id)) { $location_id //= ''; NGCP::Panel::Utils::Message::error( c => $c, data => { id => $location_id }, desc => $c->loc('Invalid location id detected'), ); $c->response->redirect($c->uri_for()); $c->detach; return; } my $res = $c->stash->{contract}->voip_contract_locations->find($location_id); unless(defined($res)) { NGCP::Panel::Utils::Message::error( c => $c, desc => $c->loc('Location does not exist'), ); $c->response->redirect($c->uri_for()); $c->detach; return; } $c->stash(location => {$res->get_inflated_columns}, location_blocks => [ map { { $_->get_inflated_columns }; } $res->voip_contract_location_blocks->all ], location_result => $res); } sub location_edit :Chained('location_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::Customer::Location", $c); my $params = $c->stash->{location}; $params->{blocks} = $c->stash->{location_blocks}; $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->{'location_result'}->update({ name => $form->values->{name}, description => $form->values->{description}, }); $c->stash->{'location_result'}->voip_contract_location_blocks->delete; for my $block (@{$form->values->{blocks}}) { $c->stash->{'location_result'}->create_related("voip_contract_location_blocks", $block); } }); NGCP::Panel::Utils::Message::info( c => $c, desc => $c->loc('Location successfully updated'), ); } catch ($e) { NGCP::Panel::Utils::Message::error( c => $c, error => $e, desc => $c->loc('Failed to update location'), ); } 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 location_delete :Chained('location_base') :PathPart('delete') :Args(0) { my ($self, $c) = @_; my $contract = $c->stash->{contract}; my $location = $c->stash->{location_result}; try { $location->delete; NGCP::Panel::Utils::Message::info( c => $c, data => $c->stash->{location}, desc => $c->loc('Location successfully deleted'), ); } catch ($e) { NGCP::Panel::Utils::Message::error( c => $c, error => $e, data => $c->stash->{location}, desc => $c->loc('Failed to terminate location'), ); }; NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for_action("/customer/details", [$contract->id])); } sub location_preferences :Chained('location_base') :PathPart('preferences') :Args(0) { my ($self, $c) = @_; $self->load_preference_list($c); $c->stash(template => 'customer/preferences.tt'); } sub location_preferences_base :Chained('location_base') :PathPart('preferences') :CaptureArgs(1) { my ($self, $c, $pref_id) = @_; $self->preferences_base($c, $pref_id); } sub location_preferences_edit :Chained('location_preferences_base') :PathPart('edit') :Args(0) { my ($self, $c) = @_; my $contract = $c->stash->{contract}; my $location = $c->stash->{location}; my $pref_id = $c->stash->{pref_id}; my $base_uri = $c->uri_for($contract->id, 'location', $location->{id}, 'preferences'); my $edit_uri = $c->uri_for($contract->id, 'location', $location->{id}, 'preferences', $pref_id, 'edit'); $c->stash(edit_preference => 1); my @enums = $c->stash->{preference_meta} ->voip_preferences_enums ->search({contract_pref => 1, contract_location_pref => 1}) ->all; my $pref_rs = $contract->voip_contract_preferences( { location_id => $location->{id} }, undef); NGCP::Panel::Utils::Preferences::create_preference_form( c => $c, pref_rs => $pref_rs, enums => \@enums, base_uri => $base_uri, edit_uri => $edit_uri, ); } sub preferences :Chained('base') :PathPart('preferences') :Args(0) { my ($self, $c) = @_; $self->load_preference_list($c); $c->stash(template => 'customer/preferences.tt'); } sub preferences_base :Chained('base') :PathPart('preferences') :CaptureArgs(1) { my ($self, $c, $pref_id) = @_; $self->load_preference_list($c); $c->stash->{preference_meta} = $c->model('DB') ->resultset('voip_preferences') ->single({id => $pref_id}); if($c->user->roles eq 'subscriberadmin' && !$c->stash->{preference_meta}->expose_to_customer) { $c->log->error("invalid access to pref_id '$pref_id' by provisioning subscriber id '".$c->user->id."'"); $c->detach('/denied_page'); } my $blob_short_value_size = NGCP::Panel::Utils::Preferences::get_blob_short_value_size; $c->stash->{preference} = $c->model('DB') ->resultset('voip_contract_preferences') ->search({ attribute_id => $pref_id, contract_id => $c->stash->{contract}->id, location_id => $c->stash->{location}{id} || undef, },{ join => 'blob', '+select' => [ \"SUBSTRING(blob.value, 1, $blob_short_value_size)" ], '+as' => [ 'short_blob_value' ], }); $c->stash->{pref_id} = $pref_id; $c->stash(template => 'customer/preferences.tt'); } sub preferences_edit :Chained('preferences_base') :PathPart('edit') :Args(0) { my ($self, $c) = @_; $c->stash(edit_preference => 1); my @enums = $c->stash->{preference_meta} ->voip_preferences_enums ->search({contract_pref => 1}) ->all; my $pref_rs = $c->stash->{contract}->voip_contract_preferences( { location_id => undef }, undef); my $base_uri = $c->uri_for_action('/customer/preferences', [$c->stash->{contract}->id]); my $edit_uri = $c->uri_for_action('/customer/preferences_edit', [$c->stash->{contract}->id]); NGCP::Panel::Utils::Preferences::create_preference_form( c => $c, pref_rs => $pref_rs, enums => \@enums, base_uri => $base_uri, edit_uri => $edit_uri, blob_rs => $c->model('DB')->resultset('voip_contract_preferences_blob'), ); } sub load_preference_list :Private { my ($self, $c) = @_; my $contract_pref_values = $c->model('DB') ->resultset('voip_preferences') ->search({ contract_id => $c->stash->{contract}->id, 'voip_contract_preferences.location_id' => $c->stash->{location}{id} || undef, },{ prefetch => 'voip_contract_preferences', }); my %pref_values; foreach my $value($contract_pref_values->all) { if ($value->data_type eq "blob") { $pref_values{$value->attribute} = [ map {$_->blob ? $_->blob->content_type : ''} $value->voip_contract_preferences->all ]; } else { $pref_values{$value->attribute} = [ map {$_->value} $value->voip_contract_preferences->all ]; } } my $reseller_id = $c->stash->{contract}->contact->reseller_id; my $ncos_levels_rs = $c->model('DB')->resultset('ncos_levels')->search_rs({ reseller_id => $reseller_id, $c->user->roles eq 'subscriberadmin' ? (expose_to_customer => 1) : () }); $c->stash(ncos_levels_rs => $ncos_levels_rs, ncos_levels => [$ncos_levels_rs->all]); my $ncos_sets_rs = $c->model('DB')->resultset('ncos_sets')->search_rs({ reseller_id => $reseller_id, $c->user->roles eq 'subscriberadmin' ? (expose_to_customer => 1) : () }); $c->stash(ncos_sets_rs => $ncos_sets_rs, ncos_sets => [$ncos_sets_rs->all]); my $emergency_mapping_containers_rs = $c->model('DB') ->resultset('emergency_containers') ->search_rs({ reseller_id => $reseller_id, }); $c->stash(emergency_mapping_containers_rs => $emergency_mapping_containers_rs, emergency_mapping_containers => [$emergency_mapping_containers_rs->all]); NGCP::Panel::Utils::Preferences::load_preference_list( c => $c, pref_values => \%pref_values, contract_pref => 1, contract_location_pref => $c->stash->{location}{id} ? 1 : 0, customer_view => ($c->user->roles eq 'subscriberadmin' ? 1 : 0), ); } sub is_valid_contact { my ($self, $c, $contact_id) = @_; my $contact = $c->model('DB')->resultset('contacts')->search_rs({ 'id' => $contact_id, #'reseller_id' => { '!=' => undef }, 'status' => { '!=' => 'terminated' }, })->first; if( $contact ) { return 1; } else { return 0; } } 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_root :Chained('base') :PathPart('phonebook') :Args(0) { my ($self, $c) = @_; } sub phonebook_create :Chained('base') :PathPart('phonebook/create') :Args(0) :Does(License) :RequiresLicense('phonebook') :LicenseDetachTo('/denied_page') { 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]), phonebook_create_flag => 1, form => $form ); } sub phonebook_base :Chained('base') :PathPart('phonebook') :CaptureArgs(1) :Does(License) :RequiresLicense('phonebook') :LicenseDetachTo('/denied_page') { 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]), phonebook_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) :Does(License) :RequiresLicense('phonebook') :LicenseDetachTo('/denied_page') { 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( phonebook_create_flag => 1, form => $form, ); return; } sub phonebook_download_csv :Chained('base') :PathPart('phonebook_download_csv') :Args(0) :Does(License) :RequiresLicense('phonebook') :LicenseDetachTo('/denied_page') { 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,,, =head1 LICENSE This library is free software. You can redistribute it and/or modify it under the same terms as Perl itself. =cut 1; # vim: set tabstop=4 expandtab: