TT#41553 TT#41554 billing fee match_mode

Change-Id: I77e2cccdcb320e55642f3166bc49ca4f491a8eea
changes/58/23058/6
Rene Krenn 7 years ago
parent baa7329bfb
commit 2b7a1a33a8

@ -189,7 +189,7 @@ sub POST :Allow {
$self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Invalid 'billing_zone_id'.");
last;
}
$resource->{match_mode} = 'regex_longest_pattern' unless $resource->{match_mode};
last unless $self->validate_form(
c => $c,
resource => $resource,

@ -333,11 +333,20 @@ sub terminate :Chained('base') :PathPart('terminate') :Args(0) {
sub fees_list :Chained('base') :PathPart('fees') :CaptureArgs(0) {
my ($self, $c) = @_;
$c->stash->{fee_dt_columns} = NGCP::Panel::Utils::Datatables::set_columns($c, [
{ name => 'id', search => 1, title => $c->loc('#') },
{ name => 'source', search => 1, title => $c->loc('Source Pattern') },
{ name => 'destination', search => 1, title => $c->loc('Destination Pattern') },
{ name => 'direction', search => 1, title => $c->loc('Match Direction') },
{ name => 'match_mode', search => 0, title => $c->loc('Match Mode'),
custom_renderer => 'function ( data, type, full ) {'.
'if(full.match_mode == "regex_longest_pattern"){return "' . $c->loc('Regular expression - longest pattern') . '";}'.
'else if(full.match_mode == "regex_longest_match"){return "' . $c->loc('Regular expression - longest match') . '";}'.
'else if(full.match_mode == "prefix"){return "' . $c->loc('Prefix string') . '";}'.
'else if(full.match_mode == "exact_destination"){return "' . $c->loc('Exact string (destination)') . '";}'.
'}',
},
{ name => 'direction', search => 1, title => $c->loc('Direction') },
{ name => 'billing_zone.detail', search => 1, title => $c->loc('Billing Zone') },
]);
$c->stash(template => 'billing/fees.tt');
@ -410,6 +419,7 @@ sub fees_create :Chained('fees_list') :PathPart('create') :Args(0) {
);
if($form->validated) {
$form->values->{source} ||= '.';
$form->values->{match_mode} ||= 'regex_longest_pattern';
my $schema = $c->model('DB');
$schema->txn_do(sub {
NGCP::Panel::Utils::Billing::insert_unique_billing_fees(
@ -529,6 +539,7 @@ sub fees_edit :Chained('fees_base') :PathPart('edit') :Args(0) {
);
if($posted && $form->validated) {
$form->values->{source} ||= '.';
$form->values->{match_mode} ||= 'regex_longest_pattern';
$form->values->{billing_zone_id} = $form->values->{billing_zone}{id};
delete $form->values->{billing_zone};
$c->stash->{'fee_result'}

@ -28,7 +28,7 @@ sub auto :Does(ACL) :ACLDetachTo('/denied_page') :AllowedRole(admin) :AllowedRol
sub voucher_list :Chained('/') :PathPart('voucher') :CaptureArgs(0) {
my ( $self, $c ) = @_;
my $voucher_rs = $c->model('DB')->resultset('vouchers'); #->search_rs(undef, {
#'join' => { 'customer' => 'contact'},
#'+select' => [
@ -73,25 +73,25 @@ sub _process_dt_voucher_rows :Private {
sub ajax :Chained('voucher_list') :PathPart('ajax') :Args(0) {
my ($self, $c) = @_;
my $resultset = $c->stash->{voucher_rs};
NGCP::Panel::Utils::Datatables::process($c, $resultset, $c->stash->{voucher_dt_columns}, sub {
my $row = shift;
return _process_dt_voucher_rows($c,$row);
});
$c->detach( $c->view("JSON") );
}
sub ajax_package_filter :Chained('voucher_list') :PathPart('ajax/package') :Args(1) {
my ($self, $c, $package_id) = @_;
my $resultset = $c->stash->{voucher_rs}->search_rs({ package_id => $package_id },undef);
NGCP::Panel::Utils::Datatables::process($c, $resultset, $c->stash->{voucher_dt_columns}, sub {
my $row = shift;
return _process_dt_voucher_rows($c,$row);
});
$c->detach( $c->view("JSON") );
}
@ -188,7 +188,7 @@ sub edit :Chained('base') :PathPart('edit') {
if($posted && $form->validated) {
try {
if($c->user->is_superuser) {
$form->values->{reseller_id} = $form->values->{reseller}{id};
$form->values->{reseller_id} = $form->values->{reseller}{id};
} else {
$form->values->{reseller_id} = $c->user->reseller_id;
}
@ -267,7 +267,7 @@ sub create :Chained('voucher_list') :PathPart('create') :Args(0) {
if($posted && $form->validated) {
try {
if($c->user->is_superuser) {
$form->values->{reseller_id} = $form->values->{reseller}{id};
$form->values->{reseller_id} = $form->values->{reseller}{id};
} else {
$form->values->{reseller_id} = $c->user->reseller_id;
}
@ -275,7 +275,7 @@ sub create :Chained('voucher_list') :PathPart('create') :Args(0) {
$form->values->{customer_id} = $form->values->{customer}{id};
delete $form->values->{customer};
$form->values->{package_id} = $form->values->{package}{id};
delete $form->values->{package};
delete $form->values->{package};
$form->values->{created_at} = NGCP::Panel::Utils::DateTime::current_local;
if($form->values->{valid_until} =~ /^\d{4}\-\d{2}\-\d{2}$/) {
$form->values->{valid_until} = NGCP::Panel::Utils::DateTime::from_string($form->values->{valid_until})
@ -286,7 +286,7 @@ sub create :Chained('voucher_list') :PathPart('create') :Args(0) {
$c->session->{created_objects}->{voucher} = { id => $voucher->id };
delete $c->session->{created_objects}->{reseller};
delete $c->session->{created_objects}->{customer};
delete $c->session->{created_objects}->{profile_package};
delete $c->session->{created_objects}->{profile_package};
NGCP::Panel::Utils::Message::info(
c => $c,
desc => $c->loc('Billing voucher successfully created'),
@ -310,12 +310,12 @@ sub voucher_upload :Chained('voucher_list') :PathPart('upload') :Args(0) {
$c->detach('/denied_page')
unless($c->user->billing_data);
my $form = NGCP::Panel::Form::get("NGCP::Panel::Form::Voucher::Upload", $c);
my $upload = $c->req->upload('upload_vouchers');
my $posted = $c->req->method eq 'POST';
my @params = (
upload_fees => $posted ? $upload : undef,
upload_vouchers => $posted ? $upload : undef,
);
$form->process(
posted => $posted,

@ -5,28 +5,45 @@ extends 'HTML::FormHandler';
use HTML::FormHandler::Widget::Block::Bootstrap;
use NGCP::Panel::Field::BillingZone;
use NGCP::Panel::Utils::Billing qw();
has '+widget_wrapper' => ( default => 'Bootstrap' );
has_field 'submitid' => ( type => 'Hidden' );
sub build_render_list {[qw/submitid fields actions/]}
sub build_form_element_class { [qw/form-horizontal/] }
has_field 'match_mode' => (
type => 'Select',
options => [
{ value => 'regex_longest_pattern', label => 'Regular expression - longest pattern' },
{ value => 'regex_longest_match', label => 'Regular expression - longest match' },
{ value => 'prefix', label => 'Prefix string' },
{ value => 'exact_destination', label => 'Exact string (destination)' },
],
default => 'regex_longest_pattern',
required => 1,
element_attr => {
rel => ['tooltip'],
title => ['The mode how the the fee\'s source/destination has to match a call\'s source/destination.']
},
);
has_field 'source' => (
type => '+NGCP::Panel::Field::Regexp',
type => 'Text',
maxlength => 255,
element_attr => {
rel => ['tooltip'],
title => ['A PCRE regular expression to match the calling number (e.g. ^.+$).']
title => ['A string (eg. 431001), string prefix (eg. 43) or PCRE regular expression (eg. ^.+$) to match the calling number or sip uri.']
},
);
has_field 'destination' => (
type => '+NGCP::Panel::Field::Regexp',
type => 'Text',
maxlength => 255,
required => 1,
element_attr => {
rel => ['tooltip'],
title => ['A PCRE regular expression to match the called number (e.g. ^431.+$).']
title => ['A string (eg. 431001), string prefix (eg. 43) or PCRE regular expression (eg. ^.+$) to match the called number or sip uri.']
},
);
@ -60,7 +77,7 @@ has_field 'onpeak_init_rate' => (
precision => 14,
element_attr => {
rel => ['tooltip'],
title => ['The cost of the first interval in cents per second (e.g. 0.90).']
title => ['The cost per second of the first interval during onpeak hours (e.g. 0.90 cent).']
},
default => 0,
);
@ -69,11 +86,10 @@ has_field 'onpeak_init_interval' => (
type => 'Integer',
element_attr => {
rel => ['tooltip'],
title => ['The length of the first interval in seconds (e.g. 60).']
title => ['The length of the first interval during onpeak hours in seconds (e.g. 60).']
},
default => 60,
required => 1,
validate_method => \&validate_interval,
);
has_field 'onpeak_follow_rate' => (
@ -82,7 +98,7 @@ has_field 'onpeak_follow_rate' => (
precision => 14,
element_attr => {
rel => ['tooltip'],
title => ['The cost of each following interval in cents per second (e.g. 0.90).']
title => ['The cost per second of each following interval during onpeak hours in cents (e.g. 0.90 cents).']
},
default => 0,
);
@ -91,11 +107,10 @@ has_field 'onpeak_follow_interval' => (
type => 'Integer',
element_attr => {
rel => ['tooltip'],
title => ['The length of each following interval in seconds (e.g. 30).']
title => ['The length of each following interval during onpeak hours in seconds (e.g. 30).']
},
default => 60,
required => 1,
validate_method => \&validate_interval,
);
has_field 'offpeak_init_rate' => (
@ -104,7 +119,7 @@ has_field 'offpeak_init_rate' => (
precision => 14,
element_attr => {
rel => ['tooltip'],
title => ['The cost of the first interval in cents per second (e.g. 0.90).']
title => ['The cost per second of the first interval during offpeak hours in cents (e.g. 0.70 cents).']
},
default => 0,
);
@ -113,11 +128,10 @@ has_field 'offpeak_init_interval' => (
type => 'Integer',
element_attr => {
rel => ['tooltip'],
title => ['The length of the first interval in seconds (e.g. 60).']
title => ['The length of the first interval during offpeak hours in seconds (e.g. 60).']
},
default => 60,
required => 1,
validate_method => \&validate_interval,
);
has_field 'offpeak_follow_rate' => (
@ -126,7 +140,7 @@ has_field 'offpeak_follow_rate' => (
precision => 14,
element_attr => {
rel => ['tooltip'],
title => ['The cost of each following interval in cents per second (e.g. 0.90).']
title => ['The cost per second of each following interval during offpeak hours in cents (e.g. 0.70 cents).']
},
default => 0,
);
@ -135,11 +149,10 @@ has_field 'offpeak_follow_interval' => (
type => 'Integer',
element_attr => {
rel => ['tooltip'],
title => ['The length of each following interval in seconds (e.g. 30).']
title => ['The length of each following interval during offpeak hours in seconds (e.g. 30).']
},
default => 60,
required => 1,
validate_method => \&validate_interval,
);
has_field 'use_free_time' => (
@ -161,7 +174,7 @@ has_field 'save' => (
has_block 'fields' => (
tag => 'div',
class => [qw/modal-body/],
render_list => [qw/billing_zone source destination direction
render_list => [qw/billing_zone match_mode source destination direction
onpeak_init_rate onpeak_init_interval onpeak_follow_rate
onpeak_follow_interval offpeak_init_rate offpeak_init_interval
offpeak_follow_rate offpeak_follow_interval use_free_time
@ -174,12 +187,25 @@ has_block 'actions' => (
render_list => [qw/save/],
);
sub validate_interval {
my ($self, $field) = @_;
sub validate {
my ($self) = @_;
my $c = $self->ctx;
return unless $c;
NGCP::Panel::Utils::Billing::validate_billing_fee(
$self->values,
sub {
my ($field,$error,$error_detail) = @_;
$self->field($field)->add_error($self->field($field)->label . ' ' . $error);
return 1;
},
sub {
my ($field) = @_;
return $self->field($field)->value;
},
)
if(int($field->value) < 1) {
$field->add_error("Invalid interval, must be bigger than 0");
}
}
1;

@ -25,7 +25,7 @@ has_field 'purge_existing' => (
has_block 'fields' => (
tag => 'div',
class => [qw/modal-body/],
render_list => [qw/purge_existing billing_zone billing_profile_id source destination direction
render_list => [qw/purge_existing billing_zone billing_profile_id match_mode source destination direction
onpeak_init_rate onpeak_init_interval onpeak_follow_rate
onpeak_follow_interval offpeak_init_rate offpeak_init_interval
offpeak_follow_rate offpeak_follow_interval use_free_time

@ -96,7 +96,7 @@ sub update_fee {
}
$form //= $self->get_form($c);
$resource->{match_mode} = 'regex_longest_pattern' unless $resource->{match_mode};
return unless $self->validate_form(
c => $c,
form => $form,
@ -136,7 +136,7 @@ sub get_billing_zone{
my($self,$c,$profile,$resource) = @_;
if( (!defined $profile) && defined $resource->{billing_profile_id} ){
$profile = $c->model('DB')->resultset('billing_profiles')->find($resource->{billing_profile_id});
$profile = $c->model('DB')->resultset('billing_profiles')->find($resource->{billing_profile_id});
}
if(!defined $profile){
$c->log->debug("in get_billing_zone: no profile;");
@ -144,7 +144,7 @@ sub get_billing_zone{
}
my $zone;
# in case of implicit zone declaration (name/detail instead of id),
# find or create the zone
if( (!defined $resource->{billing_zone_id}) &&

@ -188,6 +188,39 @@ sub prepare_peaktime_specials {
return 1;
}
sub validate_billing_fee {
my ($values,$error_code,$value_code) = @_;
$value_code //= sub {
my $field = shift;
return $values->{$field};
};
my $match_mode = $values->{match_mode};
if (defined $match_mode
and ('regex_longest_pattern' eq $match_mode
or 'regex_longest_match' eq $match_mode)) {
foreach my $field (qw(source destination)) {
my $pattern = &$value_code($field);
if (defined $pattern and length($pattern) > 0) {
eval {
qr/$pattern/;
};
if ($@) {
return 0 unless &$error_code($field,'no valid regexp',$@);
}
}
}
}
foreach my $field (qw(onpeak_init_interval onpeak_follow_interval offpeak_init_interval offpeak_follow_interval)) {
if(int(&$value_code($field)) < 1) {
return 0 unless &$error_code($field,'must be greater than 0');
}
}
return 1;
}
sub process_billing_fees{
my(%params) = @_;
my ($c,$data,$profile,$schema) = @params{qw/c data profile schema/};
@ -211,10 +244,6 @@ sub process_billing_fees{
next;
}
@fields = $csv->fields();
unless (scalar @fields == scalar @cols) {
push @fails, $linenum;
next;
}
my $row = {};
@{$row}{@cols} = @fields;
unless($row->{zone}){
@ -232,6 +261,17 @@ sub process_billing_fees{
$row->{billing_zone_id} = $zones{$k};
delete $row->{zone};
delete $row->{zone_detail};
$row->{match_mode} = 'regex_longest_pattern' unless $row->{match_mode};
unless (validate_billing_fee($row,
sub {
my ($field,$error,$error_detail) = @_;
return 0;
},
undef,
)) {
push @fails, $linenum;
next;
}
push @fees, $row;
}

@ -122,6 +122,7 @@ log4perl.appender.Default.layout.ConversionPattern=%d{ISO8601} [%p] [%F +%L] %m{
element_order offpeak_follow_rate
element_order offpeak_follow_interval
element_order use_free_time
element_order match_mode
</fees_csv>
<sip>

Loading…
Cancel
Save