X-Git-Url: http://git.freeside.biz/gitweb/?a=blobdiff_plain;f=FS%2FFS%2Fcust_pkg.pm;h=c95d31b4da422480baaf076378872c38ca5636c2;hb=e122171357abcfb53719a23ffeb8345e1c88381d;hp=ee9afa3e9f8f093f06b89ca5f50d6c97dac19595;hpb=a36d5bbb21a67515e6560547669bd80fc2b3a8d9;p=freeside.git diff --git a/FS/FS/cust_pkg.pm b/FS/FS/cust_pkg.pm index ee9afa3e9..c95d31b4d 100644 --- a/FS/FS/cust_pkg.pm +++ b/FS/FS/cust_pkg.pm @@ -1048,18 +1048,23 @@ sub cancel { $dbh->commit or die $dbh->errstr if $oldAutoCommit; return '' if $date; #no errors - my @invoicing_list = grep { $_ !~ /^(POST|FAX)$/ } $self->cust_main->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 $cust_main = $self->cust_main; + + my @invoicing_list = $cust_main->invoicing_list_emailonly; + if ( !$options{'quiet'} + && $conf->config_bool('emailcancel', $cust_main->agentnum) + && @invoicing_list + ) + { + my $msgnum = $conf->config('cancel_msgnum', $cust_main->agentnum); my $error = ''; if ( $msgnum ) { my $msg_template = qsearchs('msg_template', { msgnum => $msgnum }); - $error = $msg_template->send( 'cust_main' => $self->cust_main, - 'object' => $self ); - } - else { + $error = $msg_template->send( + 'cust_main' => $cust_main, + 'object' => $self, + ); + } else { $error = send_email( 'from' => $conf->invoice_from_full( $self->cust_main->agentnum ), 'to' => \@invoicing_list, @@ -1072,6 +1077,23 @@ sub cancel { #should this do something on errors? } + my %pkg_class = map { $_=>1 } + $conf->config('cancel_msgnum-referring_cust-pkg_class'); + my $ref_msgnum = $conf->config('cancel_msgnum-referring_cust'); + if ( !$options{'quiet'} + && $cust_main->referral_custnum + && $pkg_class{ $self->classnum } + && $ref_msgnum + ) + { + my $msg_template = qsearchs('msg_template', { msgnum => $ref_msgnum }); + my $error = $msg_template->send( + 'cust_main' => $cust_main->referring_cust_main, + 'object' => $self, + ); + #should this do something on errors? + } + ''; #no errors } @@ -2341,13 +2363,15 @@ sub change { $same_pkgpart = 0; } + $self->set('waive_setup', $opt->{'waive_setup'}) if $opt->{'waive_setup'}; + # 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 usage pools, copy invoice details, or change any # dates. We DO need to "transfer" services (from the package to itself) to # check their validity on the new pkgpart. if ( ! $self->setup and ! $opt->{cust_pkg} and ! $opt->{cust_main} ) { - foreach ( qw( locationnum pkgpart quantity refnum salesnum ) ) { + foreach ( qw( locationnum pkgpart quantity refnum salesnum waive_setup ) ) { if ( length($opt->{$_}) ) { $self->set($_, $opt->{$_}); } @@ -2973,7 +2997,7 @@ sub modify_charge { $pkg_opt_modified = 1; } } - $pkg_opt_modified = 1 if (scalar(@old_additional) - 1) != $i; + $pkg_opt_modified = 1 if scalar(@old_additional) != $i; $pkg_opt{'additional_count'} = $i if $i > 0; my $old_classnum; @@ -3126,8 +3150,6 @@ sub modify_charge { ''; } - - use Storable 'thaw'; use MIME::Base64; use Data::Dumper; @@ -4360,8 +4382,10 @@ sub transfer { $target{$pkg_svc->svcpart} = $pkg_svc->quantity * ( $dest->quantity || 1 ); } - foreach my $cust_svc ($dest->cust_svc) { - $target{$cust_svc->svcpart}--; + unless ( $self->pkgnum == $dest->pkgnum ) { + foreach my $cust_svc ($dest->cust_svc) { + $target{$cust_svc->svcpart}--; + } } my %svcpart2svcparts = (); @@ -4395,24 +4419,42 @@ sub transfer { my $error; foreach my $cust_svc ($self->cust_svc) { my $svcnum = $cust_svc->svcnum; - if($target{$cust_svc->svcpart} > 0 - or $FS::cust_svc::ignore_quantity) { # maybe should be a 'force' option + + if ( $target{$cust_svc->svcpart} > 0 + or $FS::cust_svc::ignore_quantity # maybe should be a 'force' option + ) + { $target{$cust_svc->svcpart}--; + + local $FS::cust_svc::ignore_quantity = 1 + if $self->pkgnum == $dest->pkgnum; + + #why run replace at all in the $self->pkgnum == $dest->pkgnum case? + # we do want to trigger location and pkg_change exports, but + # without pkgnum changing from an old to new package, cust_svc->replace + # doesn't know how to trigger those. :/ + # does this mean we scrap the whole idea of "safe to modify it in place", + # or do we special-case and pass the info needed to cust_svc->replace? :/ + my $new = new FS::cust_svc { $cust_svc->hash }; $new->pkgnum($dest_pkgnum); $error = $new->replace($cust_svc); + } elsif ( exists $opt{'change_svcpart'} && $opt{'change_svcpart'} ) { + if ( $DEBUG ) { warn "looking for alternates for svcpart ". $cust_svc->svcpart. "\n"; warn "alternates to consider: ". join(', ', @{$svcpart2svcparts{$cust_svc->svcpart}}). "\n"; } + my @alternate = grep { warn "considering alternate svcpart $_: ". "$target{$_} available in new package\n" if $DEBUG; $target{$_} > 0; } @{$svcpart2svcparts{$cust_svc->svcpart}}; + if ( @alternate ) { warn "alternate(s) found\n" if $DEBUG; my $change_svcpart = $alternate[0]; @@ -4424,13 +4466,16 @@ sub transfer { } else { $remaining++; } + } else { $remaining++ } + if ( $error ) { my @label = $cust_svc->label; return "service $label[1]: $error"; } + } return $remaining; } @@ -5345,7 +5390,7 @@ sub search { } ### - # parse refnum (advertising source) + # parse (customer) refnum (advertising source) ### if ( exists($params->{'refnum'}) ) { @@ -5356,7 +5401,7 @@ sub search { @refnum = ( $params->{'refnum'} ); } my $in = join(',', grep /^\d+$/, @refnum); - push @where, "refnum IN($in)" if length $in; + push @where, "cust_main.refnum IN($in)" if length $in; } ### @@ -5535,29 +5580,38 @@ sub search { foreach my $field (qw( setup last_bill bill adjourn susp expire contract_end change_date cancel )) { - next unless exists($params->{$field}); + if ( $params->{$field.'_null'} ) { + + push @where, "cust_pkg.$field IS NULL"; + # this should surely be obsoleted by now: OR cust_pkg.$field == 0 - my($beginning, $ending) = @{$params->{$field}}; + } else { - next if $beginning == 0 && $ending == 4294967295; + next unless exists($params->{$field}); - push @where, - "cust_pkg.$field IS NOT NULL", - "cust_pkg.$field >= $beginning", - "cust_pkg.$field <= $ending"; - - $orderby ||= "ORDER BY cust_pkg.$field"; - - if ( $field eq 'setup' ) { - $exclude_change_from = 1; - } elsif ( $field eq 'cancel' ) { - $exclude_change_to = 1; - } elsif ( $field eq 'change_date' ) { - # if we are given setup and change_date ranges, and the setup date - # falls in _both_ ranges, then include the package whether it was - # a change or not - $exclude_change_from = 0; + my($beginning, $ending) = @{$params->{$field}}; + + next if $beginning == 0 && $ending == 4294967295; + + push @where, + "cust_pkg.$field IS NOT NULL", + "cust_pkg.$field >= $beginning", + "cust_pkg.$field <= $ending"; + + $orderby ||= "ORDER BY cust_pkg.$field"; + + if ( $field eq 'setup' ) { + $exclude_change_from = 1; + } elsif ( $field eq 'cancel' ) { + $exclude_change_to = 1; + } elsif ( $field eq 'change_date' ) { + # if we are given setup and change_date ranges, and the setup date + # falls in _both_ ranges, then include the package whether it was + # a change or not + $exclude_change_from = 0; + } } + } if ($exclude_change_from) { @@ -5570,6 +5624,7 @@ sub search { WHERE cust_pkg.pkgnum = changed_to_pkg.change_pkgnum )"; } + } $orderby ||= 'ORDER BY bill'; @@ -6209,6 +6264,32 @@ sub _upgrade_data { # class method my $error = $part_pkg_link->remove_linked; die $error if $error; } + + # RT#73607: canceling a package with billing addons sometimes changes its + # pkgpart. + # Find records where the last replace_new record for the package before it + # was canceled has a different pkgpart from the package itself. + my @cust_pkg = qsearch({ + 'table' => 'cust_pkg', + 'select' => 'cust_pkg.*, h_cust_pkg.pkgpart AS h_pkgpart', + 'addl_from' => ' JOIN ( + SELECT pkgnum, MAX(historynum) AS historynum FROM h_cust_pkg + WHERE cancel IS NULL + AND history_action = \'replace_new\' + GROUP BY pkgnum + ) AS last_history USING (pkgnum) + JOIN h_cust_pkg USING (historynum)', + 'extra_sql' => ' WHERE cust_pkg.cancel is not null + AND cust_pkg.pkgpart != h_cust_pkg.pkgpart' + }); + foreach my $cust_pkg ( @cust_pkg ) { + my $pkgnum = $cust_pkg->pkgnum; + warn "fixing pkgpart on canceled pkg#$pkgnum\n"; + $cust_pkg->set('pkgpart', $cust_pkg->h_pkgpart); + my $error = $cust_pkg->replace; + die $error if $error; + } + } # will autoload in v4+