diff --git a/lib/admin/Controller/account.pm b/lib/admin/Controller/account.pm index 8020081..7ed1a42 100644 --- a/lib/admin/Controller/account.pm +++ b/lib/admin/Controller/account.pm @@ -126,10 +126,10 @@ sub detail : Local { } else { my $product; return unless $c->model('Provisioning')->call_prov( $c, 'billing', 'get_product', - { product => $c->session->{voip_account}{product} }, + { handle => $c->session->{voip_account}{product} }, \$product ); - $c->session->{voip_account}{product_name} = $$product{name}; + $c->session->{voip_account}{product_name} = $$product{data}{name}; } $c->stash->{billing_features} = 1; diff --git a/lib/admin/Controller/billing.pm b/lib/admin/Controller/billing.pm index 80e3332..327f255 100644 --- a/lib/admin/Controller/billing.pm +++ b/lib/admin/Controller/billing.pm @@ -360,6 +360,293 @@ sub do_delete_bilprof : Local { return; } +=head2 search_fees + +Search billing profile fees and display the result. + +=cut + +sub search_fees : Local { + my ( $self, $c ) = @_; + $c->stash->{template} = 'tt/billing_fees.tt'; + + my $bilprof = $c->request->params->{bilprof}; + + return unless $c->model('Provisioning')->call_prov( $c, 'billing', 'get_billing_profile', + { handle => $bilprof }, + \$c->stash->{bilprof} + ); + + my $limit = 10; + my %filter; + my %exact; + + if($c->request->params->{use_session}) { + %filter = %{ $c->session->{search_filter} }; + %exact = %{ $c->session->{exact_filter} }; + $c->stash->{feeerr} = $c->session->{feeerr}; + delete $c->session->{feeerr}; + } else { + foreach my $sf (qw(destination zone zone_detail)) { + if(( defined $c->request->params->{'search_'.$sf} + and length $c->request->params->{'search_'.$sf}) + or $c->request->params->{'exact_'.$sf}) + { + $filter{$sf} = $c->request->params->{'search_'.$sf} || ''; + $exact{$sf} = 1 if $c->request->params->{'exact_'.$sf}; + } + } + $c->session->{search_filter} = { %filter }; + $c->session->{exact_filter} = { %exact }; + } + + foreach my $sf (qw(destination zone zone_detail)) { + # set values for webform + $c->stash->{'exact_'.$sf} = $exact{$sf}; + $c->stash->{'search_'.$sf} = $filter{$sf}; + + next unless defined $filter{$sf}; + + # alter filter for SOAP call + $filter{$sf} =~ s/\*/\%/g; + $filter{$sf} =~ s/\?/\_/g; + unless($exact{$sf}) { + $filter{$sf} =~ s/^\%*/\%/; + $filter{$sf} =~ s/\%*$/\%/; + } + } + + my $offset = $c->request->params->{offset} || 0; + $offset = 0 if $offset !~ /^\d+$/; + + my $fee_list; + return unless $c->model('Provisioning')->call_prov( $c, 'billing', 'search_billing_profile_fees', + { handle => $bilprof, + filter => { %filter, + limit => $limit, + offset => $limit * $offset, + }, + }, + \$fee_list + ); + + $c->stash->{searched} = 1; + if(ref $$fee_list{fees} eq 'ARRAY' and @{$$fee_list{fees}}) { + $c->stash->{fee_list} = $$fee_list{fees}; + $c->stash->{total_count} = $$fee_list{total_count}; + $c->stash->{offset} = $offset; + if($$fee_list{total_count} > @{$$fee_list{fees}}) { + # paginate! + $c->stash->{pagination} = admin::Utils::paginate($c, $fee_list, $offset, $limit); + $c->stash->{max_offset} = $#{$c->stash->{pagination}}; + if(@{$$fee_list{fees}} == 1) { + $c->stash->{last_one} = 1; + } + } + } + + return 1; +} + +=head2 set_fees + +Set billing profile fees from CSV data. + +=cut + +sub set_fees : Local { + my ( $self, $c ) = @_; + + my %messages; + my @fees; + + my $upload = $c->req->upload('upload_fees'); + $messages{feefileerr} = 'Web.Fees.MissingFilename' unless defined $upload; + my $fees = eval { $upload->slurp }; + + my $bilprof = $c->request->params->{bilprof}; + my $purge = $c->request->params->{purge_existing} ? 1 : 0; + my $offset = $c->request->params->{offset} || 0; + + my @elements = eval { @{$c->config->{fees_csv}{element_order}} }; + + my $line = 0; + foreach my $fee (split /\r?\n/, $fees) { + $line++; + my %keyval; + my @values = map { length $_ ? $_ : undef } split / *, */, $fee; + unless(@elements == @values) { + $messages{feeerr} = 'Web.Fees.Fieldcount'; + $c->session->{feeerr}{line} = $line; + $messages{feeerrdetail} = 'Web.Fees.FieldsFoundRequired'; + $c->session->{feeerr}{detail} = scalar(@values) . '/' . scalar(@elements); + last; + } + @keyval{@elements} = @values; + if($keyval{destination} =~ /^\d+$/) { + $keyval{destination} = '^'. $keyval{destination} .'.*$'; + } elsif($keyval{destination} =~ /^(?:[a-z0-9]+(?:-[a-z0-9]+)*\.)+[a-z]+$/i + or $keyval{destination} =~ /^[\d.]+$/) + { + $keyval{destination} = '^.*@'. $keyval{destination} .'$'; + } elsif($keyval{destination} =~ /^.+\@(?:[a-z0-9]+(?:-[a-z0-9]+)*\.)+[a-z]+$/i + or $keyval{destination} =~ /^.+\@[\d.]+$/) + { + $keyval{destination} = '^'. $keyval{destination} .'$'; + } else { + $messages{feeerr} = 'Web.Fees.InvalidDestination'; + $c->session->{feeerr}{line} = $line; + last; + } + push @fees, \%keyval; + } + + unless(keys %messages) { + if($c->model('Provisioning')->call_prov( $c, 'billing', 'set_billing_profile_fees', + { handle => $bilprof, + fees => \@fees, + purge_existing => $purge, + }, + undef)) + { + $messages{feemsg} = 'Web.Bilprof.Updated'; + $c->session->{messages} = \%messages; + $c->response->redirect("/billing/search_fees?bilprof=$bilprof&use_session=1&offset=$offset"); + return; + } + } + $c->session->{messages} = \%messages; + + # TODO: add offset? + $c->response->redirect("/billing/search_fees?bilprof=$bilprof&use_session=1&offset=$offset"); + return; +} + +=head2 edit_fee + +Display a billing profile fee entry and allow the user to make changes. + +=cut + +sub edit_fee : Local { + my ( $self, $c ) = @_; + $c->stash->{template} = 'tt/fee_edit.tt'; + + my $bilprof = $c->stash->{bilprof} = $c->request->params->{bilprof}; + my $destination = $c->stash->{destination} = $c->request->params->{destination}; + $c->stash->{offset} = $c->request->params->{offset} || 0; + + if(ref $c->session->{restore_fee_input} eq 'HASH') { + $c->stash->{fee} = $c->session->{restore_fee_input}; + delete $c->session->{restore_fee_input}; + } else { + my $fee_list; + return unless $c->model('Provisioning')->call_prov( $c, 'billing', 'search_billing_profile_fees', + { handle => $bilprof, + filter => { destination => $destination }, + }, + \$fee_list + ); + + $c->stash->{searched} = 1; + if(ref $$fee_list{fees} eq 'ARRAY' and @{$$fee_list{fees}}) { + if(@{$$fee_list{fees}} > 1) { + $c->session->{messages}{feeerr} = 'Web.Fees.DuplicatedDestination'; + } else { + $c->stash->{fee} = $$fee_list{fees}[0]; + } + } else { + $c->session->{messages}{feeerr} = 'Web.Fees.NoSuchDestination'; + } + } + + return 1; +} + +=head2 do_edit_fee + +Update a billing profile fee entry. + +=cut + +sub do_edit_fee : Local { + my ( $self, $c ) = @_; + + my %messages; + my %settings; + + my $bilprof = $c->request->params->{bilprof}; + my $offset = $c->request->params->{offset}; + $settings{destination} = $c->request->params->{destination}; + if(defined $c->request->params->{new_destination}) { + $settings{destination} = $c->request->params->{new_destination}; + } + $settings{zone} = $c->request->params->{zone}; + $settings{zone_detail} = $c->request->params->{zone_detail}; + $settings{onpeak_init_rate} = $c->request->params->{onpeak_init_rate}; + $settings{onpeak_init_interval} = $c->request->params->{onpeak_init_interval}; + $settings{onpeak_follow_rate} = $c->request->params->{onpeak_follow_rate}; + $settings{onpeak_follow_interval} = $c->request->params->{onpeak_follow_interval}; + $settings{offpeak_init_rate} = $c->request->params->{offpeak_init_rate}; + $settings{offpeak_init_interval} = $c->request->params->{offpeak_init_interval}; + $settings{offpeak_follow_rate} = $c->request->params->{offpeak_follow_rate}; + $settings{offpeak_follow_interval} = $c->request->params->{offpeak_follow_interval}; + $settings{use_free_time} = $c->request->params->{use_free_time} ? 1 : 0; + + for(qw(onpeak_init_rate onpeak_init_interval onpeak_follow_rate onpeak_follow_interval + offpeak_init_rate offpeak_init_interval offpeak_follow_rate offpeak_follow_interval)) + { + $settings{$_} = undef unless length $settings{$_}; + } + + if($c->model('Provisioning')->call_prov( $c, 'billing', 'set_billing_profile_fees', + { handle => $bilprof, + fees => [ \%settings ], + purge_existing => 0, + }, + undef)) + { + $c->session->{messages}{stfeemsg} = 'Web.Bilprof.Updated'; + $c->response->redirect("/billing/search_fees?bilprof=$bilprof&use_session=1&offset=$offset#stored"); + return; + } + + $c->session->{restore_fee_input} = \%settings; + $c->response->redirect("/billing/edit_fee?bilprof=$bilprof&offset=$offset"); + return; +} + +=head2 do_delete_fee + +Delete a billing profile fee entry. + +=cut + +sub do_delete_fee : Local { + my ( $self, $c ) = @_; + + my $bilprof = $c->request->params->{bilprof}; + my $destination = $c->request->params->{destination}; + my $offset = $c->request->params->{offset}; + + if($c->model('Provisioning')->call_prov( $c, 'billing', 'set_billing_profile_fees', + { handle => $bilprof, + fees => [ { destination => $destination } ], + purge_existing => 0, + }, + undef)) + { + $c->session->{messages}{stfeemsg} = 'Web.Bilprof.Updated'; + $offset-- if $c->request->params->{last_one}; + $c->response->redirect("/billing/search_fees?bilprof=$bilprof&use_session=1&offset=$offset#stored"); + return; + } + + $c->response->redirect("/billing/search_fees?bilprof=$bilprof&use_session=1&offset=$offset"); + return; +} + + =head1 BUGS AND LIMITATIONS =over diff --git a/root/css/admin.css b/root/css/admin.css index 4a85910..7056ced 100644 --- a/root/css/admin.css +++ b/root/css/admin.css @@ -96,13 +96,20 @@ h3 { #contentplace .tdkey { font-weight: bold; white-space: nowrap; - vertical-align: top; + vertical-align: top; } #contentplace .table_header { font-weight: bold; white-space: nowrap; } +.bottom { + vertical-align: bottom; +} +.header_rate_interval { + margin-left: 25px; + margin-right: -25px; +} #contentplace .nowrap { white-space: nowrap; @@ -139,6 +146,30 @@ h3 { border: 1px solid #606060; } +/* + * input type="file" can't be themed, so we use some magic + */ +/* +#container #contentplace div.fileinputs { + position: relative; + margin-left: 100px; +} +#container #contentplace div.fakefile { + position: absolute; + top: 0px; + left: 0px; + z-index: 1; +} +#container #contentplace input.file { + position: relative; + text-align: right; + -moz-opacity:0 ; + filter:alpha(opacity: 0); + opacity: 0; + z-index: 2; +} +*/ + /* sigh, no border for checkboxes */ #contentplace .checkbox { border: none !important; @@ -271,7 +302,8 @@ h3 { #contentplace .orders, #contentplace .contracts, #contentplace .order_payments, -#contentplace .registered_contacts +#contentplace .registered_contacts, +#contentplace .billing_fees { width: 100%; border-spacing: 0px; diff --git a/root/tt/billing.tt b/root/tt/billing.tt index a61ae87..dad2efc 100644 --- a/root/tt/billing.tt +++ b/root/tt/billing.tt @@ -64,6 +64,7 @@
| destination | +zone | +zone detail | ++ | + |
| [% fee.destination %] | +[% fee.zone %] | +[% fee.zone_detail %] | ++ edit + | ++ delete + | +