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 @@ name + [% id = 0 %] [% FOREACH bilprof = bilprofs %] @@ -89,11 +90,14 @@ [% END %] + + show fees + [% id = id + 1 %] [% END %] [% ELSE %] - No billing profiles found in the database. + No billing profiles found in the database. [% END %] diff --git a/root/tt/billing_fees.tt b/root/tt/billing_fees.tt new file mode 100644 index 0000000..a15e9f2 --- /dev/null +++ b/root/tt/billing_fees.tt @@ -0,0 +1,148 @@ +

[% bilprof.data.name %]

+ +

Upload Billing Fees

+ +
+
+ + + [% IF messages.feemsg %]
[% messages.feemsg %]
[% END %] + [% IF messages.feeerr %] +
[% messages.feeerr %] + [% IF feeerr.line %] + at line [% feeerr.line %] + [% END %] + [% IF messages.feeerrdetail %] +
+ [% messages.feeerrdetail %] + [% IF feeerr.detail %] + [% feeerr.detail %] + [% END %] + [% END %] +
+ [% END %] + + +
+ + [% IF messages.feefileerr %]
[% messages.feefileerr %]
[% END %] + +
+ +
+
+ + + +
+
+ + + + + +
+ + + + +
+ + + + +
+ + +
+
+ +

Stored Billing Fees

+ + [% IF fee_list %] + +
+ [% IF messages.stfeemsg %]
[% messages.stfeemsg %]
[% END %] + + + + + + + + [% bgflip = 1 %] + [% FOREACH fee = fee_list %] + [% IF bgflip %] + [% bgflip = 0 %] + + [% ELSE %] + [% bgflip = 1 %] + + [% END %] + + + + + + + [% END %] +
destinationzonezone detail + +
[% fee.destination %][% fee.zone %][% fee.zone_detail %] + edit + + delete +
+ + [% IF pagination %] + + [% END %] + +
+ + [% ELSIF searched %] + +
+ No matching billing fees found. +
+ + [% END %] + diff --git a/root/tt/fee_edit.tt b/root/tt/fee_edit.tt new file mode 100644 index 0000000..eb0bad1 --- /dev/null +++ b/root/tt/fee_edit.tt @@ -0,0 +1,106 @@ + [% IF destination %] +

Edit Billing Fee

+ [% ELSE %] +

Create New Billing Fee

+ [% END %] + +
+ cancel +
+ +
+
+ + + + + + + [% IF destination %] + + [% ELSE %] + + [% END %] + + [% IF messages.destination %] + + [% END %] + + + + + [% IF messages.zone %] + + [% END %] + + + + + [% IF messages.zone_detail %] + + [% END %] + + + + + [% IF messages.onpeak_init_rate %] + + [% END %] + + + + + [% IF messages.onpeak_init_interval %] + + [% END %] + + + + + [% IF messages.onpeak_follow_rate %] + + [% END %] + + + + + [% IF messages.onpeak_follow_interval %] + + [% END %] + + + + + [% IF messages.offpeak_init_rate %] + + [% END %] + + + + + [% IF messages.offpeak_init_interval %] + + [% END %] + + + + + [% IF messages.offpeak_follow_rate %] + + [% END %] + + + + + [% IF messages.offpeak_follow_interval %] + + [% END %] + + + + +
destination:[% fee.destination %]
[% messages.destination %]
zone:
[% messages.zone %]
zone detail:
[% messages.zone_detail %]
onpeak init rate:
[% messages.onpeak_init_rate %]
onpeak init interval:
[% messages.onpeak_init_interval %]
onpeak follow rate:
[% messages.onpeak_follow_rate %]
onpeak follow interval:
[% messages.onpeak_follow_interval %]
offpeak init rate:
[% messages.offpeak_init_rate %]
offpeak init interval:
[% messages.offpeak_init_interval %]
offpeak follow rate:
[% messages.offpeak_follow_rate %]
offpeak follow interval:
[% messages.offpeak_follow_interval %]
use free time:
+ +
+
+