# for modify_charge
use FS::cust_credit;
+use Data::Dumper;
+
# temporary fix; remove this once (un)suspend admin notices are cleaned up
use FS::Misc qw(send_email);
our $cache_enabled = 0;
+our $disable_start_on_hold = 0;
+
sub _simplecache {
my( $self, $hashref ) = @_;
if ( $cache_enabled && $hashref->{'pkg'} && $hashref->{'plan'} ) {
$self->start_date( timelocal_nocheck(0,0,0,1,$mon,$year) );
}
- if ($self->susp eq 'now' or $part_pkg->start_on_hold) {
+ if ( $self->susp eq 'now'
+ or ( $part_pkg->start_on_hold && ! $disable_start_on_hold )
+ )
+ {
# if the package was ordered on hold:
# - suspend it
# - don't set the start date (it will be started manually)
$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 $error = '';
- if ( $msgnum ) {
- my $msg_template = qsearchs('msg_template', { msgnum => $msgnum });
- $error = $msg_template->send( 'cust_main' => $self->cust_main,
- 'object' => $self );
- }
+ my $cust_main = $self->cust_main;
+
+ my @invoicing_list = $cust_main->invoicing_list_emailonly;
+ my $msgnum = $conf->config('cancel_msgnum', $cust_main->agentnum);
+ if ( !$options{'quiet'}
+ && $conf->config_bool('emailcancel', $cust_main->agentnum)
+ && @invoicing_list
+ && $msgnum
+ )
+ {
+ my $msg_template = qsearchs('msg_template', { msgnum => $msgnum });
+ my $error = $msg_template->send(
+ 'cust_main' => $cust_main,
+ 'object' => $self,
+ );
+ #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?
}
$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->{$_});
}
$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;
'';
}
-
-
-use Data::Dumper;
sub process_bulk_cust_pkg {
my $job = shift;
my $param = shift;
$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 = ();
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];
} else {
$remaining++;
}
+
} else {
$remaining++
}
+
if ( $error ) {
my @label = $cust_svc->label;
return "$label[0] $label[1]: $error";
}
+
}
return $remaining;
}
return %email;
}
+# Used by FS::Upgrade to migrate to a new database.
+sub _upgrade_schema { # class method
+ my ($class, %opts) = @_;
+
+ my $sql = '
+ UPDATE cust_pkg SET change_to_pkgnum = NULL
+ WHERE change_to_pkgnum IS NOT NULL
+ AND NOT EXISTS ( SELECT 1 FROM cust_pkg AS ctcp
+ WHERE ctcp.pkgnum = cust_pkg.change_to_pkgnum
+ )
+ ';
+
+ my $sth = dbh->prepare($sql) or die dbh->errstr;
+ $sth->execute or die $sth->errstr;
+ '';
+}
+
# Used by FS::Upgrade to migrate to a new database.
sub _upgrade_data { # class method
my ($class, %opts) = @_;
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;
+ }
+
}
=back