From: Ivan Kohler Date: Thu, 9 Jan 2014 01:23:05 +0000 (-0800) Subject: time/data/etc. unit pricing add-ons, RT#24392 X-Git-Url: http://git.freeside.biz/gitweb/?p=freeside.git;a=commitdiff_plain;h=8861d46820af163c7de7839178b6120c9b32ab2c time/data/etc. unit pricing add-ons, RT#24392 --- diff --git a/FS/FS.pm b/FS/FS.pm index 3ff36560d..afea6f122 100644 --- a/FS/FS.pm +++ b/FS/FS.pm @@ -257,6 +257,8 @@ L - Package definition localization class L - Package definition usage pricing add-on class +L - Customer package usage pricing add-on class + L - Package definition local currency prices L - Currency exchange rates diff --git a/FS/FS/Mason.pm b/FS/FS/Mason.pm index 276c8c0b0..62e575560 100644 --- a/FS/FS/Mason.pm +++ b/FS/FS/Mason.pm @@ -367,6 +367,7 @@ if ( -e $addl_handler_use_file ) { use FS::conferencing_quality; use FS::svc_video; use FS::part_pkg_usageprice; + use FS::cust_pkg_usageprice; # Sammath Naur if ( $FS::Mason::addl_handler_use ) { diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm index 5833d865c..870ac4fec 100644 --- a/FS/FS/Schema.pm +++ b/FS/FS/Schema.pm @@ -3056,6 +3056,26 @@ sub tables_hashref { ], }, + 'cust_pkg_usageprice' => { + 'columns' => [ + 'usagepricenum', 'serial', '', '', '', '', + 'pkgnum', 'int', '', '', '', '', + 'usagepricepart', 'int', '', '', '', '', + 'quantity', 'int', '', '', '', '', + ], + 'primary_key' => 'usagepricenum', + 'unique' => [ [ 'pkgnum', 'usagepricepart' ] ], + 'index' => [ [ 'pkgnum' ] ], + 'foreign_keys' => [ + { columns => [ 'pkgnum' ], + table => 'cust_pkg', + }, + { columns => [ 'usagepricepart' ], + table => 'part_pkg_usageprice', + }, + ], + }, + 'part_pkg_link' => { 'columns' => [ 'pkglinknum', 'serial', '', '', '', '', diff --git a/FS/FS/cust_pkg.pm b/FS/FS/cust_pkg.pm index afdd41d59..97354d429 100644 --- a/FS/FS/cust_pkg.pm +++ b/FS/FS/cust_pkg.pm @@ -267,6 +267,10 @@ orders. (Currently this includes: intro periods when delay_setup is on.) cust_pkg_option records will be created +=item cust_pkg_usageprice + +Array reference of cust_pkg_usageprice objects, will be inserted + =item ticket_subject a ticket will be added to this customer with this subject @@ -353,6 +357,17 @@ sub insert { 'params' => $self->refnum, ); + if ( $self->hashref->{cust_pkg_usageprice} ) { + for my $cust_pkg_usageprice ( @{ $self->hashref->{cust_pkg_usageprice} } ) { + $cust_pkg_usageprice->pkgnum( $self->pkgnum ); + my $error = $cust_pkg_usageprice->insert; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; + } + } + } + if ( $self->discountnum ) { my $error = $self->insert_discount(); if ( $error ) { diff --git a/FS/FS/cust_pkg_usageprice.pm b/FS/FS/cust_pkg_usageprice.pm new file mode 100644 index 000000000..5380081b5 --- /dev/null +++ b/FS/FS/cust_pkg_usageprice.pm @@ -0,0 +1,117 @@ +package FS::cust_pkg_usageprice; +use base qw( FS::Record ); + +use strict; +#use FS::Record qw( qsearch qsearchs ); + +=head1 NAME + +FS::cust_pkg_usageprice - Object methods for cust_pkg_usageprice records + +=head1 SYNOPSIS + + use FS::cust_pkg_usageprice; + + $record = new FS::cust_pkg_usageprice \%hash; + $record = new FS::cust_pkg_usageprice { 'column' => 'value' }; + + $error = $record->insert; + + $error = $new_record->replace($old_record); + + $error = $record->delete; + + $error = $record->check; + +=head1 DESCRIPTION + +An FS::cust_pkg_usageprice object represents an specific customer package usage +pricing add-on. FS::cust_pkg_usageprice inherits from FS::Record. The +following fields are currently supported: + +=over 4 + +=item usagepricenum + +primary key + +=item pkgnum + +pkgnum + +=item usagepricepart + +usagepricepart + +=item quantity + +quantity + + +=back + +=head1 METHODS + +=over 4 + +=item new HASHREF + +Creates a new record. To add the record to the database, see L<"insert">. + +Note that this stores the hash reference, not a distinct copy of the hash it +points to. You can ask the object for a copy with the I method. + +=cut + +# the new method can be inherited from FS::Record, if a table method is defined + +sub table { 'cust_pkg_usageprice'; } + +=item insert + +Adds this record to the database. If there is an error, returns the error, +otherwise returns false. + +=item delete + +Delete this record from the database. + +=item replace OLD_RECORD + +Replaces the OLD_RECORD with this one in the database. If there is an error, +returns the error, otherwise returns false. + +=item check + +Checks all fields to make sure this is a valid record. If there is +an error, returns the error, otherwise returns false. Called by the insert +and replace methods. + +=cut + +sub check { + my $self = shift; + + my $error = + $self->ut_numbern('usagepricenum') + || $self->ut_number('pkgnum') + || $self->ut_number('usagepricepart') + || $self->ut_number('quantity') + ; + return $error if $error; + + $self->SUPER::check; +} + +=back + +=head1 BUGS + +=head1 SEE ALSO + +L + +=cut + +1; + diff --git a/FS/FS/part_pkg_usageprice.pm b/FS/FS/part_pkg_usageprice.pm index 88e3870d4..9c3b1be87 100644 --- a/FS/FS/part_pkg_usageprice.pm +++ b/FS/FS/part_pkg_usageprice.pm @@ -121,8 +121,21 @@ sub check { $self->SUPER::check; } +=item target_info + +Returns a hash reference of information about the target of this object. +Keys are "label" and "multiplier". + +=cut + +sub target_info { + my $self = shift; + $self->targets->{$self->target}; +} + =item targets +(Class method) Returns a hash reference. Keys are possible values for the "target" field. Values are hash references with "label" and "multiplier" keys. diff --git a/FS/MANIFEST b/FS/MANIFEST index 3bed3eef1..315e62c3d 100644 --- a/FS/MANIFEST +++ b/FS/MANIFEST @@ -747,3 +747,5 @@ FS/svc_video.pm t/svc_video.t FS/part_pkg_usageprice.pm t/part_pkg_usageprice.t +FS/cust_pkg_usageprice.pm +t/cust_pkg_usageprice.t diff --git a/FS/t/cust_pkg_usageprice.t b/FS/t/cust_pkg_usageprice.t new file mode 100644 index 000000000..2e057edf0 --- /dev/null +++ b/FS/t/cust_pkg_usageprice.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::cust_pkg_usageprice; +$loaded=1; +print "ok 1\n"; diff --git a/httemplate/browse/part_pkg.cgi b/httemplate/browse/part_pkg.cgi index b07386618..574cf7af3 100755 --- a/httemplate/browse/part_pkg.cgi +++ b/httemplate/browse/part_pkg.cgi @@ -278,9 +278,8 @@ push @fields, sub { ), ], ( - map { my $amount = $_->amount - / (FS::part_pkg_usageprice->targets->{$_->target}{multiplier}||1); - my $label = FS::part_pkg_usageprice->targets->{$_->target}{label}; + map { my $amount = $_->amount / ($_->target_info->{multiplier} || 1); + my $label = $_->target_info->{label}; [ { data => "Plus $money_char". $_->price. ' '. ( $_->action eq 'increment' ? 'per' : 'for' ). diff --git a/httemplate/edit/process/quick-cust_pkg.cgi b/httemplate/edit/process/quick-cust_pkg.cgi index 7dcd232bc..95c92432e 100644 --- a/httemplate/edit/process/quick-cust_pkg.cgi +++ b/httemplate/edit/process/quick-cust_pkg.cgi @@ -133,6 +133,21 @@ my %hash = ( ); $hash{'custnum'} = $cust_main->custnum if $cust_main; +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)), + }; +} +$hash{cust_pkg_usageprice} = \@cust_pkg_usageprice; + if ( $quotationnum ) { $quotation_pkg = new FS::quotation_pkg \%hash; diff --git a/httemplate/elements/cust_pkg_usageprice.html b/httemplate/elements/cust_pkg_usageprice.html new file mode 100644 index 000000000..729099320 --- /dev/null +++ b/httemplate/elements/cust_pkg_usageprice.html @@ -0,0 +1,72 @@ +% unless ( $opt{'js_only'} ) { + + + + + + + + +% ### +% # action +% ### + + + +
+% # maybe we should be a quantity entry instead of a select? even more +% # javascript auto-calculation need to display a subtotal & total + +
+ +% } +<%init> + +#my $targets = FS::part_pkg_usageprice->targets; + +my( %opt ) = @_; + +my $conf = new FS::Conf; +my $money_char = $conf->config('money_char') || '$'; + +my $name = $opt{'element_name'} || $opt{'field'} || 'usagepricenum'; +my $id = $opt{'id'} || $opt{'field'} || 'usagepricenum'; + +my $curr_value = $opt{'curr_value'} || $opt{'value'}; + +my $onchange = ''; +if ( $opt{'onchange'} ) { + $onchange = $opt{'onchange'}; + $onchange .= '(this)' unless $onchange =~ /\(\w*\);?$/; + $onchange =~ s/\(what\);/\(this\);/g; #ugh, terrible hack. all onchange + #callbacks should act the same + $onchange = 'onChange="'. $onchange. '"'; +} + +my $cust_pkg_usageprice = $curr_value + ? qsearchs('cust_pkg_usageprice', { 'usagepricenum' => $curr_value } ) + : new FS::cust_pkg_usageprice { 'usagepricepart' => $opt{usagepricepart} }; + +my $part_pkg_usageprice = $cust_pkg_usageprice->part_pkg_usageprice; + + diff --git a/httemplate/elements/order_pkg.js b/httemplate/elements/order_pkg.js index 4e41fd64a..393b845c9 100644 --- a/httemplate/elements/order_pkg.js +++ b/httemplate/elements/order_pkg.js @@ -21,8 +21,8 @@ function pkg_changed () { } } -// if this form element exists, then the start date is a future -// package change date; don't replace it + // if this form element exists, then the start date is a future + // package change date; don't replace it if ( form.delay ) { return; } @@ -39,6 +39,8 @@ function pkg_changed () { date_button_disabled.style.display = ''; } + get_part_pkg_usageprice( opt.value, update_part_pkg_usageprice ); + } else { form.submitButton.disabled = true; if ( discountnum ) { form.discountnum.disabled = true; } @@ -46,6 +48,69 @@ function pkg_changed () { } } +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/elements/order_pkg_link.html b/httemplate/elements/order_pkg_link.html index 0cae49284..6e0dd6852 100644 --- a/httemplate/elements/order_pkg_link.html +++ b/httemplate/elements/order_pkg_link.html @@ -21,6 +21,6 @@ my %optional = grep $opt{$_}, qw( lock_pkgpart lock_locationnum qualnum quotationnum svcpart ); -my $height = $opt{'lock_locationnum'} ? 336 : 606; +my $height = $opt{'lock_locationnum'} ? 470 : 740; diff --git a/httemplate/elements/tr-cust_pkg_usageprice.html b/httemplate/elements/tr-cust_pkg_usageprice.html new file mode 100644 index 000000000..ccd06311a --- /dev/null +++ b/httemplate/elements/tr-cust_pkg_usageprice.html @@ -0,0 +1,24 @@ +% unless ( $opt{'js_only'} ) { + + <% include('tr-td-label.html', %opt) %> + > + +% } +% + <% include( '/elements/cust_pkg_usageprice.html', %opt ) %> +% +% unless ( $opt{'js_only'} ) { + + + + +% } +<%init> + +my( %opt ) = @_; + +my $cell_style = $opt{'cell_style'} ? 'STYLE="'. $opt{'cell_style'}. '"' : ''; + +#$opt{'label'} ||= 'XXX Something'; + + diff --git a/httemplate/misc/detach_pkg.html b/httemplate/misc/detach_pkg.html index 366bbac3f..b2dfa389d 100755 --- a/httemplate/misc/detach_pkg.html +++ b/httemplate/misc/detach_pkg.html @@ -1,7 +1,5 @@ <& /elements/header-popup.html, mt("Detach Package to New Customer") &> - - <& /elements/error.html &>
diff --git a/httemplate/misc/order_pkg.html b/httemplate/misc/order_pkg.html index b06f9622c..080ba41d9 100644 --- a/httemplate/misc/order_pkg.html +++ b/httemplate/misc/order_pkg.html @@ -5,6 +5,12 @@ } &> +<& /elements/xmlhttp.html, + 'url' => $p.'misc/xmlhttp-part_pkg_usageprice.html', + 'subs' => [ 'get_part_pkg_usageprice' ], +&> + + @@ -113,6 +119,21 @@
+%#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? + + + +
+
+ % my $discount_cust_pkg = $curuser->access_right('Discount customer package'); % my $waive_setup_fee = $curuser->access_right('Waive setup fee'); % diff --git a/httemplate/misc/xmlhttp-part_pkg_usageprice.html b/httemplate/misc/xmlhttp-part_pkg_usageprice.html new file mode 100644 index 000000000..d4e2d8469 --- /dev/null +++ b/httemplate/misc/xmlhttp-part_pkg_usageprice.html @@ -0,0 +1,24 @@ +<% encode_json( \@return ) %>\ +<%init> + +my( $pkgpart ) = $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 $num = 0; + +my @return = map { + my @inc = ('/elements/cust_pkg_usageprice.html', + 'usagepricepart' => $_->usagepricepart, + ); + + ( include(@inc, field=>'usagepricenum'.$num, html_only=>1 ), + include(@inc, field=>'usagepricenum'.$num++, js_only=>1 ), + ); + } + $part_pkg->part_pkg_usageprice; + +