From 8eeac13d3a8b231efd786eca0555087de5dbb17e Mon Sep 17 00:00:00 2001 From: Jonathan Prykop Date: Thu, 10 Sep 2015 21:33:37 -0500 Subject: [PATCH] RT#33410: Package GB add-ons --- FS/FS/cust_pkg.pm | 12 ++- FS/FS/cust_pkg_usageprice.pm | 5 + FS/FS/part_pkg/sqlradacct_hour.pm | 15 ++- FS/FS/part_pkg_usageprice.pm | 44 ++++++++- httemplate/edit/process/change-cust_pkg.html | 39 +++++++- httemplate/elements/cust_pkg_usageprice.html | 9 +- httemplate/elements/order_pkg.js | 68 +------------ httemplate/misc/change_pkg.cgi | 23 ++++- httemplate/misc/cust_pkg_usageprice.html | 121 +++++++++++++++++++++++ httemplate/misc/order_pkg.html | 21 +--- httemplate/misc/xmlhttp-part_pkg_usageprice.html | 18 +++- 11 files changed, 267 insertions(+), 108 deletions(-) create mode 100644 httemplate/misc/cust_pkg_usageprice.html diff --git a/FS/FS/cust_pkg.pm b/FS/FS/cust_pkg.pm index c5a3d2e58..0ef7aa0fa 100644 --- a/FS/FS/cust_pkg.pm +++ b/FS/FS/cust_pkg.pm @@ -2296,9 +2296,15 @@ sub change { } } - # transfer usage pricing add-ons, if we're not changing pkgpart - if ( $same_pkgpart ) { - foreach my $old_cust_pkg_usageprice ($self->cust_pkg_usageprice) { + # transfer usage pricing add-ons, if we're not changing pkgpart or if they were specified + if ( $same_pkgpart || $opt->{'cust_pkg_usageprice'}) { + my @old_cust_pkg_usageprice; + if ($opt->{'cust_pkg_usageprice'}) { + @old_cust_pkg_usageprice = @{ $opt->{'cust_pkg_usageprice'} }; + } else { + @old_cust_pkg_usageprice = $self->cust_pkg_usageprice; + } + foreach my $old_cust_pkg_usageprice (@old_cust_pkg_usageprice) { my $new_cust_pkg_usageprice = new FS::cust_pkg_usageprice { 'pkgnum' => $cust_pkg->pkgnum, 'usagepricepart' => $old_cust_pkg_usageprice->usagepricepart, diff --git a/FS/FS/cust_pkg_usageprice.pm b/FS/FS/cust_pkg_usageprice.pm index 5b6b18c70..29e627882 100644 --- a/FS/FS/cust_pkg_usageprice.pm +++ b/FS/FS/cust_pkg_usageprice.pm @@ -163,6 +163,11 @@ sub apply { #this has no multiplication involved, its just a set only #} elsif ( $target eq 'svc_conferencing.confqualitynum' ) { + + } elsif ( $target eq 'sqlradacct_hour.recur_included_total' ) { + + $error = "Cannot call apply on target $target"; + } if ( $error ) { diff --git a/FS/FS/part_pkg/sqlradacct_hour.pm b/FS/FS/part_pkg/sqlradacct_hour.pm index 79e64fbab..206bea0d0 100644 --- a/FS/FS/part_pkg/sqlradacct_hour.pm +++ b/FS/FS/part_pkg/sqlradacct_hour.pm @@ -105,7 +105,18 @@ sub calc_recur { 'AcctOutputOctets' ) / BU; - my $total = $input + $output - $self->option('recur_included_total'); + my $included_total = $self->option('recur_included_total') || 0; + my $addoncharge = 0; + foreach my $cust_pkg_usageprice ($cust_pkg->cust_pkg_usageprice) { + my $part_pkg_usageprice = $cust_pkg_usageprice->part_pkg_usageprice; + $included_total += $cust_pkg_usageprice->quantity * $part_pkg_usageprice->amount; + $addoncharge += $cust_pkg_usageprice->price; + } + my $raw_total = $input + $output; + push(@$details,sprintf( "%.3f %ss included, %.3f %ss used", $included_total, BA, $raw_total, BA )) + if $included_total; + + my $total = $input + $output - $included_total; $total = 0 if $total < 0; $input = $input - $self->option('recur_included_input'); $input = 0 if $input < 0; @@ -153,7 +164,7 @@ sub calc_recur { sprintf('%.1f', $hours). " hours: $hourscharge"; } - my $charges = $hourscharge + $inputcharge + $outputcharge + $totalcharge; + my $charges = $hourscharge + $inputcharge + $outputcharge + $totalcharge + $addoncharge; if ( $self->option('global_cap') && $charges > $self->option('global_cap') ) { $charges = $self->option('global_cap'); push @$details, "Usage charges capped at: $charges"; diff --git a/FS/FS/part_pkg_usageprice.pm b/FS/FS/part_pkg_usageprice.pm index 9c3b1be87..b33904e9e 100644 --- a/FS/FS/part_pkg_usageprice.pm +++ b/FS/FS/part_pkg_usageprice.pm @@ -111,13 +111,46 @@ sub check { || $self->ut_enum('action', [ 'increment', 'set' ]) || $self->ut_enum('target', [ 'svc_acct.totalbytes', 'svc_acct.seconds', 'svc_conferencing.participants', - 'svc_conferencing.confqualitynum' +# 'svc_conferencing.confqualitynum', + 'sqlradacct_hour.recur_included_total' ] ) || $self->ut_text('amount') ; return $error if $error; + #Check target against package + #UI doesn't currently prevent these from happing, + #so keep error messages informative + my $part_pkg = $self->part_pkg; + my $target = $self->target; + my $label = $self->target_info->{'label'}; + my ($needs_svcdb, $needs_plan); + if ( $target =~ /^svc_acct.(\w+)$/ ) { + $needs_svcdb = 'svc_acct'; + } elsif ( $target eq 'svc_conferencing.participants' ) { + $needs_svcdb = 'svc_conferencing'; + } elsif ( $target =~ /^sqlradacct_hour.(\w+)$/ ) { + $needs_plan = 'sqlradacct_hour'; + } + if ($needs_svcdb) { + my $has_svcdb = 0; + foreach my $pkg_svc ($part_pkg->pkg_svc) { + next unless $pkg_svc->quantity; + my $svcdb = $pkg_svc->part_svc->svcdb; + $has_svcdb = 1 + if $svcdb eq $needs_svcdb; + last if $has_svcdb; + } + return "Usage pricing add-on \'$label\' can only be used on packages with at least one $needs_svcdb service.\n" + unless $has_svcdb; + } + if ($needs_plan) { + return "Usage pricing add-on \'$label\' can only be used on packages with pricing plan \'" . + FS::part_pkg->plan_info->{$needs_plan}->{'shortname'} . "\'\n" + unless ref($part_pkg) eq 'FS::part_pkg::' . $needs_plan; + } + $self->SUPER::check; } @@ -147,10 +180,10 @@ sub targets { #'svc_acct.totalbytes' => { label => 'Megabytes', # multiplier => 1048576, # }, - 'svc_acct.totalbytes' => { label => 'Gigabytes', + 'svc_acct.totalbytes' => { label => 'Total Gigabytes', multiplier => 1073741824, }, - 'svc_acct.seconds' => { label => 'Hours', + 'svc_acct.seconds' => { label => 'Total Hours', multiplier => 3600, }, 'svc_conferencing.participants' => { label => 'Conference Participants', @@ -160,6 +193,11 @@ sub targets { # and then value comes from a select, not a text field # 'svc_conferencing.confqualitynum' => { label => 'Conference Quality', # }, + + # this bypasses usual apply methods, handled entirely in sqlradacct_hour + 'sqlradacct_hour.recur_included_total' => { label => 'Included Gigabytes', + multiplier => 1 }, #recur_included_total is stored in GB + ; \%targets; diff --git a/httemplate/edit/process/change-cust_pkg.html b/httemplate/edit/process/change-cust_pkg.html index 046a9795c..308ea8ffd 100644 --- a/httemplate/edit/process/change-cust_pkg.html +++ b/httemplate/edit/process/change-cust_pkg.html @@ -59,6 +59,40 @@ unless ($error) { $error = $cust_pkg->change_later(\%change); } } else { + + # for now, can't change usageprice with change_later + my @old_cust_pkg_usageprice = $cust_pkg->cust_pkg_usageprice; + + # build new usageprice array + # false laziness with /edit/process/quick-cust_pkg.cgi + my @cust_pkg_usageprice = (); + foreach my $quantity_param ( grep { $cgi->param($_) && $cgi->param($_) > 0 } + grep /^usagepricenum(\d+)_quantity$/, + $cgi->param + ) + { + $quantity_param =~ /^usagepricenum(\d+)_quantity$/ or die 'unpossible'; + my $num = $1; + push @cust_pkg_usageprice, new FS::cust_pkg_usageprice { + usagepricepart => scalar($cgi->param("usagepricenum${num}_usagepricepart")), + quantity => scalar($cgi->param($quantity_param)), + }; + } + + # Need to figure out if usagepricepart quantities changed + my %oldup = map { $_->usagepricepart, $_->quantity } @old_cust_pkg_usageprice; + my %newup = map { $_->usagepricepart, $_->quantity } @cust_pkg_usageprice; + my $usagechanged = 0; + foreach my $up (keys %oldup) { + last if $usagechanged; + $usagechanged = 1 unless $oldup{$up} == $newup{$up}; + } + foreach my $up (keys %newup) { + last if $usagechanged; + $usagechanged = 1 unless $oldup{$up} == $newup{$up}; + } + $change{'cust_pkg_usageprice'} = \@cust_pkg_usageprice; + # special case: if there's a package change scheduled, and it matches # the parameters the user requested this time, then change to the existing # future package. @@ -68,12 +102,13 @@ unless ($error) { $change_to->pkgpart == $change{'pkgpart'} and $change_to->locationnum == $change{'locationnum'} and $change_to->quantity == $change{'quantity'} and - $change_to->contract_end == $change{'contract_end'} + $change_to->contract_end == $change{'contract_end'} and + !$usagechanged ) { %change = ( 'cust_pkg' => $change_to ); } } - + # do a package change right now my $pkg_or_error = $cust_pkg->change( \%change ); $error = ref($pkg_or_error) ? '' : $pkg_or_error; diff --git a/httemplate/elements/cust_pkg_usageprice.html b/httemplate/elements/cust_pkg_usageprice.html index 729099320..74b7842be 100644 --- a/httemplate/elements/cust_pkg_usageprice.html +++ b/httemplate/elements/cust_pkg_usageprice.html @@ -23,15 +23,16 @@ > % my $info = $part_pkg_usageprice->target_info; % my $amount = $part_pkg_usageprice->amount / ($info->{multiplier}||1); - % for (1..100) { #100? arbitrary. - % } @@ -42,8 +43,6 @@ % } <%init> -#my $targets = FS::part_pkg_usageprice->targets; - my( %opt ) = @_; my $conf = new FS::Conf; diff --git a/httemplate/elements/order_pkg.js b/httemplate/elements/order_pkg.js index 3586a54cb..a850d2193 100644 --- a/httemplate/elements/order_pkg.js +++ b/httemplate/elements/order_pkg.js @@ -1,10 +1,12 @@ function pkg_changed () { var form = document.OrderPkgForm; var discountnum = form.discountnum; + var opt = form.pkgpart.options[form.pkgpart.selectedIndex]; + + usageprice_pkg_changed( opt.value ); if ( form.pkgpart.selectedIndex > 0 ) { - var opt = form.pkgpart.options[form.pkgpart.selectedIndex]; var date_button = document.getElementById('start_date_button'); var date_button_disabled = document.getElementById('start_date_disabled'); var date_text = document.getElementById('start_date_text'); @@ -68,78 +70,14 @@ function pkg_changed () { } } - get_part_pkg_usageprice( opt.value, update_part_pkg_usageprice ); - } else { form.submitButton.disabled = true; if ( discountnum ) { form.discountnum.disabled = true; } discountnum_changed(form.discountnum); } -} - -function update_part_pkg_usageprice(part_pkg_usageprice) { - - var table = document.getElementById('cust_pkg_usageprice_table'); - - // black the current usage price rows - for ( var r = table.rows.length - 1; r >= 0; r-- ) { - table.deleteRow(r); - } - - // add the new usage price rows - var rownum = 0; - var usagepriceArray = eval('(' + part_pkg_usageprice + ')' ); - for ( var s = 0; s < usagepriceArray.length; s=s+2 ) { - //surely this should be some kind of JSON structure - var html = usagepriceArray[s+0]; - var javascript = usagepriceArray[s+1]; - - // a lot like ("inspiried by") edit/elements/edit.html function spawn_<%$field%> - - // XXX evaluate the javascript - //if (window.ActiveXObject) { - // window.execScript(newfunc); - //} else { /* (window.XMLHttpRequest) */ - // //window.eval(newfunc); - // setTimeout(newfunc, 0); - //} - - var row = table.insertRow(rownum++); - - //var label_cell = document.createElement('TD'); - - //label_cell.id = '<% $field %>_label' + <%$field%>_fieldnum; - - //label_cell.style.textAlign = "right"; - //label_cell.style.verticalAlign = "top"; - //label_cell.style.borderTop = "1px solid black"; - //label_cell.style.paddingTop = "5px"; - - //label_cell.innerHTML = '<% $label %>'; - - //row.appendChild(label_cell); - - var widget_cell = document.createElement('TD'); - - //widget_cell.style.borderTop = "1px solid black"; - widget_cell.style.paddingTop = "3px"; - widget_cell.colSpan = "2"; - - widget_cell.innerHTML = html; - - row.appendChild(widget_cell); - - } - - if ( rownum > 0 ) { - document.getElementById('cust_pkg_usageprice_title').style.display = ''; - } else { - document.getElementById('cust_pkg_usageprice_title').style.display = 'none'; - } } - function standardize_new_location() { var form = document.OrderPkgForm; var loc = form.locationnum; diff --git a/httemplate/misc/change_pkg.cgi b/httemplate/misc/change_pkg.cgi index e74747e82..b562d24cd 100755 --- a/httemplate/misc/change_pkg.cgi +++ b/httemplate/misc/change_pkg.cgi @@ -19,13 +19,13 @@ <& /elements/tr-select-cust-part_pkg.html, 'pre_label' => emt('New'), - 'curr_value' => scalar($cgi->param('pkgpart')), + 'curr_value' => scalar($cgi->param('pkgpart')) || $cust_pkg->pkgpart, 'classnum' => $part_pkg->classnum, 'cust_main' => $cust_main, &> <& /elements/tr-input-pkg-quantity.html, - 'curr_value' => $cust_pkg->quantity + 'curr_value' => scalar($cgi->param('quantity')) || $cust_pkg->quantity &> % if ($use_contract_end) { @@ -39,6 +39,11 @@
+<% include('/misc/cust_pkg_usageprice.html', + 'pkgpart' => (scalar($cgi->param('pkgpart')) || $cust_pkg->pkgpart), + 'pkgnum' => ($cust_pkg->change_to_pkgnum || $pkgnum), + ) %> +
<% mt('Change') |h %> <% ntable('#cccccc') %> @@ -49,8 +54,16 @@ document.getElementById('start_date_text').disabled = !enable; document.getElementById('start_date_button').style.display = (enable ? '' : 'none'); - document.getElementById('start_date_button_disabled').style.display = - (enable ? 'none' : ''); + if (document.getElementById('start_date_button_disabled')) { // does this ever exist anymore? + document.getElementById('start_date_button_disabled').style.display = + (enable ? 'none' : ''); + } + if (enable) { + usageprice_disable(1); + } else { + var form = document.OrderPkgForm; + usageprice_disable(0,form.pkgpart.options[form.pkgpart.selectedIndex].value); + } } <&| /elements/onload.js &> delay_changed(); @@ -96,7 +109,7 @@ TYPE = "button" VALUE = "<% mt("Change package") |h %>" onClick = "this.disabled=true; standardize_new_location();" - <% scalar($cgi->param('pkgpart')) ? '' : 'DISABLED' %> + <% #scalar($cgi->param('pkgpart')) ? '' : 'DISABLED' %> > diff --git a/httemplate/misc/cust_pkg_usageprice.html b/httemplate/misc/cust_pkg_usageprice.html new file mode 100644 index 000000000..f2e0f57e6 --- /dev/null +++ b/httemplate/misc/cust_pkg_usageprice.html @@ -0,0 +1,121 @@ +<%doc> +Sets up the xmlhttp, javascript and initial (empty) table for selecting cust_pkg_usageprice. +Available values are based on pkgpart, and can be updated when changing pkgpart +by passing the new pkgpart to the following javascript: + + usageprice_pkg_changed( pkgpart, pkgnum ) + +The pkgnum input is optional, and will be used to set initial selected values. + +If pkgpart is passed as an option to this element, will run usageprice_pkg_changed +once to initialize table; pkgnum can be passed as an option along with this. + +You can disable usageprice selection temporarily (remove the fields from the form) +with the javascript usageprice_disable(1), and restore it with usageprice_disable(0,pkgnum). +While disabled, calling usageprice_pkg_changed will have no effect. + + +<& /elements/xmlhttp.html, + 'url' => $p.'misc/xmlhttp-part_pkg_usageprice.html', + 'subs' => [ 'get_part_pkg_usageprice' ], +&> + + + + +
+ + + +<%init> +my %opt = @_; + + + diff --git a/httemplate/misc/order_pkg.html b/httemplate/misc/order_pkg.html index 799165fe0..cb2bd4832 100644 --- a/httemplate/misc/order_pkg.html +++ b/httemplate/misc/order_pkg.html @@ -5,11 +5,6 @@ } &> -<& /elements/xmlhttp.html, - 'url' => $p.'misc/xmlhttp-part_pkg_usageprice.html', - 'subs' => [ 'get_part_pkg_usageprice' ], -&> - <& /elements/init_calendar.html &> @@ -121,19 +116,9 @@
-%#so: -%# - hide until you selecdt a pacakge with add-ons -%# -lookup and display the available add-ons when -%# -add them to the (recur if there is one, otherwise setup) price and display magically like processing fees do on edit/cust_pay.cgi - -%# better label? - - - -
+<% include('/misc/cust_pkg_usageprice.html', + 'pkgpart' => $pkgpart + ) %>
% my $discount_cust_pkg = $curuser->access_right('Discount customer package'); diff --git a/httemplate/misc/xmlhttp-part_pkg_usageprice.html b/httemplate/misc/xmlhttp-part_pkg_usageprice.html index d4e2d8469..9decdeff9 100644 --- a/httemplate/misc/xmlhttp-part_pkg_usageprice.html +++ b/httemplate/misc/xmlhttp-part_pkg_usageprice.html @@ -1,24 +1,32 @@ <% encode_json( \@return ) %>\ <%init> -my( $pkgpart ) = $cgi->param('arg'); +my( $pkgpart, $pkgnum ) = $cgi->param('arg'); #could worry about agent-virting this so you can't see the add-on pricing of # other agents, but not a real-world big worry my $part_pkg = qsearchs( 'part_pkg', { pkgpart=>$pkgpart } ); +my %curr_quantity; +if ($pkgnum) { + my $cust_pkg = qsearchs( 'cust_pkg', { pkgnum=>$pkgnum } ); + %curr_quantity = map { $_->usagepricepart, $_->quantity } $cust_pkg->cust_pkg_usageprice; +} + my $num = 0; -my @return = map { +# probably don't need to be returning js_only anymore? +my @return = ($pkgpart, map { + my $usagepricepart = $_->usagepricepart; my @inc = ('/elements/cust_pkg_usageprice.html', - 'usagepricepart' => $_->usagepricepart, + 'usagepricepart' => $usagepricepart, ); - + push(@inc,'curr_quantity',($curr_quantity{$usagepricepart} || 0)); ( include(@inc, field=>'usagepricenum'.$num, html_only=>1 ), include(@inc, field=>'usagepricenum'.$num++, js_only=>1 ), ); } - $part_pkg->part_pkg_usageprice; + $part_pkg->part_pkg_usageprice); -- 2.11.0