X-Git-Url: http://git.freeside.biz/gitweb/?p=freeside.git;a=blobdiff_plain;f=FS%2FFS%2Fcust_pkg.pm;h=eaadd95db3e499e3a162b6dcf89b65a7b67deaa8;hp=8eb4ce308060ce6f1268d9ea54fe5e0d8c236969;hb=94fefd033f726f9810bdaa905465af435d875a2f;hpb=f054f4db2a70c60b3adbaaa1672eefcddd62d96e diff --git a/FS/FS/cust_pkg.pm b/FS/FS/cust_pkg.pm index 8eb4ce308..eaadd95db 100644 --- a/FS/FS/cust_pkg.pm +++ b/FS/FS/cust_pkg.pm @@ -106,6 +106,8 @@ FS::cust_pkg - Object methods for cust_pkg objects $seconds = $record->seconds_since($timestamp); + #bulk cancel+order... perhaps slightly deprecated, only used by the bulk + # cancel+order in the web UI and nowhere else (edit/process/cust_pkg.cgi) $error = FS::cust_pkg::order( $custnum, \@pkgparts ); $error = FS::cust_pkg::order( $custnum, \@pkgparts, \@remove_pkgnums ] ); @@ -785,9 +787,9 @@ 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. -=item delay_cancel - for internal use, to allow proper handling of -supplemental packages when the main package is flagged to suspend -before cancelling +=item no_delay_cancel - prevents delay_cancel behavior +no matter what other options say, for use when changing packages (or any +other time you're really sure you want an immediate cancel) =back @@ -795,6 +797,12 @@ If there is an error, returns the error, otherwise returns false. =cut +#NOT DOCUMENTING - this should only be used when calling recursively +#=item delay_cancel - for internal use, to allow proper handling of +#supplemental packages when the main package is flagged to suspend +#before cancelling, probably shouldn't be used otherwise (set the +#corresponding package option instead) + sub cancel { my( $self, %options ) = @_; my $error; @@ -828,9 +836,10 @@ sub cancel { my $date = $options{'date'} if $options{'date'}; # expire/cancel later $date = '' if ($date && $date <= $cancel_time); # complain instead? - my $delay_cancel = $options{'delay_cancel'}; + my $delay_cancel = $options{'no_delay_cancel'} ? 0 : $options{'delay_cancel'}; if ( !$date && $self->part_pkg->option('delay_cancel',1) && (($self->status eq 'active') || ($self->status eq 'suspended')) + && !$options{'no_delay_cancel'} ) { my $expdays = $conf->config('part_pkg-delay_cancel-days') || 1; my $expsecs = 60*60*24*$expdays; @@ -931,7 +940,7 @@ sub cancel { $error = $new->replace( $self, options => { $self->options } ); if ( $self->change_to_pkgnum ) { my $change_to = FS::cust_pkg->by_key($self->change_to_pkgnum); - $error ||= $change_to->cancel || $change_to->delete; + $error ||= $change_to->cancel('no_delay_cancel' => 1) || $change_to->delete; } if ( $error ) { $dbh->rollback if $oldAutoCommit; @@ -1325,6 +1334,7 @@ sub suspend { if $error; } + my $cust_pkg_reason; if ( $options{'reason'} ) { $error = $self->insert_reason( 'reason' => $options{'reason'}, 'action' => $date ? 'adjourn' : 'suspend', @@ -1335,6 +1345,11 @@ sub suspend { dbh->rollback if $oldAutoCommit; return "Error inserting cust_pkg_reason: $error"; } + $cust_pkg_reason = qsearchs('cust_pkg_reason', { + 'date' => $date ? $date : $suspend_time, + 'action' => $date ? 'A' : 'S', + 'pkgnum' => $self->pkgnum, + }); } # if a reasonnum was passed, get the actual reason object so we can check @@ -1415,6 +1430,27 @@ sub suspend { } } + # suspension fees: if there is a feepart, and it's not an unsuspend fee, + # and this is not a suspend-before-cancel + if ( $cust_pkg_reason ) { + my $reason_obj = $cust_pkg_reason->reason; + if ( $reason_obj->feepart and + ! $reason_obj->fee_on_unsuspend and + ! $options{'from_cancel'} ) { + + # register the need to charge a fee, cust_main->bill will do the rest + warn "registering suspend fee: pkgnum ".$self->pkgnum.", feepart ".$reason->feepart."\n" + if $DEBUG; + my $cust_pkg_reason_fee = FS::cust_pkg_reason_fee->new({ + 'pkgreasonnum' => $cust_pkg_reason->num, + 'pkgnum' => $self->pkgnum, + 'feepart' => $reason->feepart, + 'nextbill' => $reason->fee_hold, + }); + $error ||= $cust_pkg_reason_fee->insert; + } + } + my $conf = new FS::Conf; if ( $conf->config('suspend_email_admin') && !$options{'from_cancel'} ) { @@ -1712,23 +1748,39 @@ sub unsuspend { my $unsusp_pkg; - if ( $reason && $reason->unsuspend_pkgpart ) { - my $part_pkg = FS::part_pkg->by_key($reason->unsuspend_pkgpart) - or $error = "Unsuspend package definition ".$reason->unsuspend_pkgpart. - " not found."; - my $start_date = $self->cust_main->next_bill_date - if $reason->unsuspend_hold; - - if ( $part_pkg ) { - $unsusp_pkg = FS::cust_pkg->new({ - 'custnum' => $self->custnum, - 'pkgpart' => $reason->unsuspend_pkgpart, - 'start_date' => $start_date, - 'locationnum' => $self->locationnum, - # discount? probably not... + if ( $reason ) { + if ( $reason->unsuspend_pkgpart ) { + warn "Suspend reason '".$reason->reason."' uses deprecated unsuspend_pkgpart feature.\n"; + my $part_pkg = FS::part_pkg->by_key($reason->unsuspend_pkgpart) + or $error = "Unsuspend package definition ".$reason->unsuspend_pkgpart. + " not found."; + my $start_date = $self->cust_main->next_bill_date + if $reason->unsuspend_hold; + + if ( $part_pkg ) { + $unsusp_pkg = FS::cust_pkg->new({ + 'custnum' => $self->custnum, + 'pkgpart' => $reason->unsuspend_pkgpart, + 'start_date' => $start_date, + 'locationnum' => $self->locationnum, + # discount? probably not... + }); + + $error ||= $self->cust_main->order_pkg( 'cust_pkg' => $unsusp_pkg ); + } + } + # new way, using fees + if ( $reason->feepart and $reason->fee_on_unsuspend ) { + # register the need to charge a fee, cust_main->bill will do the rest + warn "registering unsuspend fee: pkgnum ".$self->pkgnum.", feepart ".$reason->feepart."\n" + if $DEBUG; + my $cust_pkg_reason_fee = FS::cust_pkg_reason_fee->new({ + 'pkgreasonnum' => $cust_pkg_reason->num, + 'pkgnum' => $self->pkgnum, + 'feepart' => $reason->feepart, + 'nextbill' => $reason->fee_hold, }); - - $error ||= $self->cust_main->order_pkg( 'cust_pkg' => $unsusp_pkg ); + $error ||= $cust_pkg_reason_fee->insert; } if ( $error ) { @@ -1915,6 +1967,40 @@ sub change { my $error; + if ( $opt->{'cust_location'} ) { + $error = $opt->{'cust_location'}->find_or_insert; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return "creating location record: $error"; + } + $opt->{'locationnum'} = $opt->{'cust_location'}->locationnum; + } + + # Before going any further here: if the package is still in the pre-setup + # state, it's safe to modify it in place. No need to charge/credit for + # partial period, transfer services, transfer usage pools, copy invoice + # details, or change any dates. + if ( ! $self->setup and ! $opt->{cust_pkg} and ! $opt->{cust_main} ) { + foreach ( qw( locationnum pkgpart quantity refnum salesnum ) ) { + if ( length($opt->{$_}) ) { + $self->set($_, $opt->{$_}); + } + } + # almost. if the new pkgpart specifies start/adjourn/expire timers, + # apply those. + if ( $opt->{'pkgpart'} and $opt->{'pkgpart'} != $self->pkgpart ) { + $self->set_initial_timers; + } + $error = $self->replace; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return "modifying package: $error"; + } else { + $dbh->commit if $oldAutoCommit; + return ''; + } + } + my %hash = (); my $time = time; @@ -1925,15 +2011,6 @@ sub change { $hash{"change_$_"} = $self->$_() foreach qw( pkgnum pkgpart locationnum ); - if ( $opt->{'cust_location'} ) { - $error = $opt->{'cust_location'}->find_or_insert; - if ( $error ) { - $dbh->rollback if $oldAutoCommit; - return "creating location record: $error"; - } - $opt->{'locationnum'} = $opt->{'cust_location'}->locationnum; - } - if ( $opt->{'cust_pkg'} ) { # treat changing to a package with a different pkgpart as a # pkgpart change (because it is) @@ -1980,6 +2057,9 @@ sub change { # 2. (more importantly) changing a package before it's billed $hash{'waive_setup'} = $self->waive_setup; + # if this package is scheduled for a future package change, preserve that + $hash{'change_to_pkgnum'} = $self->change_to_pkgnum; + my $custnum = $self->custnum; if ( $opt->{cust_main} ) { my $cust_main = $opt->{cust_main}; @@ -2026,7 +2106,9 @@ sub change { } # Transfer services and cancel old package. - + # Enforce service limits only if this is a pkgpart change. + local $FS::cust_svc::ignore_quantity; + $FS::cust_svc::ignore_quantity = 1 if $same_pkgpart; $error = $self->transfer($cust_pkg); if ($error and $error == 0) { # $old_pkg->transfer failed. @@ -2198,6 +2280,7 @@ sub change { unused_credit => $unused_credit, nobill => $keep_dates, change_custnum => ( $self->custnum != $custnum ? $custnum : '' ), + no_delay_cancel => 1, ); if ($error) { $dbh->rollback if $oldAutoCommit; @@ -2295,7 +2378,7 @@ sub change_later { $error = $self->replace || $err_or_pkg->replace || - $change_to->cancel || + $change_to->cancel('no_delay_cancel' => 1) || $change_to->delete; } else { $error = $err_or_pkg; @@ -4677,6 +4760,9 @@ sub _X_show_zero { =item order CUSTNUM, PKGPARTS_ARYREF, [ REMOVE_PKGNUMS_ARYREF [ RETURN_CUST_PKG_ARRAYREF [ REFNUM ] ] ] +Bulk cancel + order subroutine. Perhaps slightly deprecated, only used by the +bulk cancel+order in the web UI and nowhere else (edit/process/cust_pkg.cgi) + CUSTNUM is a customer (see L) PKGPARTS is a list of pkgparts specifying the the billing item definitions (see @@ -4813,7 +4899,7 @@ sub order { $dbh->rollback if $oldAutoCommit; return "Unable to transfer all services from package ".$old_pkg->pkgnum; } - $error = $old_pkg->cancel( quiet=>1 ); + $error = $old_pkg->cancel( quiet=>1, 'no_delay_cancel'=>1 ); if ($error) { $dbh->rollback; return $error;