From 2d5f9e43a60773a9b079e96c330cb9e0e089800a Mon Sep 17 00:00:00 2001 From: ivan Date: Sat, 30 Jan 2010 08:55:12 +0000 Subject: [PATCH] discounts, RT#6679 --- FS/FS/cust_pkg.pm | 269 ++++++++++++++---------- FS/FS/cust_pkg_discount.pm | 20 +- httemplate/edit/process/quick-cust_pkg.cgi | 1 + httemplate/elements/select-discount.html | 20 ++ httemplate/elements/tr-select-discount.html | 27 +++ httemplate/misc/order_pkg.html | 8 +- httemplate/view/cust_main/packages/package.html | 2 +- httemplate/view/cust_main/packages/status.html | 39 +++- 8 files changed, 266 insertions(+), 120 deletions(-) create mode 100644 httemplate/elements/select-discount.html create mode 100644 httemplate/elements/tr-select-discount.html diff --git a/FS/FS/cust_pkg.pm b/FS/FS/cust_pkg.pm index a95a67d2e..5cdca09ac 100644 --- a/FS/FS/cust_pkg.pm +++ b/FS/FS/cust_pkg.pm @@ -25,6 +25,8 @@ use FS::reg_code; use FS::part_svc; use FS::cust_pkg_reason; use FS::reason; +use FS::cust_pkg_discount; +use FS::discount; use FS::UI::Web; # need to 'use' these instead of 'require' in sub { cancel, suspend, unsuspend, @@ -274,6 +276,22 @@ sub insert { 'params' => $self->refnum, ); + if ( $self->discountnum ) { + #XXX new/custom discount case + my $cust_pkg_discount = new FS::cust_pkg_discount { + 'pkgnum' => $self->pkgnum, + 'discountnum' => $self->discountnum, + 'months_used' => 0, + 'end_date' => '', #XXX + 'otaker' => $self->otaker, + }; + my $error = $cust_pkg_discount->insert; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; + } + } + #if ( $self->reg_code ) { # my $reg_code = qsearchs('reg_code', { 'code' => $self->reg_code } ); # $error = $reg_code->delete; @@ -2211,6 +2229,141 @@ sub reexport { } +=item insert_reason + +Associates this package with a (suspension or cancellation) reason (see +L, possibly inserting a new reason on the fly (see +L). + +Available options are: + +=over 4 + +=item reason + +can be set to a cancellation reason (see L), either a reasonnum of an existing reason, or passing a hashref will create a new reason. The hashref should have the following keys: typenum - Reason type (see L, reason - Text of the new reason. + +=item reason_otaker + +the access_user (see L) providing the reason + +=item date + +a unix timestamp + +=item action + +the action (cancel, susp, adjourn, expire) associated with the reason + +=back + +If there is an error, returns the error, otherwise returns false. + +=cut + +sub insert_reason { + my ($self, %options) = @_; + + my $otaker = $options{reason_otaker} || + $FS::CurrentUser::CurrentUser->username; + + my $reasonnum; + if ( $options{'reason'} =~ /^(\d+)$/ ) { + + $reasonnum = $1; + + } elsif ( ref($options{'reason'}) ) { + + return 'Enter a new reason (or select an existing one)' + unless $options{'reason'}->{'reason'} !~ /^\s*$/; + + my $reason = new FS::reason({ + 'reason_type' => $options{'reason'}->{'typenum'}, + 'reason' => $options{'reason'}->{'reason'}, + }); + my $error = $reason->insert; + return $error if $error; + + $reasonnum = $reason->reasonnum; + + } else { + return "Unparsable reason: ". $options{'reason'}; + } + + my $cust_pkg_reason = + new FS::cust_pkg_reason({ 'pkgnum' => $self->pkgnum, + 'reasonnum' => $reasonnum, + 'otaker' => $otaker, + 'action' => substr(uc($options{'action'}),0,1), + 'date' => $options{'date'} + ? $options{'date'} + : time, + }); + + $cust_pkg_reason->insert; +} + +=item set_usage USAGE_VALUE_HASHREF + +USAGE_VALUE_HASHREF is a hashref of svc_acct usage columns and the amounts +to which they should be set (see L). Currently seconds, +upbytes, downbytes, and totalbytes are appropriate keys. + +All svc_accts which are part of this package have their values reset. + +=cut + +sub set_usage { + my ($self, $valueref, %opt) = @_; + + foreach my $cust_svc ($self->cust_svc){ + my $svc_x = $cust_svc->svc_x; + $svc_x->set_usage($valueref, %opt) + if $svc_x->can("set_usage"); + } +} + +=item recharge USAGE_VALUE_HASHREF + +USAGE_VALUE_HASHREF is a hashref of svc_acct usage columns and the amounts +to which they should be set (see L). Currently seconds, +upbytes, downbytes, and totalbytes are appropriate keys. + +All svc_accts which are part of this package have their values incremented. + +=cut + +sub recharge { + my ($self, $valueref) = @_; + + foreach my $cust_svc ($self->cust_svc){ + my $svc_x = $cust_svc->svc_x; + $svc_x->recharge($valueref) + if $svc_x->can("recharge"); + } +} + +=item cust_pkg_discount + +=cut + +sub cust_pkg_discount { + my $self = shift; + qsearch('cust_pkg_discount', { 'pkgnum' => $self->pkgnum } ); +} + +=item cust_pkg_discount_active + +=cut + +sub cust_pkg_discount_active { + my $self = shift; + grep { my $d = $_->discount; + ! $d->months || $_->months_used < $d->months; # XXX also end date + } + $self->cust_pkg_discount; +} + =back =head1 CLASS METHODS @@ -2631,7 +2784,7 @@ sub search { 'cust_pkg.*', ( map "part_pkg.$_", qw( pkg freq ) ), 'pkg_class.classname', - 'cust_main.custnum as cust_main_custnum', + 'cust_main.custnum AS cust_main_custnum', FS::UI::Web::cust_sql_fields( $params->{'cust_fields'} ), @@ -2937,120 +3090,6 @@ sub bulk_change { ''; } -=item insert_reason - -Associates this package with a (suspension or cancellation) reason (see -L, possibly inserting a new reason on the fly (see -L). - -Available options are: - -=over 4 - -=item reason - -can be set to a cancellation reason (see L), either a reasonnum of an existing reason, or passing a hashref will create a new reason. The hashref should have the following keys: typenum - Reason type (see L, reason - Text of the new reason. - -=item reason_otaker - -the access_user (see L) providing the reason - -=item date - -a unix timestamp - -=item action - -the action (cancel, susp, adjourn, expire) associated with the reason - -=back - -If there is an error, returns the error, otherwise returns false. - -=cut - -sub insert_reason { - my ($self, %options) = @_; - - my $otaker = $options{reason_otaker} || - $FS::CurrentUser::CurrentUser->username; - - my $reasonnum; - if ( $options{'reason'} =~ /^(\d+)$/ ) { - - $reasonnum = $1; - - } elsif ( ref($options{'reason'}) ) { - - return 'Enter a new reason (or select an existing one)' - unless $options{'reason'}->{'reason'} !~ /^\s*$/; - - my $reason = new FS::reason({ - 'reason_type' => $options{'reason'}->{'typenum'}, - 'reason' => $options{'reason'}->{'reason'}, - }); - my $error = $reason->insert; - return $error if $error; - - $reasonnum = $reason->reasonnum; - - } else { - return "Unparsable reason: ". $options{'reason'}; - } - - my $cust_pkg_reason = - new FS::cust_pkg_reason({ 'pkgnum' => $self->pkgnum, - 'reasonnum' => $reasonnum, - 'otaker' => $otaker, - 'action' => substr(uc($options{'action'}),0,1), - 'date' => $options{'date'} - ? $options{'date'} - : time, - }); - - $cust_pkg_reason->insert; -} - -=item set_usage USAGE_VALUE_HASHREF - -USAGE_VALUE_HASHREF is a hashref of svc_acct usage columns and the amounts -to which they should be set (see L). Currently seconds, -upbytes, downbytes, and totalbytes are appropriate keys. - -All svc_accts which are part of this package have their values reset. - -=cut - -sub set_usage { - my ($self, $valueref, %opt) = @_; - - foreach my $cust_svc ($self->cust_svc){ - my $svc_x = $cust_svc->svc_x; - $svc_x->set_usage($valueref, %opt) - if $svc_x->can("set_usage"); - } -} - -=item recharge USAGE_VALUE_HASHREF - -USAGE_VALUE_HASHREF is a hashref of svc_acct usage columns and the amounts -to which they should be set (see L). Currently seconds, -upbytes, downbytes, and totalbytes are appropriate keys. - -All svc_accts which are part of this package have their values incremented. - -=cut - -sub recharge { - my ($self, $valueref) = @_; - - foreach my $cust_svc ($self->cust_svc){ - my $svc_x = $cust_svc->svc_x; - $svc_x->recharge($valueref) - if $svc_x->can("recharge"); - } -} - =back =head1 BUGS diff --git a/FS/FS/cust_pkg_discount.pm b/FS/FS/cust_pkg_discount.pm index 76118ad92..87f8c5283 100644 --- a/FS/FS/cust_pkg_discount.pm +++ b/FS/FS/cust_pkg_discount.pm @@ -2,7 +2,7 @@ package FS::cust_pkg_discount; use strict; use base qw( FS::Record ); -use FS::Record; # qw( qsearch qsearchs ); +use FS::Record qw( qsearchs ); # qsearch ); use FS::cust_pkg; use FS::discount; @@ -131,6 +131,24 @@ sub check { $self->SUPER::check; } +=item cust_pkg + +=cut + +sub cust_pkg { + my $self = shift; + qsearchs('cust_pkg', { 'pkgnum' => $self->pkgnum } ); +} + +=item discount + +=cut + +sub discount { + my $self = shift; + qsearchs('discount', { 'discountnum' => $self->discountnum } ); +} + =back =head1 BUGS diff --git a/httemplate/edit/process/quick-cust_pkg.cgi b/httemplate/edit/process/quick-cust_pkg.cgi index 7a0f08280..c60156746 100644 --- a/httemplate/edit/process/quick-cust_pkg.cgi +++ b/httemplate/edit/process/quick-cust_pkg.cgi @@ -53,6 +53,7 @@ my $cust_pkg = new FS::cust_pkg { ? str2time($cgi->param('start_date')) : '' ), + 'discountnum' => scalar($cgi->param('discountnum')), 'refnum' => $refnum, 'locationnum' => $locationnum, }; diff --git a/httemplate/elements/select-discount.html b/httemplate/elements/select-discount.html new file mode 100644 index 000000000..e0b6e6cbe --- /dev/null +++ b/httemplate/elements/select-discount.html @@ -0,0 +1,20 @@ +<% include( '/elements/select-table.html', + 'table' => 'discount', + 'name_col' => 'description', + 'order_by' => 'ORDER BY discountnum', #XXX weight + 'value' => $discountnum, + 'empty_label' => '(none)', + 'hashref' => { 'disabled' => '' }, + %opt, + ) +%> +<%init> + +my %opt = @_; +my $discountnum = $opt{'curr_value'} || $opt{'value'}; + +$opt{'records'} = delete $opt{'discount'} + if $opt{'discount'}; + + + diff --git a/httemplate/elements/tr-select-discount.html b/httemplate/elements/tr-select-discount.html new file mode 100644 index 000000000..4ff8d1357 --- /dev/null +++ b/httemplate/elements/tr-select-discount.html @@ -0,0 +1,27 @@ +% if ( scalar(@{ $opt{'discount'} }) == 0 ) { + + + +% } else { + + + <% $opt{'label'} || 'Discount' %> + + <% include( '/elements/select-discount.html', + 'curr_value' => $discountnum, + %opt, + ) + %> + + + +% } +<%init> + +my %opt = @_; +my $discountnum = $opt{'curr_value'} || $opt{'value'}; + +$opt{'discount'} ||= [ qsearch( 'discount', { disabled=>'' } ) ]; + + + diff --git a/httemplate/misc/order_pkg.html b/httemplate/misc/order_pkg.html index a7571ca58..684f94e7c 100644 --- a/httemplate/misc/order_pkg.html +++ b/httemplate/misc/order_pkg.html @@ -60,6 +60,10 @@ }); +% if ( $curuser->access_right('Discount customer package') ) { + <% include('/elements/tr-select-discount.html') %> +% } + % if ( $conf->exists('pkg_referral') ) { <% include('/elements/tr-select-part_referral.html', 'curr_value' => scalar( $cgi->param('refnum') ), #get rid of empty_label first# || $cust_main->refnum, @@ -86,8 +90,10 @@ <%init> +my $curuser = $FS::CurrentUser::CurrentUser; + die "access denied" - unless $FS::CurrentUser::CurrentUser->access_right('Order customer package'); + unless $curuser->access_right('Order customer package'); my $conf = new FS::Conf; diff --git a/httemplate/view/cust_main/packages/package.html b/httemplate/view/cust_main/packages/package.html index 280a01682..33bcd2ad4 100644 --- a/httemplate/view/cust_main/packages/package.html +++ b/httemplate/view/cust_main/packages/package.html @@ -1,4 +1,4 @@ - +
diff --git a/httemplate/view/cust_main/packages/status.html b/httemplate/view/cust_main/packages/status.html index 6667a554d..40d6438f3 100644 --- a/httemplate/view/cust_main/packages/status.html +++ b/httemplate/view/cust_main/packages/status.html @@ -42,6 +42,8 @@ ) %> + <% pkg_status_row_discount( $cust_pkg, %opt, 'colspan'=>$colspan ) %> + % unless ( $cust_pkg->get('setup') ) { <% pkg_status_row_colspan( $cust_pkg, 'Never billed', '', 'colspan'=>$colspan, %opt ) %> % } else { @@ -70,10 +72,12 @@ % % unless ( $cust_pkg->get('setup') ) { #not setup % -% unless ( $part_pkg->freq ) { +% unless ( $part_pkg->freq ) { <% pkg_status_row_colspan( $cust_pkg, 'Not yet billed (one-time charge)', '', 'colspan'=>$colspan, %opt ) %> + <% pkg_status_row_discount( $cust_pkg, %opt, 'colspan'=>$colspan ) %> + <% pkg_status_row_if( $cust_pkg, ( $part_pkg->freq ? 'Start billing' : 'Bill on' ), @@ -94,7 +98,9 @@ % } else { - <% pkg_status_row_colspan($cust_pkg, "Not yet billed ($billed_or_prepaid ". myfreq($part_pkg). ')', '', 'colspan'=>$colspan, %opt ) %> + <% pkg_status_row_colspan($cust_pkg, "Not yet billed ($billed_or_prepaid ". myfreq($part_pkg). ')', '', 'colspan'=>$colspan, %opt ) %> + + <% pkg_status_row_discount( $cust_pkg, %opt, 'colspan'=>$colspan ) %> <% pkg_status_row_if($cust_pkg, 'Start billing', 'start_date', %opt) %> @@ -108,6 +114,8 @@ <% pkg_status_row($cust_pkg, 'Billed', 'setup', %opt) %> + <% pkg_status_row_discount( $cust_pkg, %opt, 'colspan'=>$colspan ) %> + % } else { % % if (scalar($cust_pkg->overlimit)) { @@ -130,6 +138,8 @@ %> % } + <% pkg_status_row_discount( $cust_pkg, %opt, 'colspan'=>$colspan ) %> + <% pkg_status_row($cust_pkg, 'Setup', 'setup', %opt) %> % } @@ -273,12 +283,37 @@ sub pkg_status_row_changed { 'size' => '-1', 'align' => 'right', 'colspan' => $opt{'colspan'}, + #%opt, ); } $html; } +sub pkg_status_row_discount { + my( $cust_pkg, %opt ) = @_; + + my $html; + + foreach my $cust_pkg_discount ( $cust_pkg->cust_pkg_discount_active ) { + + my $discount = $cust_pkg_discount->discount; + + my $label = 'Discount: '. $discount->description; + $label .= ' ('. ( $discount->months - $cust_pkg_discount->months_used ). + ' months remaining)' + if $discount->months; + + $html .= pkg_status_row_colspan( $cust_pkg, $label, '', + 'colspan' => $opt{'colspan'}, + #%opt, + ); + + } + + $html; +} + sub pkg_status_row_colspan { my($cust_pkg, $title, $addl, %opt) = @_; -- 2.11.0