TT#2371 Implement SMS forwarding

Change-Id: If3478f7e962aa514931268bc560caa3c1dde9fc1
changes/59/9859/3
Andreas Granig 9 years ago
parent 9d13bf3bb9
commit 776dd24f6f

2
debian/control vendored

@ -42,6 +42,7 @@ Depends: gettext,
libemail-sender-perl,
libemail-valid-perl,
libfcgi-procmanager-perl,
libfile-fnmatch-perl,
libfile-slurp-perl,
libfile-slurp-unicode-perl,
libfile-spec-perl (>= 3.4000~) | perl-base (>= 5.20.0-60~),
@ -89,6 +90,7 @@ Depends: gettext,
libtext-csv-xs-perl,
libtext-glob-perl,
libtext-table-perl,
libtime-period-perl,
libtime-warp-perl,
libtypes-path-tiny-perl,
liburi-encode-perl,

@ -20,7 +20,7 @@ sub allowed_methods{
}
sub api_description {
return 'Specifies callforward mappings of a subscriber, where multiple mappings can be specified per type (cfu, cfb, cft cfna) ' .
return 'Specifies callforward mappings of a subscriber, where multiple mappings can be specified per type (cfu, cfb, cft, cfna, cfs) ' .
'Each mapping consists of a destinationset name (see <a href="#cfdestinationsets">CFDestinationSets</a>), a timeset name ' .
'(see <a href="#cftimesets">CFTimeSets</a>) and a sourceset name (see <a href="#cfsourcesets">CFSourceSets</a>).';
}
@ -41,6 +41,7 @@ sub documentation_sample {
cft => [],
cft_ringtimeout => "200",
cfu => [],
cfs => [],
} ;
}

@ -23,7 +23,7 @@ sub allowed_methods{
sub api_description {
return 'Specifies basic callforwards of a subscriber, where a number of destinations, times and sources ' .
' can be specified for each type (cfu, cfb, cft cfna). For more complex configurations with ' .
' can be specified for each type (cfu, cfb, cft, cfna, cfs). For more complex configurations with ' .
' multiple combinations of Timesets, Destinationsets and SourceSets see <a href="#cfmappings">CFMappings</a>.';
};
@ -45,6 +45,7 @@ sub documentation_sample {
cfna => {},
cft => { "ringtimeout" => "199" },
cfu => {},
cfs => {},
};
}

@ -127,7 +127,7 @@ sub query_params {
},
{
param => 'type',
description => 'Filter for calls with a specific type. One of "call", "cfu", "cfb", "cft", "cfna".',
description => 'Filter for calls with a specific type. One of "call", "cfu", "cfb", "cft", "cfna", "cfs".',
query => {
first => sub {
my $q = shift;
@ -140,7 +140,7 @@ sub query_params {
},
{
param => 'type_ne',
description => 'Filter for calls not having a specific type. One of "call", "cfu", "cfb", "cft", "cfna".',
description => 'Filter for calls not having a specific type. One of "call", "cfu", "cfb", "cft", "cfna", "cfs".',
query => {
first => sub {
my $q = shift;

@ -82,11 +82,11 @@ sub query_params {
},
{
param => 'direction',
description => 'Filter for messages sent ("out") or received ("in").',
description => 'Filter for messages sent ("out"), received ("in") or forwarded ("forward").',
query => {
first => sub {
my $q = shift;
if ($q eq "out" || $q eq "in") {
if ($q eq "out" || $q eq "in" || $q eq "forward") {
return { "me.direction" => $q };
} else {
return {},

@ -223,7 +223,7 @@ sub POST :Allow {
expose_to_customer => 1,
},
{
attribute => { -in => [qw/cfu cft cfna cfb/] },
attribute => { -in => [qw/cfu cft cfna cfb cfs/] },
},
],
});

@ -2,8 +2,9 @@ package NGCP::Panel::Controller::InternalSms;
use NGCP::Panel::Utils::Generic qw(:all);
use Sipwise::Base;
use parent 'Catalyst::Controller';
use Time::Period;
use File::FnMatch qw(:fnmatch);
#sub auto :Does(ACL) :ACLDetachTo('/denied_page') :AllowedRole(admin) {
sub auto {
@ -38,6 +39,7 @@ sub receive :Chained('list') :PathPart('receive') :Args(0) {
$to =~ s/^\+//;
$from =~ s/^\+//;
my $now = time;
try {
my $schema = $c->model('DB');
@ -62,6 +64,101 @@ sub receive :Chained('list') :PathPart('receive') :Args(0) {
callee => $to,
text => $text,
});
# check for cfs
my $cf_maps = $c->model('DB')->resultset('voip_cf_mappings')->search({
subscriber_id => $prov_dbalias->subscriber_id,
type => 'cfs'
});
unless($cf_maps->count) {
$c->log->info("No cfs for inbound sms from $from to $to found.");
last;
}
my $dset;
foreach my $map($cf_maps->all) {
# check source set
my $source_pass = 1;
if($map->source_set) {
$source_pass = 0;
foreach my $source($map->source_set->voip_cf_sources->all) {
$c->log->info(">>>> checking $from against ".$source->source);
if(fnmatch($source->source, $from)) {
$c->log->info(">>>> matched $from against ".$source->source.", pass");
$source_pass = 1;
last;
}
}
}
if($source_pass) {
$c->log->info(">>>> source check for $from passed, continue with time check");
} else {
$c->log->info(">>>> source check for $from failed, trying next map entry");
next;
}
my $time_pass = 1;
if($map->time_set) {
$time_pass = 0;
foreach my $time($map->time_set->voip_cf_periods->all) {
my $timestring = join(' ',
$time->year // '',
$time->month // '',
$time->mday // '',
$time->wday // '',
$time->hour // '',
$time->minute // ''
);
$c->log->info(">>>> checking $now against ".$timestring);
if(inPeriod($now, $timestring)) {
$c->log->info(">>>> matched $now against ".$timestring.", pass");
$time_pass = 1;
last;
}
}
}
if($time_pass) {
$c->log->info(">>>> time check for $now passed, use destination set");
$dset = $map->destination_set;
last;
} else {
$c->log->info(">>>> time check for $now failed, trying next map entry");
next;
}
}
unless($dset) {
$c->log->info(">>>> checks failed, bailing out without forwarding");
last;
}
$c->log->info(">>>> proceed sms forwarding");
unless($dset->voip_cf_destinations->first) {
$c->log->info(">>>> detected cf mapping has no destinations in destination set");
last;
}
my $dst = $dset->voip_cf_destinations->first->destination;
$dst =~ s/^sip:(.+)\@.+$/$1/;
$c->log->info(">>>> forward sms to $dst");
# feed back into kannel
my $error_msg;
NGCP::Panel::Utils::SMS::send_sms(
c => $c,
caller => $to, # use the original to as new from
callee => $dst,
text => $text,
err_code => sub {$error_msg = shift;},
);
my $fwd_item = $c->model('DB')->resultset('sms_journal')->create({
subscriber_id => $prov_dbalias->subscriber_id,
direction => "forward",
caller => $to,
callee => $dst,
text => $text,
});
});
} catch($e) {
$c->log->error("Failed to store received SMS message.");

@ -624,7 +624,7 @@ sub preferences :Chained('base') :PathPart('preferences') :Args(0) {
my $prov_subscriber = $c->stash->{subscriber}->provisioning_voip_subscriber;
my $cfs = {};
foreach my $type(qw/cfu cfna cft cfb/) {
foreach my $type(qw/cfu cfna cft cfb cfs/) {
my $maps = $prov_subscriber->voip_cf_mappings
->search({ type => $type });
$cfs->{$type} = [];
@ -695,7 +695,7 @@ sub preferences :Chained('base') :PathPart('preferences') :Args(0) {
$c->stash->{pref_groups} = \@newprefgroups;
my $special_prefs = { check => 1 };
foreach my $pref(qw/cfu cft cfna cfb
foreach my $pref(qw/cfu cft cfna cfb cfs
speed_dial reminder auto_attendant
voice_mail fax_server/) {
my $preference = $c->model('DB')->resultset('voip_preferences')->find({
@ -839,6 +839,10 @@ sub preferences_callforward :Chained('base') :PathPart('preferences/callforward'
$cf_desc = $c->loc('Call Forward Unavailable');
last SWITCH;
};
/^cfs$/ && do {
$cf_desc = $c->loc('Call Forward SMS');
last SWITCH;
};
# default
NGCP::Panel::Utils::Message::error(
c => $c,
@ -1096,6 +1100,10 @@ sub preferences_callforward_advanced :Chained('base') :PathPart('preferences/cal
$cf_desc = $c->loc('Call Forward Unavailable');
last SWITCH;
};
/^cfs$/ && do {
$cf_desc = $c->loc('Call Forward SMS');
last SWITCH;
};
# default
NGCP::Panel::Utils::Message::error(
c => $c,
@ -2620,7 +2628,7 @@ sub edit_master :Chained('master') :PathPart('edit') :Args(0) :Does(ACL) :ACLDet
if(keys %old_profile_attributes) {
my $cfs = $c->model('DB')->resultset('voip_preferences')->search({
id => { -in => [ keys %old_profile_attributes ] },
attribute => { -in => [qw/cfu cfb cft cfna/] },
attribute => { -in => [qw/cfu cfb cft cfna cfs/] },
});
$prov_subscriber->voip_usr_preferences->search({
attribute_id => { -in => [ keys %old_profile_attributes ] },

@ -517,7 +517,7 @@ sub profile_edit :Chained('profile_base') :PathPart('edit') :Does(ACL) :ACLDetac
if(keys %old_attributes) {
my $cfs = $c->model('DB')->resultset('voip_preferences')->search({
id => { -in => [ keys %old_attributes ] },
attribute => { -in => [qw/cfu cfb cft cfna/] },
attribute => { -in => [qw/cfu cfb cft cfna cfs/] },
});
my @subs = $c->model('DB')->resultset('provisioning_voip_subscribers')
->search({

@ -64,6 +64,19 @@ has_field 'cfna' => (
},
);
has_field 'cfs' => (
type => 'Repeatable',
do_wrapper => 1,
do_label => 0,
required => 0,
element_attr => {
rel => ['tooltip'],
title => ['Call Forward SMS, Number of Objects, each containing the keys ' .
'"destinationset", "timeset" and "sourceset". The values must be the name of ' .
'a corresponding set which belongs to the same subscriber.'],
},
);
has_field 'cfu.destinationset' => (
type => 'Text',
do_wrapper => 1,
@ -136,6 +149,24 @@ has_field 'cfna.sourceset' => (
do_label => 0,
);
has_field 'cfs.destinationset' => (
type => 'Text',
do_wrapper => 1,
do_label => 0,
);
has_field 'cfs.timeset' => (
type => 'Text',
do_wrapper => 1,
do_label => 0,
);
has_field 'cfs.sourceset' => (
type => 'Text',
do_wrapper => 1,
do_label => 0,
);
has_field 'cft_ringtimeout' => (
type => 'PosInteger',
do_wrapper => 1,
@ -145,7 +176,7 @@ has_field 'cft_ringtimeout' => (
has_block 'fields' => (
tag => 'div',
class => [qw(modal-body)],
render_list => [qw(cfu cfb cft cfna)],
render_list => [qw(cfu cfb cft cfna cfs)],
);
1;

@ -69,6 +69,20 @@ has_field 'cfna' => (
},
);
has_field 'cfs' => (
type => 'Compound',
do_wrapper => 1,
do_label => 0,
required => 0,
element_attr => {
rel => ['tooltip'],
title => ['Call Forward SMS, Contains the keys "destinations", "times" and "sources". "destinations" is an Array of Objects ' .
'having a "destination", "priority" and "timeout" field. "times" is an Array of Objects having the fields ' .
'"minute", "hour", "wday", "mday", "month", "year". "times" can be empty, then the CF is applied always. ' .
'"sources" is an Array of Objects having one field "source". "sources" can be empty.'],
},
);
has_field 'cfu.destinations' => (
type => 'Repeatable',
do_wrapper => 1,
@ -189,6 +203,36 @@ has_field 'cfna.sources.source' => (
type => 'Text',
);
has_field 'cfs.destinations' => (
type => 'Repeatable',
do_wrapper => 1,
do_label => 0,
);
has_field 'cfs.destinations.destination' => (
type => 'Text',
);
has_field 'cfs.destinations.timeout' => (
type => 'PosInteger',
);
has_field 'cfs.times' => (
type => 'Repeatable',
do_wrapper => 1,
do_label => 0,
);
has_field 'cfs.sources' => (
type => 'Repeatable',
do_wrapper => 1,
do_label => 0,
);
has_field 'cfs.sources.source' => (
type => 'Text',
);
has_field 'cft.ringtimeout' => (
type => 'PosInteger',
do_wrapper => 1,
@ -198,7 +242,7 @@ has_field 'cft.ringtimeout' => (
has_block 'fields' => (
tag => 'div',
class => [qw(modal-body)],
render_list => [qw(cfu cfb cft cfna)],
render_list => [qw(cfu cfb cft cfna cfs)],
);
1;

@ -400,10 +400,11 @@ has_field 'call_type' => (
{ label => 'cfb', value => 'cfb' },
{ label => 'cft', value => 'cft' },
{ label => 'cfna', value => 'cfna' },
{ label => 'cfs', value => 'cfs' },
],
element_attr => {
rel => ['tooltip'],
title => ['The type of call, one of call, cfu, cfb, cft, cfna.']
title => ['The type of call, one of call, cfu, cfb, cft, cfna, cfs.']
},
);

@ -80,10 +80,11 @@ has_field 'type' => (
{ label => 'cfb', value => 'cfb' },
{ label => 'cft', value => 'cft' },
{ label => 'cfna', value => 'cfna' },
{ label => 'cfs', value => 'cfs' },
],
element_attr => {
rel => ['tooltip'],
title => ['The type of call, one of call, cfu, cfb, cft, cfna.']
title => ['The type of call, one of call, cfu, cfb, cft, cfna, cfs.']
},
);

@ -33,12 +33,13 @@ has_field 'direction' => (
options => [
{ value => 'in', label => 'inbound' },
{ value => 'out', label => 'outbound' },
{ value => 'forward', label => 'forwarded' },
],
default => 'out', # FYI, default is not considered with API
required => 0, # should be "1" actually, see above
element_attr => {
rel => ['tooltip'],
title => ['Whether the logged message is sent or received'],
title => ['Whether the logged message is sent, received or forwarded'],
},
);

@ -76,7 +76,7 @@ sub field_list {
expose_to_customer => 1,
},
{
attribute => { -in => [qw/cfu cft cfna cfb/] },
attribute => { -in => [qw/cfu cft cfna cfb cfs/] },
}
],
});

@ -24,7 +24,7 @@ sub hal_from_item {
my ($self, $c, $item, $type) = @_;
my $form;
my $resource = { subscriber_id => $item->id, cfu => [], cfb => [], cfna => [], cft => [], };
my $resource = { subscriber_id => $item->id, cfu => [], cfb => [], cfna => [], cft => [], cfs => []};
my $b_subs_id = $item->id;
my $p_subs_id = $item->provisioning_voip_subscriber->id;
@ -125,7 +125,7 @@ sub update_item {
my $tsets_rs = $c->model('DB')->resultset('voip_cf_time_sets');
my $ssets_rs = $c->model('DB')->resultset('voip_cf_source_sets');
for my $type ( qw/cfu cfb cft cfna/) {
for my $type ( qw/cfu cfb cft cfna cfs/) {
if (ref $resource->{$type} ne "ARRAY") {
$self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Invalid field '$type'. Must be an array.");
return;
@ -174,7 +174,7 @@ sub update_item {
$autoattendant_count += NGCP::Panel::Utils::Subscriber::check_dset_autoattendant_status($map->destination_set);
}
$mappings_rs->delete;
for my $type ( qw/cfu cfb cft cfna/) {
for my $type ( qw/cfu cfb cft cfna cfs/) {
$cf_preferences{$type}->delete;
}
for my $mapping ( @new_mappings ) {

@ -46,7 +46,7 @@ sub hal_from_item {
],
relation => 'ngcp:'.$self->resource_name,
);
@resource{qw/cfu cfb cft cfna/} = ({}) x 4;
@resource{qw/cfu cfb cft cfna cfs/} = ({}) x 5;
for my $item_cf ($item->provisioning_voip_subscriber->voip_cf_mappings->all) {
$resource{$item_cf->type} = $self->_contents_from_cfm($c, $item_cf, $item);
}
@ -111,7 +111,7 @@ sub update_item {
run => 1,
);
for my $type (qw/cfu cfb cft cfna/) {
for my $type (qw/cfu cfb cft cfna cfs/) {
next unless "ARRAY" eq ref $resource->{$type}{destinations};
for my $d (@{ $resource->{$type}{destinations} }) {
if (exists $d->{timeout} && ! is_int($d->{timeout})) {
@ -122,7 +122,7 @@ sub update_item {
}
}
for my $type (qw/cfu cfb cft cfna/) {
for my $type (qw/cfu cfb cft cfna cfs/) {
my $mapping = $c->model('DB')->resultset('voip_cf_mappings')->search_rs({
subscriber_id => $prov_subscriber_id,
type => $type,

@ -157,7 +157,7 @@ sub update_item {
expose_to_customer => 1,
},
{
attribute => { -in => [qw/cfu cft cfna cfb/] },
attribute => { -in => [qw/cfu cft cfna cfb cfs/] },
},
],
});
@ -175,7 +175,7 @@ sub update_item {
if(keys %old_attributes) {
my $cfs = $c->model('DB')->resultset('voip_preferences')->search({
id => { -in => [ keys %old_attributes ] },
attribute => { -in => [qw/cfu cfb cft cfna/] },
attribute => { -in => [qw/cfu cfb cft cfna cfs/] },
});
my @subs = $c->model('DB')->resultset('provisioning_voip_subscribers')
->search({

@ -582,7 +582,7 @@ sub update_item {
if(keys %old_profile_attributes) {
my $cfs = $schema->resultset('voip_preferences')->search({
id => { -in => [ keys %old_profile_attributes ] },
attribute => { -in => [qw/cfu cfb cft cfna/] },
attribute => { -in => [qw/cfu cfb cft cfna cfs/] },
});
$prov_subscriber->voip_usr_preferences->search({
attribute_id => { -in => [ keys %old_profile_attributes ] },

@ -299,7 +299,7 @@ sub get_dummy_data {
start_time => time,
source_customer_cost => int(rand(100000)),
duration => int(rand(7200)) + 10,
call_type => (qw/cfu cfb cft cfna/)[int(rand 4)],
call_type => (qw/cfu cfb cft cfna cfs/)[int(rand 4)],
zone => "Zone $_",
zone_detail => "Detail $_",
}}(1 .. 50)

@ -21,7 +21,8 @@ sub _get_cf_type_descriptions {
return { cfu => $c->loc("Call Forward Unconditional"),
cfb => $c->loc("Call Forward Busy"),
cft => $c->loc("Call Forward Timeout"),
cfna => $c->loc("Call Forward Unavailable") };
cfna => $c->loc("Call Forward Unavailable"),
cfs => $c->loc("Call Forward SMS"), };
}
sub cfs {
@ -31,7 +32,7 @@ sub cfs {
my $cfs = {};
my $descriptions = $self->_get_cf_type_descriptions($c);
foreach my $type (qw/cfu cfna cft cfb/) {
foreach my $type (qw/cfu cfna cft cfb cfs/) {
my $maps = $prov_subscriber->voip_cf_mappings
->search({ type => $type });
my @mappings = ();

@ -43,7 +43,7 @@ for my $item ($item_rs->all) {
# print Dumper({$item->get_inflated_columns});
my %resource = ();
my $prov_subs = $item->provisioning_voip_subscriber;
for my $cf_type (qw/cfu cfb cft cfna/) {
for my $cf_type (qw/cfu cfb cft cfna cfs/) {
my $mapping = $schema->resultset('voip_cf_mappings')->search({
subscriber_id => $prov_subs->id,
type => $cf_type,
@ -73,7 +73,7 @@ $time = time();
for my $item ($item_rs->all) {
my %resource = ();
my $prov_subs = $item->provisioning_voip_subscriber;
@resource{qw/cfu cfb cft cfna/} = ({}) x 4;
@resource{qw/cfu cfb cft cfna cfs/} = ({}) x 5;
for my $item_cf ($item->provisioning_voip_subscriber->voip_cf_mappings->all){
$resource{$item_cf->type} = _contents_from_cfm($item_cf, $item);
}
@ -247,4 +247,4 @@ sub validate_fields {
return 1;
}
1;
1;

@ -77,7 +77,8 @@
[% FOR cf IN [ { type = "cfu", desc = c.loc("Call Forward Unconditional") },
{ type = "cfb", desc = c.loc("Call Forward Busy") },
{ type = "cft", desc = c.loc("Call Forward Timeout") },
{ type = "cfna", desc = c.loc("Call Forward Unavailable") } ] -%]
{ type = "cfna", desc = c.loc("Call Forward Unavailable") },
{ type = "cfs", desc = c.loc("Call Forward SMS") } ] -%]
[% IF c.user.roles == "subscriber" || c.user.roles == "subscriberadmin" -%]
[% NEXT IF special_prefs.check && !special_prefs.callforward.${cf.type} -%]
[% END -%]

@ -89,6 +89,7 @@ SKIP:{
ok(exists $cf1single->{cfb}, "cf should have key cfb");
ok(exists $cf1single->{cft}, "cf should have key cft");
ok(exists $cf1single->{cfna}, "cf should have key cfna");
ok(exists $cf1single->{cfs}, "cf should have key cfs");
#write cf and check written values
my($cf1_put,$cf1_get) = $test_machine->check_put2get({data_in => $test_machine->DATA_ITEM, uri => $cf1single_uri},undef, 1 );

@ -891,6 +891,8 @@ sub test_callforwards {
times => $cftimeset->{times}},
cfu => { destinations => $cfdestinationset->{destinations},
times => $cftimeset->{times}},
cfs => { destinations => $cfdestinationset->{destinations},
times => $cftimeset->{times}},
}));
$res = $ua->request($req);
is($res->code, 200, _get_request_test_message("PUT test callforwards"));
@ -952,6 +954,8 @@ sub test_cfmapping {
timeset => $cftimeset->{name}}],
cfu => [{ destinationset => $cfdestinationset->{name},
timeset => $cftimeset->{name}}],
cfs => [{ destinationset => $cfdestinationset->{name},
timeset => $cftimeset->{name}}],
}));
$res = $ua->request($req);
is($res->code, 200, _get_request_test_message("PUT test cfmappings"));

Loading…
Cancel
Save