From b05628db60d3390887fa14c83580d51b2c01b111 Mon Sep 17 00:00:00 2001 From: Rene Krenn Date: Mon, 14 Feb 2022 14:11:57 +0100 Subject: [PATCH] TT#81004 support callforwards in provisioning templates provisioning templates can now include configuration of callforward mappings, eg.: cf_mappings: cfu: [] cfb: - enabled: 1 destinationset: name: "test" destinations: - destination: "123" cft: [] cfna: [] cfs: [] cfr: [] cfo: [] Change-Id: I8073322630d63e5ef23e7a9f8c299a6010cb4866 --- .../Panel/Controller/API/CFDestinationSets.pm | 12 +- lib/NGCP/Panel/Role/API/CFDestinationSets.pm | 53 +- lib/NGCP/Panel/Role/API/CFMappings.pm | 413 ++------------- lib/NGCP/Panel/Utils/CallForwards.pm | 491 ++++++++++++++++++ lib/NGCP/Panel/Utils/ProvisioningTemplates.pm | 115 +++- tools_bin/ngcp-provisioning-template | 27 +- 6 files changed, 694 insertions(+), 417 deletions(-) create mode 100644 lib/NGCP/Panel/Utils/CallForwards.pm diff --git a/lib/NGCP/Panel/Controller/API/CFDestinationSets.pm b/lib/NGCP/Panel/Controller/API/CFDestinationSets.pm index 4944ec671a..0b0ab17030 100644 --- a/lib/NGCP/Panel/Controller/API/CFDestinationSets.pm +++ b/lib/NGCP/Panel/Controller/API/CFDestinationSets.pm @@ -9,6 +9,7 @@ use Data::HAL::Link qw(); use HTTP::Headers qw(); use HTTP::Status qw(:constants); +use NGCP::Panel::Utils::CallForwards qw(); sub allowed_methods{ return [qw/GET POST OPTIONS HEAD/]; @@ -157,9 +158,18 @@ sub POST :Allow { if (! exists $resource->{destinations} ) { $resource->{destinations} = []; } - if (!$self->check_destinations($c, $resource)) { + + if (!NGCP::Panel::Utils::CallForwards::check_destinations( + c => $c, + resource => $resource, + err_code => sub { + my ($err) = @_; + $self->error($c, HTTP_UNPROCESSABLE_ENTITY, $err); + }, + )) { last; } + try { my $primary_nr_rs = $b_subscriber->primary_number; my $number; diff --git a/lib/NGCP/Panel/Role/API/CFDestinationSets.pm b/lib/NGCP/Panel/Role/API/CFDestinationSets.pm index 846548e5da..bb2018f79b 100644 --- a/lib/NGCP/Panel/Role/API/CFDestinationSets.pm +++ b/lib/NGCP/Panel/Role/API/CFDestinationSets.pm @@ -12,6 +12,7 @@ use HTTP::Status qw(:constants); use JSON::Types; use NGCP::Panel::Utils::Subscriber; use NGCP::Panel::Form; +use NGCP::Panel::Utils::CallForwards qw(); sub get_form { my ($self, $c) = @_; @@ -129,7 +130,16 @@ sub update_item { if (! exists $resource->{destinations} ) { $resource->{destinations} = []; } - if(!$self->check_destinations($c, $resource)){ + + if(!NGCP::Panel::Utils::CallForwards::check_destinations( + c => $c, + schema => $schema, + resource => $resource, + err_code => sub { + my ($err) = @_; + $self->error($c, HTTP_UNPROCESSABLE_ENTITY, $err); + }, + )){ return; } @@ -190,46 +200,5 @@ sub update_item { return $item; } -sub check_destinations{ - my($self,$c,$resource) = @_; - if (ref $resource->{destinations} ne "ARRAY") { - $self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Invalid field 'destinations'. Must be an array."); - return; - } - for my $d (@{ $resource->{destinations} }) { - if (exists $d->{timeout} && ! is_int($d->{timeout})) { - $c->log->error("Invalid timeout for the destination '".$c->qs($d->{destination})."'"); - $self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Invalid timeout for the destination '".$c->qs($d->{destination})."'"); - return; - } - if (exists $d->{priority} && ! is_int($d->{priority})) { - $c->log->error("Invalid priority for the destination '".$c->qs($d->{destination})."'"); - $self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Invalid priority for the destination '".$c->qs($d->{destination})."'"); - return; - } - if (defined $d->{announcement_id}) { - #todo: I think that user expects that put and get will be the same - if(('customhours' ne $d->{destination}) && ('sip:custom-hours@app.local' ne $d->{destination}) ){ - $c->log->error("Invalid parameter 'announcement_id' for the destination '".$c->qs($d->{destination})."'"); - $self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Invalid parameter 'announcement_id' for the destination '".$c->qs($d->{destination})."'"); - return; - }elsif(! is_int($d->{announcement_id})){ - $c->log->error("Invalid announcement_id"); - $self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Invalid announcement_id"); - return; - }elsif(! $c->model('DB')->resultset('voip_sound_handles')->search_rs({ - 'me.id' => $d->{announcement_id}, - 'group.name' => 'custom_announcements', - },{ - 'join' => 'group', - })->first() ){ - $c->log->error("Unknown announcement_id: ".$d->{announcement_id}); - $self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Unknown announcement_id:".$d->{announcement_id}); - return; - } - } - } - return 1; -} 1; # vim: set tabstop=4 expandtab: diff --git a/lib/NGCP/Panel/Role/API/CFMappings.pm b/lib/NGCP/Panel/Role/API/CFMappings.pm index 48091d3cdd..7ce94645d2 100644 --- a/lib/NGCP/Panel/Role/API/CFMappings.pm +++ b/lib/NGCP/Panel/Role/API/CFMappings.pm @@ -11,9 +11,13 @@ use Data::HAL qw(); use Data::HAL::Link qw(); use HTTP::Status qw(:constants); use JSON::Types; -use NGCP::Panel::Utils::Subscriber; use NGCP::Panel::Utils::Preferences; use NGCP::Panel::Form; +use NGCP::Panel::Utils::CallForwards qw(); +use NGCP::Panel::Role::API::CFDestinationSets qw(); +use NGCP::Panel::Role::API::CFTimeSets qw(); +use NGCP::Panel::Role::API::CFSourceSets qw(); +use NGCP::Panel::Role::API::CFBNumberSets qw(); sub get_form { my ($self, $c) = @_; @@ -139,369 +143,58 @@ sub item_by_id { sub update_item { my ($self, $c, $item, $old_resource, $resource, $form, $params) = @_; - - $params //= {}; - - if (ref $resource ne "HASH") { - $self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Must be a hash."); - return; - } - - delete $resource->{id}; - my $schema = $c->model('DB'); - - return unless $self->validate_form( + + return NGCP::Panel::Utils::CallForwards::update_cf_mappings( c => $c, - form => $form, resource => $resource, - ); - - my $mappings_rs = $item->provisioning_voip_subscriber->voip_cf_mappings; - my $p_subs_id = $item->provisioning_voip_subscriber->id; - my $domain = $item->provisioning_voip_subscriber->domain->domain // ''; - my $primary_nr_rs = $item->primary_number; - my $number; - if ($primary_nr_rs) { - $number = $primary_nr_rs->cc . ($primary_nr_rs->ac //'') . $primary_nr_rs->sn; - } else { - $number = $item->uuid; - } - my @new_mappings; - my @new_destinations; - my @new_times; - my @new_sources; - my @new_bnumbers; - my @new_dsets; - my @new_tsets; - my @new_ssets; - my @new_bsets; - my %cf_preferences; - my $dsets_rs = $c->model('DB')->resultset('voip_cf_destination_sets'); - my $tsets_rs = $c->model('DB')->resultset('voip_cf_time_sets'); - my $ssets_rs = $c->model('DB')->resultset('voip_cf_source_sets'); - my $bsets_rs = $c->model('DB')->resultset('voip_cf_bnumber_sets'); - my $dset_max_id = $dsets_rs->search( undef, { for => 'update' } )->get_column('id')->max() // -1; - my $tset_max_id = $tsets_rs->search( undef, { for => 'update' } )->get_column('id')->max() // -1; - my $sset_max_id = $ssets_rs->search( undef, { for => 'update' } )->get_column('id')->max() // -1; - my $bset_max_id = $bsets_rs->search( undef, { for => 'update' } )->get_column('id')->max() // -1; - - for my $type ( qw/cfu cfb cft cfna cfs cfr cfo/) { - if (ref $resource->{$type} ne "ARRAY") { - $self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Invalid field '$type'. Must be an array."); - return; - } - - $cf_preferences{$type} = NGCP::Panel::Utils::Preferences::get_usr_preference_rs( - c => $c, prov_subscriber => $item->provisioning_voip_subscriber, attribute => $type); - for my $mapping (@{ $resource->{$type} }) { - my $dset; - if(defined $mapping->{destinationset_id}) { - $dset = $dsets_rs->find({ - subscriber_id => $p_subs_id, - id => $mapping->{destinationset_id}, - }); - } elsif ($mapping->{destinationset} && !ref $mapping->{destinationset}) { - $dset = $dsets_rs->find({ - subscriber_id => $p_subs_id, - name => $mapping->{destinationset}, - }); - } elsif ($mapping->{destinationset} && ref $mapping->{destinationset} eq 'HASH') { - $mapping->{destinationset}->{subscriber_id} = $p_subs_id; - $form = NGCP::Panel::Role::API::CFDestinationSets->get_form($c); - return unless $self->validate_form( - c => $c, - resource => $mapping->{destinationset}, - form => $form, - ); - if (! exists $mapping->{destinationset}->{destinations} ) { - $mapping->{destinationset}->{destinations} = []; - } - if (!NGCP::Panel::Role::API::CFDestinationSets->check_destinations($c, $mapping->{destinationset})) { - return; - } - - $dset_max_id +=2; - $dset = { - id => $dset_max_id, - name => $mapping->{destinationset}->{name}, - subscriber_id => $p_subs_id, - }; - push @new_dsets, $dset; - for my $d ( @{$mapping->{destinationset}->{destinations}} ) { - delete $d->{destination_set_id}; - delete $d->{simple_destination}; - $d->{destination} = NGCP::Panel::Utils::Subscriber::field_to_destination( - destination => $d->{destination}, - number => $number, - domain => $domain, - uri => $d->{destination}, - ); - $d->{destination_set_id} = $dset_max_id; - push @new_destinations, $d; - } - } else { - $self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Missing field 'destinationset' or 'destinationset_id' in '$type'."); - return; - } - unless ($dset) { - $self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Invalid 'destinationset'. Could not be found."); - return; - } - - my $tset; my $has_tset; - if (defined $mapping->{timeset_id}) { - $tset = $tsets_rs->find({ - subscriber_id => $p_subs_id, - id => $mapping->{timeset_id}, - }); - $has_tset = 1; - } elsif (defined $mapping->{timeset} && !ref $mapping->{timeset}) { - $tset = $tsets_rs->find({ - subscriber_id => $p_subs_id, - name => $mapping->{timeset}, - }); - $has_tset = 1; - } elsif ($mapping->{timeset} && ref $mapping->{timeset} eq 'HASH') { - $mapping->{timeset}->{subscriber_id} = $p_subs_id; - $form = NGCP::Panel::Role::API::CFTimeSets->get_form($c); - return unless $self->validate_form( - c => $c, - resource => $mapping->{timeset}, - form => $form, - ); - if (! exists $mapping->{timeset}->{times} ) { - $mapping->{timeset}->{times} = []; - } - if (ref $mapping->{timeset}->{times} ne "ARRAY") { - $self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Invalid field 'times'. Must be an array."); - return; - } - $tset_max_id +=2; - $tset = { - id => $tset_max_id, - name => $mapping->{timeset}->{name}, - subscriber_id => $p_subs_id, - }; - push @new_tsets, $tset; - for my $t ( @{$mapping->{timeset}->{times}} ) { - delete $t->{time_set_id}; - $t->{time_set_id} = $tset_max_id; - push @new_times, $t; - } - } - if($has_tset && !$tset) { - $self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Invalid 'timeset'. Could not be found."); - return; - } - - my $sset; my $has_sset; - if (defined $mapping->{sourceset_id}) { - $sset = $ssets_rs->find({ - subscriber_id => $p_subs_id, - id => $mapping->{sourceset_id}, - }); - $has_sset = 1; - } elsif (defined $mapping->{sourceset} && !ref $mapping->{sourceset}) { - $sset = $ssets_rs->find({ - subscriber_id => $p_subs_id, - name => $mapping->{sourceset}, - }); - $has_sset = 1; - } elsif ($mapping->{sourceset} && ref $mapping->{sourceset} eq 'HASH') { - $mapping->{sourceset}->{subscriber_id} = $p_subs_id; - $form = NGCP::Panel::Role::API::CFSourceSets->get_form($c); - return unless $self->validate_form( - c => $c, - resource => $mapping->{sourceset}, - form => $form, - ); - if (! exists $mapping->{sourceset}->{sources} ) { - $mapping->{sourceset}->{sources} = []; - } - if (ref $mapping->{sourceset}->{sources} ne "ARRAY") { - $self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Invalid field 'sources'. Must be an array."); - return; - } - - $sset_max_id +=2; - $sset = { - id => $sset_max_id, - name => $mapping->{sourceset}->{name}, - mode => $mapping->{sourceset}->{mode}, - is_regex => $mapping->{sourceset}->{is_regex} // 0, - subscriber_id => $p_subs_id, - }; - push @new_ssets, $sset; - for my $s ( @{$mapping->{sourceset}->{sources}} ) { - push @new_sources, { - source_set_id => $sset_max_id, - source => $s->{source}, - }; - } - } - if($has_sset && !$sset) { - $self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Invalid 'sourceset'. Could not be found."); - return; - } - - my $bset; my $has_bset; - if (defined $mapping->{bnumberset_id}) { - $bset = $bsets_rs->find({ - subscriber_id => $p_subs_id, - id => $mapping->{bnumberset_id}, - }); - $has_bset = 1; - } elsif (defined $mapping->{bnumberset} && !ref $mapping->{bnumberset}) { - $bset = $bsets_rs->find({ - subscriber_id => $p_subs_id, - name => $mapping->{bnumberset}, - }); - $has_bset = 1; - } elsif ($mapping->{bnumberset} && ref $mapping->{bnumberset} eq 'HASH') { - $mapping->{bnumberset}->{subscriber_id} = $p_subs_id; - $form = NGCP::Panel::Role::API::CFBNumberSets->get_form($c); - return unless $self->validate_form( - c => $c, - resource => $mapping->{bnumberset}, - form => $form, - ); - if (! exists $mapping->{bnumberset}->{bnumbers} ) { - $mapping->{bnumberset}->{bnumbers} = []; - } - if (ref $mapping->{bnumberset}->{bnumbers} ne "ARRAY") { - $self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Invalid field 'bnumbers'. Must be an array."); - return; - } - - $bset_max_id +=2; - $bset = { - id => $bset_max_id, - name => $mapping->{bnumberset}->{name}, - mode => $mapping->{bnumberset}->{mode}, - is_regex => $mapping->{bnumberset}->{is_regex} // 0, - subscriber_id => $p_subs_id, - }; - push @new_bsets, $bset; - for my $b ( @{$mapping->{bnumberset}->{bnumbers}} ) { - push @new_bnumbers, { - bnumber_set_id => $bset_max_id, - bnumber => $b->{bnumber}, - }; - } - } - if($has_bset && !$bset) { - $self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Invalid 'bnumberset'. Could not be found."); - return; - } - push @new_mappings, { - destination_set_id => ref $dset eq 'HASH' ? $dset->{id} : $dset->id, - time_set_id => ref $tset eq 'HASH' ? $tset->{id} : ( $tset ? $tset->id : undef ), - source_set_id => ref $sset eq 'HASH' ? $sset->{id} : ( $sset ? $sset->id : undef ), - bnumber_set_id => ref $bset eq 'HASH' ? $bset->{id} : ( $bset ? $bset->id : undef ), - type => $type, - enabled => defined $mapping->{enabled} ? $mapping->{enabled} : 1, - }; - } - } - - try { - my $autoattendant_count = 0; - $c->model('DB')->resultset('voip_cf_destination_sets')->populate(\@new_dsets); - $c->model('DB')->resultset('voip_cf_time_sets')->populate(\@new_tsets); - $c->model('DB')->resultset('voip_cf_source_sets')->populate(\@new_ssets); - $c->model('DB')->resultset('voip_cf_bnumber_sets')->populate(\@new_bsets); - $c->model('DB')->resultset('voip_cf_destinations')->populate(\@new_destinations); - $c->model('DB')->resultset('voip_cf_periods')->populate(\@new_times); - $c->model('DB')->resultset('voip_cf_sources')->populate(\@new_sources); - $c->model('DB')->resultset('voip_cf_bnumbers')->populate(\@new_bnumbers); - - unless ($params->{add_only}) { - foreach my $map($mappings_rs->all) { - $autoattendant_count += NGCP::Panel::Utils::Subscriber::check_dset_autoattendant_status($map->destination_set); - } - $mappings_rs->delete; - for my $type ( qw/cfu cfb cft cfna cfs cfr cfo/) { - $cf_preferences{$type}->delete; - } - } - - $mappings_rs->populate(\@new_mappings); - for my $type ( qw/cfu cfb cft cfna cfs cfr cfo/) { - my @mapping_ids_by_type = $mappings_rs->search( - { - type => $type - }, - { - select => [qw/me.id /], - as => [qw/value/], - result_class => 'DBIx::Class::ResultClass::HashRefInflator' - } - )->all(); - $cf_preferences{$type}->populate(\@mapping_ids_by_type); - } - - unless ($params->{add_only}) { - for my $mapping ($mappings_rs->all) { - $autoattendant_count -= NGCP::Panel::Utils::Subscriber::check_dset_autoattendant_status($mapping->destination_set); - } - } else { - for my $d (@new_destinations) { - $autoattendant_count -= (NGCP::Panel::Utils::Subscriber::destination_to_field($d->{destination}))[0] eq 'autoattendant' ? 1 : 0; - } - } - - if ($autoattendant_count > 0) { - while ($autoattendant_count != 0) { - $autoattendant_count--; - NGCP::Panel::Utils::Events::insert( - c => $c, schema => $c->model('DB'), - subscriber_id => $item->id, - type => 'end_ivr', - ); - } - } elsif ($autoattendant_count < 0) { - while ($autoattendant_count != 0) { - $autoattendant_count++; - NGCP::Panel::Utils::Events::insert( - c => $c, schema => $c->model('DB'), - subscriber_id => $item->id, - type => 'start_ivr', - ); - } - } - - if ($resource->{cft_ringtimeout} && $resource->{cft_ringtimeout} > 0) { - my $ringtimeout_preference = NGCP::Panel::Utils::Preferences::get_usr_preference_rs( - c => $c, attribute => 'ringtimeout', prov_subscriber => $item->provisioning_voip_subscriber); - - if($ringtimeout_preference->first) { - $ringtimeout_preference->first->update({ - value => $resource->{cft_ringtimeout}, - }); - } else { - $ringtimeout_preference->create({ - value => $resource->{cft_ringtimeout}, - }); - } - } elsif ($c->model('DB')->resultset('voip_cf_mappings')->search_rs({ - subscriber_id => $item->provisioning_voip_subscriber->id, - type => 'cft', - enabled => 1, - })->count == 0) { - NGCP::Panel::Utils::Preferences::get_usr_preference_rs( + item => $item, + err_code => sub { + my ($err) = @_; + $self->error($c, HTTP_UNPROCESSABLE_ENTITY, $err); + }, + validate_mapping_code => sub { + my $res = shift; + return $self->validate_form( c => $c, - attribute => 'ringtimeout', - prov_subscriber => $item->provisioning_voip_subscriber)->delete; - } - - } catch($e) { - $c->log->error("failed to create cfmapping: $e"); - $self->error($c, HTTP_INTERNAL_SERVER_ERROR, "Failed to create cfmapping."); - return; - }; + form => $form, + resource => $res, + ); + }, + validate_destination_set_code => sub { + my $res = shift; + return $self->validate_form( + c => $c, + form => NGCP::Panel::Role::API::CFDestinationSets->get_form($c), + resource => $res, + ); + }, + validate_time_set_code => sub { + my $res = shift; + return $self->validate_form( + c => $c, + form => NGCP::Panel::Role::API::CFTimeSets->get_form($c), + resource => $res, + ); + }, + validate_source_set_code => sub { + my $res = shift; + return $self->validate_form( + c => $c, + form => NGCP::Panel::Role::API::CFSourceSets->get_form($c), + resource => $res, + ); + }, + validate_bnumber_set_code => sub { + my $res = shift; + return $self->validate_form( + c => $c, + form => NGCP::Panel::Role::API::CFBNumberSets->get_form($c), + resource => $res, + ); + }, + params => $params, + ); - return $item; } 1; -# vim: set tabstop=4 expandtab: diff --git a/lib/NGCP/Panel/Utils/CallForwards.pm b/lib/NGCP/Panel/Utils/CallForwards.pm new file mode 100644 index 0000000000..dfdb54f33f --- /dev/null +++ b/lib/NGCP/Panel/Utils/CallForwards.pm @@ -0,0 +1,491 @@ +package NGCP::Panel::Utils::CallForwards; +use strict; +use warnings; + +use Sipwise::Base; + +use NGCP::Panel::Utils::Subscriber qw(); +use NGCP::Panel::Utils::Preferences qw(); + +sub update_cf_mappings { + my %params = @_; + + my ($c, + $schema, + $resource, + $item, + $err_code, + $validate_mapping_code, + $validate_destination_set_code, + $validate_time_set_code, + $validate_source_set_code, + $validate_bnumber_set_code, + $params) = @params{qw/ + c + schema + resource + item + err_code + validate_mapping_code + validate_destination_set_code + validate_time_set_code + validate_source_set_code + validate_bnumber_set_code + params + /}; + + $params //= {}; + + if (!defined $err_code || ref $err_code ne 'CODE') { + $err_code = sub { }; + } + if (!defined $validate_mapping_code || ref $validate_mapping_code ne 'CODE') { + $validate_mapping_code = sub { return 1; }; + } + if (!defined $validate_destination_set_code || ref $validate_destination_set_code ne 'CODE') { + $validate_destination_set_code = sub { return 1; }; + } + if (!defined $validate_time_set_code || ref $validate_time_set_code ne 'CODE') { + $validate_time_set_code = sub { return 1; }; + } + if (!defined $validate_source_set_code || ref $validate_source_set_code ne 'CODE') { + $validate_source_set_code = sub { return 1; }; + } + if (!defined $validate_bnumber_set_code || ref $validate_bnumber_set_code ne 'CODE') { + $validate_bnumber_set_code = sub { return 1; }; + } + + if (ref $resource ne "HASH") { + &{$err_code}("Must be a hash."); + return; + } + + delete $resource->{id}; + $schema //= $c->model('DB'); + + return unless &$validate_mapping_code($resource); + + #return unless $self->validate_form( + # c => $c, + # form => $form, + # resource => $resource, + #); + + my $mappings_rs = $item->provisioning_voip_subscriber->voip_cf_mappings; + my $p_subs_id = $item->provisioning_voip_subscriber->id; + my $domain = $item->provisioning_voip_subscriber->domain->domain // ''; + my $primary_nr_rs = $item->primary_number; + my $number; + if ($primary_nr_rs) { + $number = $primary_nr_rs->cc . ($primary_nr_rs->ac //'') . $primary_nr_rs->sn; + } else { + $number = $item->uuid; + } + my @new_mappings; + my @new_destinations; + my @new_times; + my @new_sources; + my @new_bnumbers; + my @new_dsets; + my @new_tsets; + my @new_ssets; + my @new_bsets; + my %cf_preferences; + my $dsets_rs = $schema->resultset('voip_cf_destination_sets'); + my $tsets_rs = $schema->resultset('voip_cf_time_sets'); + my $ssets_rs = $schema->resultset('voip_cf_source_sets'); + my $bsets_rs = $schema->resultset('voip_cf_bnumber_sets'); + my $dset_max_id = $dsets_rs->search( undef, { for => 'update' } )->get_column('id')->max() // -1; + my $tset_max_id = $tsets_rs->search( undef, { for => 'update' } )->get_column('id')->max() // -1; + my $sset_max_id = $ssets_rs->search( undef, { for => 'update' } )->get_column('id')->max() // -1; + my $bset_max_id = $bsets_rs->search( undef, { for => 'update' } )->get_column('id')->max() // -1; + + for my $type ( qw/cfu cfb cft cfna cfs cfr cfo/) { + if (ref $resource->{$type} ne "ARRAY") { + &{$err_code}("Invalid field '$type'. Must be an array."); + return; + } + + $cf_preferences{$type} = NGCP::Panel::Utils::Preferences::get_usr_preference_rs( + c => $c, prov_subscriber => $item->provisioning_voip_subscriber, attribute => $type); + for my $mapping (@{ $resource->{$type} }) { + my $dset; + if(defined $mapping->{destinationset_id}) { + $dset = $dsets_rs->find({ + subscriber_id => $p_subs_id, + id => $mapping->{destinationset_id}, + }); + } elsif ($mapping->{destinationset} && !ref $mapping->{destinationset}) { + $dset = $dsets_rs->find({ + subscriber_id => $p_subs_id, + name => $mapping->{destinationset}, + }); + } elsif ($mapping->{destinationset} && ref $mapping->{destinationset} eq 'HASH') { + $mapping->{destinationset}->{subscriber_id} = $p_subs_id; + return unless &$validate_destination_set_code($mapping->{destinationset}); + #$form = NGCP::Panel::Role::API::CFDestinationSets->get_form($c); + #return unless $self->validate_form( + # c => $c, + # resource => $mapping->{destinationset}, + # form => $form, + #); + if (! exists $mapping->{destinationset}->{destinations} ) { + $mapping->{destinationset}->{destinations} = []; + } + + if (!check_destinations( + c => $c, + schema => $schema, + resource => $mapping->{destinationset}, + err_code => $err_code, + )) { + return; + } + + $dset_max_id +=2; + $dset = { + id => $dset_max_id, + name => $mapping->{destinationset}->{name}, + subscriber_id => $p_subs_id, + }; + push @new_dsets, $dset; + for my $d ( @{$mapping->{destinationset}->{destinations}} ) { + delete $d->{destination_set_id}; + delete $d->{simple_destination}; + $d->{destination} = NGCP::Panel::Utils::Subscriber::field_to_destination( + destination => $d->{destination}, + number => $number, + domain => $domain, + uri => $d->{destination}, + ); + $d->{destination_set_id} = $dset_max_id; + push @new_destinations, $d; + } + } else { + &{$err_code}("Missing field 'destinationset' or 'destinationset_id' in '$type'."); + return; + } + unless ($dset) { + &{$err_code}("Invalid 'destinationset'. Could not be found."); + return; + } + + my $tset; my $has_tset; + if (defined $mapping->{timeset_id}) { + $tset = $tsets_rs->find({ + subscriber_id => $p_subs_id, + id => $mapping->{timeset_id}, + }); + $has_tset = 1; + } elsif (defined $mapping->{timeset} && !ref $mapping->{timeset}) { + $tset = $tsets_rs->find({ + subscriber_id => $p_subs_id, + name => $mapping->{timeset}, + }); + $has_tset = 1; + } elsif ($mapping->{timeset} && ref $mapping->{timeset} eq 'HASH') { + $mapping->{timeset}->{subscriber_id} = $p_subs_id; + return unless &$validate_time_set_code($mapping->{timeset}); + #$form = NGCP::Panel::Role::API::CFTimeSets->get_form($c); + #return unless $self->validate_form( + # c => $c, + # resource => $mapping->{timeset}, + # form => $form, + #); + if (! exists $mapping->{timeset}->{times} ) { + $mapping->{timeset}->{times} = []; + } + if (ref $mapping->{timeset}->{times} ne "ARRAY") { + &{$err_code}("Invalid field 'times'. Must be an array."); + return; + } + $tset_max_id +=2; + $tset = { + id => $tset_max_id, + name => $mapping->{timeset}->{name}, + subscriber_id => $p_subs_id, + }; + push @new_tsets, $tset; + for my $t ( @{$mapping->{timeset}->{times}} ) { + delete $t->{time_set_id}; + $t->{time_set_id} = $tset_max_id; + push @new_times, $t; + } + } + if($has_tset && !$tset) { + &{$err_code}("Invalid 'timeset'. Could not be found."); + return; + } + + my $sset; my $has_sset; + if (defined $mapping->{sourceset_id}) { + $sset = $ssets_rs->find({ + subscriber_id => $p_subs_id, + id => $mapping->{sourceset_id}, + }); + $has_sset = 1; + } elsif (defined $mapping->{sourceset} && !ref $mapping->{sourceset}) { + $sset = $ssets_rs->find({ + subscriber_id => $p_subs_id, + name => $mapping->{sourceset}, + }); + $has_sset = 1; + } elsif ($mapping->{sourceset} && ref $mapping->{sourceset} eq 'HASH') { + $mapping->{sourceset}->{subscriber_id} = $p_subs_id; + return unless &$validate_source_set_code($mapping->{sourceset}); + #$form = NGCP::Panel::Role::API::CFSourceSets->get_form($c); + #return unless $self->validate_form( + # c => $c, + # resource => $mapping->{sourceset}, + ## form => $form, + #); + if (! exists $mapping->{sourceset}->{sources} ) { + $mapping->{sourceset}->{sources} = []; + } + if (ref $mapping->{sourceset}->{sources} ne "ARRAY") { + &{$err_code}("Invalid field 'sources'. Must be an array."); + return; + } + + $sset_max_id +=2; + $sset = { + id => $sset_max_id, + name => $mapping->{sourceset}->{name}, + mode => $mapping->{sourceset}->{mode}, + is_regex => $mapping->{sourceset}->{is_regex} // 0, + subscriber_id => $p_subs_id, + }; + push @new_ssets, $sset; + for my $s ( @{$mapping->{sourceset}->{sources}} ) { + push @new_sources, { + source_set_id => $sset_max_id, + source => $s->{source}, + }; + } + } + if($has_sset && !$sset) { + &{$err_code}("Invalid 'sourceset'. Could not be found."); + return; + } + + my $bset; my $has_bset; + if (defined $mapping->{bnumberset_id}) { + $bset = $bsets_rs->find({ + subscriber_id => $p_subs_id, + id => $mapping->{bnumberset_id}, + }); + $has_bset = 1; + } elsif (defined $mapping->{bnumberset} && !ref $mapping->{bnumberset}) { + $bset = $bsets_rs->find({ + subscriber_id => $p_subs_id, + name => $mapping->{bnumberset}, + }); + $has_bset = 1; + } elsif ($mapping->{bnumberset} && ref $mapping->{bnumberset} eq 'HASH') { + $mapping->{bnumberset}->{subscriber_id} = $p_subs_id; + return unless &$validate_bnumber_set_code($mapping->{bnumberset}); + #$form = NGCP::Panel::Role::API::CFBNumberSets->get_form($c); + #return unless $self->validate_form( + # c => $c, + # resource => $mapping->{bnumberset}, + # form => $form, + #); + if (! exists $mapping->{bnumberset}->{bnumbers} ) { + $mapping->{bnumberset}->{bnumbers} = []; + } + if (ref $mapping->{bnumberset}->{bnumbers} ne "ARRAY") { + &{$err_code}("Invalid field 'bnumbers'. Must be an array."); + return; + } + + $bset_max_id +=2; + $bset = { + id => $bset_max_id, + name => $mapping->{bnumberset}->{name}, + mode => $mapping->{bnumberset}->{mode}, + is_regex => $mapping->{bnumberset}->{is_regex} // 0, + subscriber_id => $p_subs_id, + }; + push @new_bsets, $bset; + for my $b ( @{$mapping->{bnumberset}->{bnumbers}} ) { + push @new_bnumbers, { + bnumber_set_id => $bset_max_id, + bnumber => $b->{bnumber}, + }; + } + } + if($has_bset && !$bset) { + &{$err_code}("Invalid 'bnumberset'. Could not be found."); + return; + } + push @new_mappings, { + destination_set_id => ref $dset eq 'HASH' ? $dset->{id} : $dset->id, + time_set_id => ref $tset eq 'HASH' ? $tset->{id} : ( $tset ? $tset->id : undef ), + source_set_id => ref $sset eq 'HASH' ? $sset->{id} : ( $sset ? $sset->id : undef ), + bnumber_set_id => ref $bset eq 'HASH' ? $bset->{id} : ( $bset ? $bset->id : undef ), + type => $type, + enabled => defined $mapping->{enabled} ? $mapping->{enabled} : 1, + }; + } + } + + try { + my $autoattendant_count = 0; + $schema->resultset('voip_cf_destination_sets')->populate(\@new_dsets); + $schema->resultset('voip_cf_time_sets')->populate(\@new_tsets); + $schema->resultset('voip_cf_source_sets')->populate(\@new_ssets); + $schema->resultset('voip_cf_bnumber_sets')->populate(\@new_bsets); + $schema->resultset('voip_cf_destinations')->populate(\@new_destinations); + $schema->resultset('voip_cf_periods')->populate(\@new_times); + $schema->resultset('voip_cf_sources')->populate(\@new_sources); + $schema->resultset('voip_cf_bnumbers')->populate(\@new_bnumbers); + + unless ($params->{add_only}) { + foreach my $map($mappings_rs->all) { + $autoattendant_count += NGCP::Panel::Utils::Subscriber::check_dset_autoattendant_status($map->destination_set); + } + $mappings_rs->delete; + for my $type ( qw/cfu cfb cft cfna cfs cfr cfo/) { + $cf_preferences{$type}->delete; + } + } + + $mappings_rs->populate(\@new_mappings); + for my $type ( qw/cfu cfb cft cfna cfs cfr cfo/) { + my @mapping_ids_by_type = $mappings_rs->search( + { + type => $type + }, + { + select => [qw/me.id /], + as => [qw/value/], + result_class => 'DBIx::Class::ResultClass::HashRefInflator' + } + )->all(); + $cf_preferences{$type}->populate(\@mapping_ids_by_type); + } + + unless ($params->{add_only}) { + for my $mapping ($mappings_rs->all) { + $autoattendant_count -= NGCP::Panel::Utils::Subscriber::check_dset_autoattendant_status($mapping->destination_set); + } + } else { + for my $d (@new_destinations) { + $autoattendant_count -= (NGCP::Panel::Utils::Subscriber::destination_to_field($d->{destination}))[0] eq 'autoattendant' ? 1 : 0; + } + } + + if ($autoattendant_count > 0) { + while ($autoattendant_count != 0) { + $autoattendant_count--; + NGCP::Panel::Utils::Events::insert( + c => $c, schema => $schema, + subscriber_id => $item->id, + type => 'end_ivr', + ); + } + } elsif ($autoattendant_count < 0) { + while ($autoattendant_count != 0) { + $autoattendant_count++; + NGCP::Panel::Utils::Events::insert( + c => $c, schema => $schema, + subscriber_id => $item->id, + type => 'start_ivr', + ); + } + } + + if ($resource->{cft_ringtimeout} && $resource->{cft_ringtimeout} > 0) { + my $ringtimeout_preference = NGCP::Panel::Utils::Preferences::get_usr_preference_rs( + c => $c, attribute => 'ringtimeout', prov_subscriber => $item->provisioning_voip_subscriber); + + if($ringtimeout_preference->first) { + $ringtimeout_preference->first->update({ + value => $resource->{cft_ringtimeout}, + }); + } else { + $ringtimeout_preference->create({ + value => $resource->{cft_ringtimeout}, + }); + } + } elsif ($schema->resultset('voip_cf_mappings')->search_rs({ + subscriber_id => $item->provisioning_voip_subscriber->id, + type => 'cft', + enabled => 1, + })->count == 0) { + NGCP::Panel::Utils::Preferences::get_usr_preference_rs( + c => $c, + attribute => 'ringtimeout', + prov_subscriber => $item->provisioning_voip_subscriber)->delete; + } + + } catch($e) { + $c->log->error("failed to create cfmapping: $e"); + &{$err_code}("Failed to create cfmapping."); + return; + }; + + return $item; +} + +sub check_destinations { + + my %params = @_; + + my ($c, + $schema, + $resource, + $err_code) = @params{qw/ + c + schema + resource + err_code + /}; + + $schema //= $c->model('DB'); + + if (ref $resource->{destinations} ne "ARRAY") { + &{$err_code}("Invalid field 'destinations'. Must be an array."); + return; + } + for my $d (@{ $resource->{destinations} }) { + if (ref $d ne "HASH") { + &{$err_code}("Invalid element in array 'destinations'. Must be an object."); + return; + } + if (exists $d->{timeout} && ! is_int($d->{timeout})) { + $c->log->error("Invalid timeout for the destination '".$c->qs($d->{destination})."'"); + &{$err_code}("Invalid timeout for the destination '".$c->qs($d->{destination})."'"); + return; + } + if (exists $d->{priority} && ! is_int($d->{priority})) { + $c->log->error("Invalid priority for the destination '".$c->qs($d->{destination})."'"); + &{$err_code}("Invalid priority for the destination '".$c->qs($d->{destination})."'"); + return; + } + if (defined $d->{announcement_id}) { + #todo: I think that user expects that put and get will be the same + if(('customhours' ne $d->{destination}) && ('sip:custom-hours@app.local' ne $d->{destination}) ){ + $c->log->error("Invalid parameter 'announcement_id' for the destination '".$c->qs($d->{destination})."'"); + &{$err_code}("Invalid parameter 'announcement_id' for the destination '".$c->qs($d->{destination})."'"); + return; + }elsif(! is_int($d->{announcement_id})){ + $c->log->error("Invalid announcement_id"); + &{$err_code}("Invalid announcement_id"); + return; + }elsif(! $schema->resultset('voip_sound_handles')->search_rs({ + 'me.id' => $d->{announcement_id}, + 'group.name' => 'custom_announcements', + },{ + 'join' => 'group', + })->first() ){ + $c->log->error("Unknown announcement_id: ".$d->{announcement_id}); + &{$err_code}("Unknown announcement_id:".$d->{announcement_id}); + return; + } + } + } + return 1; +} + +1; diff --git a/lib/NGCP/Panel/Utils/ProvisioningTemplates.pm b/lib/NGCP/Panel/Utils/ProvisioningTemplates.pm index 5675a8be83..367a756e13 100644 --- a/lib/NGCP/Panel/Utils/ProvisioningTemplates.pm +++ b/lib/NGCP/Panel/Utils/ProvisioningTemplates.pm @@ -28,6 +28,7 @@ use NGCP::Panel::Utils::ProfilePackages qw(); use NGCP::Panel::Utils::Subscriber qw(); use NGCP::Panel::Utils::Preferences qw(); use NGCP::Panel::Utils::Kamailio qw(); +use NGCP::Panel::Utils::CallForwards qw(); use JE::Destroyer qw(); use JE qw(); @@ -644,6 +645,17 @@ sub provision_commit_row { $context, $schema, ); + _init_cf_mappings_context( + $c, + $context, + $schema, + $c->stash->{provisioning_templates}->{$template}, + ); + _create_cf_mappings( + $c, + $context, + $schema, + ); #die(); $guard->commit; @@ -703,6 +715,7 @@ sub _init_row_context { $context->{registrations} = []; $context->{trusted_sources} = []; + delete $context->{cf_mappings}; delete $context->{reseller}; delete $context->{billing_profile}; @@ -914,14 +927,15 @@ sub _init_subscriber_context { $context->{subscriber} = \%subscriber; + my $item; if (scalar @identifiers) { - my $e = $schema->resultset('voip_subscribers')->search_rs({ + $item = $schema->resultset('voip_subscribers')->search_rs({ map { $_ => $subscriber{$_}; } @identifiers },{ join => 'domain', })->first; - if ($e and 'terminated' ne $e->status) { - $subscriber{id} = $e->id; + if ($item and 'terminated' ne $item->status) { + $subscriber{id} = $item->id; } else { delete $subscriber{id}; } @@ -940,7 +954,8 @@ sub _init_subscriber_context { die($msg); }, validate_code => sub { - my ($r) = @_; + my ($res) = @_; + #todo return 1; }, getcustomer_code => sub { @@ -950,6 +965,7 @@ sub _init_subscriber_context { schema => $schema, contract_id => $contract->id) if $contract; return $contract; }, + item => $item, ); $c->log->debug("provisioning template - subscriber: " . Dumper($context->{subscriber})); @@ -1059,6 +1075,23 @@ sub _init_trusted_sources_context { } +sub _init_cf_mappings_context { + + my ($c, $context, $schema, $template) = @_; + + if (exists $template->{cf_mappings}) { + my %cf_mappings = (); + foreach my $col (keys %{$template->{cf_mappings}}) { + my ($k,$v) = _calculate($context,$col, $template->{cf_mappings}->{$col}); + $cf_mappings{$k} = $v; + } + + $context->{cf_mappings} = \%cf_mappings; + + $c->log->debug("provisioning template - cf mappings: " . Dumper($context->{cf_mappings})); + } + +} sub _create_contract_contact { @@ -1069,7 +1102,6 @@ sub _create_contract_contact { $context->{contract_contact}, ); $context->{contract_contact}->{id} = $contact->id; - #$context->{contract}->{contact_id} = $context->{contract_contact}->{id}; $c->log->debug("provisioning template - contract contact id $context->{contract_contact}->{id} created"); } @@ -1233,6 +1265,79 @@ sub _create_trusted_sources { } +sub _create_cf_mappings { + + my ($c, $context, $schema) = @_; + + if (exists $context->{cf_mappings}) { + my $subscriber = $schema->resultset('voip_subscribers')->find({ + id => $context->{subscriber}->{id}, + }); + + $subscriber = NGCP::Panel::Utils::CallForwards::update_cf_mappings( + c => $c, + resource => $context->{cf_mappings}, + item => $subscriber, + err_code => sub { + my ($msg) = @_; + die($msg); + }, + validate_mapping_code => sub { + my $res = shift; + #todo + return 1; + #return $self->validate_form( + # c => $c, + # form => $form, + # resource => $res, + #); + }, + validate_destination_set_code => sub { + my $res = shift; + #todo + return 1; + #return $self->validate_form( + # c => $c, + # form => NGCP::Panel::Role::API::CFDestinationSets->get_form($c), + # resource => $res, + #); + }, + validate_time_set_code => sub { + my $res = shift; + #todo + return 1; + #return $self->validate_form( + # c => $c, + # form => NGCP::Panel::Role::API::CFTimeSets->get_form($c), + # resource => $res, + #); + }, + validate_source_set_code => sub { + my $res = shift; + #todo + return 1; + #return $self->validate_form( + # c => $c, + # form => NGCP::Panel::Role::API::CFSourceSets->get_form($c), + # resource => $res, + #); + }, + validate_bnumber_set_code => sub { + my $res = shift; + #todo + return 1; + #return $self->validate_form( + # c => $c, + # form => NGCP::Panel::Role::API::CFBNumberSets->get_form($c), + # resource => $res, + #); + }, + params => undef, + ); + } + +} + sub _calculate_field { my ($context,$fname,$fields) = @_; diff --git a/tools_bin/ngcp-provisioning-template b/tools_bin/ngcp-provisioning-template index 85cb9672ce..03ae87ddc2 100755 --- a/tools_bin/ngcp-provisioning-template +++ b/tools_bin/ngcp-provisioning-template @@ -9,16 +9,17 @@ use Config::General qw(); use File::Slurp qw(); use XML::Simple qw(); -#use lib "/home/rkrenn/sipwise/git/ngcp-schema/lib"; -#use lib "/home/rkrenn/sipwise/git/sipwise-base/lib"; -#use lib "/usr/local/devel/ngcp-schema/lib"; -#use lib "/usr/local/devel/sipwise-base/lib"; +use lib "/home/rkrenn/sipwise/git/ngcp-schema/lib"; +use lib "/home/rkrenn/sipwise/git/sipwise-base/lib"; +use lib "/usr/local/devel/ngcp-schema/lib"; +use lib "/usr/local/devel/sipwise-base/lib"; use NGCP::Schema qw(); -#use lib "/home/rkrenn/sipwise/git/ngcp-panel/lib"; -#use lib "/usr/local/devel/ngcp-panel/lib"; +use lib "/home/rkrenn/sipwise/git/ngcp-panel/lib"; +use lib "/usr/local/devel/ngcp-panel/lib"; use NGCP::Panel::Utils::ProvisioningTemplates qw(); my @panel_configs = qw( + /home/rkrenn/sipwise/git/vagrant-ngcp/ngcp_panel.conf /etc/ngcp-panel/ngcp_panel.conf /etc/ngcp_panel.conf /ngcp_panel.conf @@ -27,13 +28,15 @@ my @panel_configs = qw( #/usr/local/devel/vagrant-ngcp/ngcp_panel.conf my @provisioning_configs = qw( - /etc/ngcp-panel/provisioning.conf + /home/rkrenn/sipwise/git/vagrant-ngcp/provisioning.conf + ); +#/etc/ngcp-panel/provisioning.conf #/home/rkrenn/sipwise/git/vagrant-ngcp/provisioning.conf #/usr/local/devel/vagrant-ngcp/provisioning.conf my %db = ( - host => undef, #'192.168.0.96', + host => '192.168.0.96', port => 3306, user => 'root', password => undef, @@ -310,7 +313,13 @@ sub _get_schema { if (-f $provisioning_config) { my $conf = XML::Simple->new->XMLin($provisioning_config, ForceArray => 1); if ($conf && $conf->{ngcp_connect_info}) { - my $connectors = $conf->{ngcp_connect_info}->[0]->{connectors} // []; + my $connectors; + if (exists $conf->{ngcp_connect_info}->[0]->{connectors}) { + $connectors = $conf->{ngcp_connect_info}->[0]->{connectors}; + } else { + $connectors = $conf->{ngcp_connect_info}; + } + $connectors //= []; $schema = NGCP::Schema->connect($connectors->[0]); $schema->set_wait_timeout(3600); return $schema;