X-Git-Url: http://git.freeside.biz/gitweb/?a=blobdiff_plain;f=FS%2FFS%2Fcust_pkg.pm;h=324dbba437534c92b39d05212dc0e1a15609a8c0;hb=d1da7fd0b1f1f15abf3552652e2607d39c934663;hp=ce5ee118292b512737cd678cbbe4a269364414a5;hpb=7416ca26f38222c7efcf046a4507dbf30b3f565a;p=freeside.git diff --git a/FS/FS/cust_pkg.pm b/FS/FS/cust_pkg.pm index ce5ee1182..324dbba43 100644 --- a/FS/FS/cust_pkg.pm +++ b/FS/FS/cust_pkg.pm @@ -125,6 +125,10 @@ Billing item definition (see L) Optional link to package location (see L) +=item order_date + +date package was ordered (also remains same on changes) + =item start_date date @@ -271,6 +275,8 @@ sub insert { } } + $self->order_date(time); + local $SIG{HUP} = 'IGNORE'; local $SIG{INT} = 'IGNORE'; local $SIG{QUIT} = 'IGNORE'; @@ -595,6 +601,12 @@ Available options are: =item nobill - can be set true to skip billing if it might otherwise be done. +=item unused_credit - can be set to 1 to credit the remaining time, or 0 to +not credit it. This must be set (by change()) when changing the package +to a different pkgpart or location, and probably shouldn't be in any other +case. If it's not set, the 'unused_credit_cancel' part_pkg option will +be used. + =back If there is an error, returns the error, otherwise returns false. @@ -645,7 +657,6 @@ sub cancel { if $error; } - my $cancel_time = $options{'time'} || time; if ( $options{'reason'} ) { @@ -661,7 +672,8 @@ sub cancel { } my %svc; - unless ( $date ) { + if ( $date ) { +# copied from below foreach my $cust_svc ( #schwartz map { $_->[0] } @@ -669,7 +681,21 @@ sub cancel { map { [ $_, $_->svc_x->table_info->{'cancel_weight'} ]; } qsearch( 'cust_svc', { 'pkgnum' => $self->pkgnum } ) ) { + my $error = $cust_svc->cancel( ('date' => $date) ); + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return "Error expiring cust_svc: $error"; + } + } + } else { #!date + foreach my $cust_svc ( + #schwartz + map { $_->[0] } + sort { $a->[1] <=> $b->[1] } + map { [ $_, $_->svc_x->table_info->{'cancel_weight'} ]; } + qsearch( 'cust_svc', { 'pkgnum' => $self->pkgnum } ) + ) { my $error = $cust_svc->cancel; if ( $error ) { @@ -677,10 +703,28 @@ sub cancel { return "Error cancelling cust_svc: $error"; } } + } #if $date - # Add a credit for remaining service - my $remaining_value = $self->calc_remain(time=>$cancel_time); - if ( $remaining_value > 0 && !$options{'no_credit'} ) { + # Add a credit for remaining service + my $last_bill = $self->getfield('last_bill') || 0; + my $next_bill = $self->getfield('bill') || 0; + my $do_credit; + if ( exists($options{'unused_credit'}) ) { + $do_credit = $options{'unused_credit'}; + } + else { + $do_credit = $self->part_pkg->option('unused_credit_cancel', 1); + } + if ( $do_credit + and $last_bill > 0 # the package has been billed + and $next_bill > 0 # the package has a next bill date + and $next_bill >= $cancel_time # which is in the future + ) { + my $remaining_value = $self->calc_remain('time' => $cancel_time); + if ( $remaining_value > 0 ) { + # && !$options{'no_credit'} ) { + # Undocumented, unused option. + # part_pkg configuration should decide this anyway. my $error = $self->cust_main->credit( $remaining_value, 'Credit for unused time on '. $self->part_pkg->pkg, @@ -691,8 +735,8 @@ sub cancel { return "Error crediting customer \$$remaining_value for unused time on". $self->part_pkg->pkg. ": $error"; } - } - } + } #if $remaining_value + } #if $do_credit my %hash = $self->hash; $date ? ($hash{'expire'} = $date) : ($hash{'cancel'} = $cancel_time); @@ -707,7 +751,9 @@ sub cancel { return '' if $date; #no errors my @invoicing_list = grep { $_ !~ /^(POST|FAX)$/ } $self->cust_main->invoicing_list; - if ( !$options{'quiet'} && $conf->exists('emailcancel') && @invoicing_list ) { + if ( !$options{'quiet'} && + $conf->exists('emailcancel', $self->cust_main->agentnum) && + @invoicing_list ) { my $msgnum = $conf->config('cancel_msgnum', $self->cust_main->agentnum); my $error = ''; if ( $msgnum ) { @@ -1201,12 +1247,23 @@ sub change { $opt->{'locationnum'} = $opt->{'cust_location'}->locationnum; } + my $unused_credit = 0; if ( $opt->{'keep_dates'} ) { foreach my $date ( qw(setup bill last_bill susp adjourn cancel expire start_date contract_end ) ) { $hash{$date} = $self->getfield($date); } } + # Special case. If the pkgpart is changing, and the customer is + # going to be credited for remaining time, don't keep setup, bill, + # or last_bill dates, and DO pass the flag to cancel() to credit + # the customer. + if ( $opt->{'pkgpart'} + and $opt->{'pkgpart'} != $self->pkgpart + and $self->part_pkg->option('unused_credit_change', 1) ) { + $unused_credit = 1; + $hash{$_} = '' foreach qw(setup bill last_bill); + } # Create the new package. my $cust_pkg = new FS::cust_pkg { @@ -1265,8 +1322,9 @@ sub change { } } - #Good to go, cancel old package. - $error = $self->cancel( quiet=>1 ); + #Good to go, cancel old package. Notify 'cancel' of whether to credit + #remaining time. + $error = $self->cancel( quiet=>1, unused_credit => $unused_credit ); if ($error) { $dbh->rollback if $oldAutoCommit; return $error; @@ -1638,24 +1696,31 @@ sub overlimit { grep { $_->overlimit } $self->cust_svc(@_); } -=item h_cust_svc END_TIMESTAMP [ START_TIMESTAMP ] +=item h_cust_svc END_TIMESTAMP [ START_TIMESTAMP ] [ MODE ] Returns historical services for this package created before END TIMESTAMP and (optionally) not cancelled before START_TIMESTAMP, as FS::h_cust_svc objects -(see L). +(see L). If MODE is 'I' (for 'invoice'), services with the +I flag will be omitted. =cut sub h_cust_svc { my $self = shift; - - $self->_sort_cust_svc( + my ($end, $start, $mode) = @_; + my @cust_svc = $self->_sort_cust_svc( [ qsearch( 'h_cust_svc', - { 'pkgnum' => $self->pkgnum, }, - FS::h_cust_svc->sql_h_search(@_), - ) - ] + { 'pkgnum' => $self->pkgnum, }, + FS::h_cust_svc->sql_h_search(@_), + ) ] ); + if ( $mode eq 'I' ) { + my %hidden_svcpart = map { $_->svcpart => $_->hidden } $self->part_svc; + return grep { !$hidden_svcpart{$_->svcpart} } @cust_svc; + } + else { + return @cust_svc; + } } sub _sort_cust_svc { @@ -1719,6 +1784,13 @@ sub available_part_svc { my $part_svc = $_->part_svc; $part_svc->{'Hash'}{'num_avail'} = #evil encapsulation-breaking $_->quantity - $self->num_cust_svc($_->svcpart); + + # more evil encapsulation breakage + if($part_svc->{'Hash'}{'num_avail'} > 0) { + my @exports = $part_svc->part_export_did; + $part_svc->{'Hash'}{'can_get_dids'} = scalar(@exports); + } + $part_svc; } $self->part_pkg->pkg_svc; @@ -1758,6 +1830,7 @@ sub part_svc { max( 0, $pkg_svc->quantity - $num_cust_svc ); $part_svc->{'Hash'}{'cust_pkg_svc'} = $num_cust_svc ? [ $self->cust_svc($part_svc->svcpart) ] : []; + $part_svc->{'Hash'}{'hidden'} = $pkg_svc->hidden; $part_svc; } $self->part_pkg->pkg_svc; @@ -1973,11 +2046,12 @@ sub labels { map { [ $_->label ] } $self->cust_svc; } -=item h_labels END_TIMESTAMP [ START_TIMESTAMP ] +=item h_labels END_TIMESTAMP [ START_TIMESTAMP ] [ MODE ] Like the labels method, but returns historical information on services that were active as of END_TIMESTAMP and (optionally) not cancelled before -START_TIMESTAMP. +START_TIMESTAMP. If MODE is 'I' (for 'invoice'), services with the +I flag will be omitted. Returns a list of lists, calling the label method for all (historical) services (see L) of this billing item. @@ -3318,9 +3392,25 @@ sub bulk_change { sub _upgrade_data { # class method my ($class, %opts) = @_; $class->_upgrade_otaker(%opts); - my $sql =('UPDATE cust_pkg SET contract_end = NULL WHERE contract_end = -1'); - my $sth = dbh->prepare($sql); - $sth->execute or die $sth->errstr; + my @statements = ( + # RT#10139, bug resulting in contract_end being set when it shouldn't + 'UPDATE cust_pkg SET contract_end = NULL WHERE contract_end = -1', + # RT#10830, bad calculation of prorate date near end of year + # the date range for bill is December 2009, and we move it forward + # one year if it's before the previous bill date (which it should + # never be) + 'UPDATE cust_pkg SET bill = bill + (365*24*60*60) WHERE bill < last_bill + AND bill > 1259654400 AND bill < 1262332800 AND (SELECT plan FROM part_pkg + WHERE part_pkg.pkgpart = cust_pkg.pkgpart) = \'prorate\'', + # RT6628, add order_date to cust_pkg + 'update cust_pkg set order_date = (select history_date from h_cust_pkg + where h_cust_pkg.pkgnum = cust_pkg.pkgnum and + history_action = \'insert\') where order_date is null', + ); + foreach my $sql (@statements) { + my $sth = dbh->prepare($sql); + $sth->execute or die $sth->errstr; + } } =back