=item change
-If set true, supresses any referral credit to a referring customer.
+If set true, supresses actions that should only be taken for new package
+orders. (Currently this includes: intro periods when delay_setup is on.)
=item options
}
}
- my $free_days = $part_pkg->option('free_days',1);
- if ( $free_days && $part_pkg->option('delay_setup',1) ) { #&& !$self->start_date
+ if ( ! $options{'change'}
+ && ( my $free_days = $part_pkg->option('free_days',1) )
+ && $part_pkg->option('delay_setup',1)
+ #&& ! $self->start_date
+ )
+ {
my ($mday,$mon,$year) = (localtime(time) )[3,4,5];
#my $start_date = ($self->start_date || timelocal(0,0,0,$mday,$mon,$year)) + 86400 * $free_days;
my $start_date = timelocal(0,0,0,$mday,$mon,$year) + 86400 * $free_days;
}
- my $error = $new->SUPER::replace($old,
- $options->{options} ? $options->{options} : ()
- );
+ my $error = $new->export_pkg_change($old)
+ || $new->SUPER::replace( $old,
+ $options->{options}
+ ? $options->{options}
+ : ()
+ );
if ( $error ) {
$dbh->rollback if $oldAutoCommit;
return $error;
}
my $svc_error = $svc_x->insert;
- if ( $svc_error && $options{svc_fatal} ) {
- $dbh->rollback if $oldAutoCommit;
- return $svc_error;
- } else {
- my $cust_svc = qsearchs('cust_svc', { 'svcnum' => $svc_x->svcnum });
- if ( $cust_svc ) {
- my $cs_error = $cust_svc->delete;
- if ( $cs_error ) {
- $dbh->rollback if $oldAutoCommit;
- return $cs_error;
+ if ( $svc_error ) {
+ if ( $options{svc_fatal} ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $svc_error;
+ } else {
+ # if we've failed to insert the svc_x object, svc_Common->insert
+ # will have removed the cust_svc already. if not, then both records
+ # were inserted but we failed for some other reason (export, most
+ # likely). in that case, report the error and delete the records.
+ push @svc_errors, $svc_error;
+ my $cust_svc = qsearchs('cust_svc', { 'svcnum' => $svc_x->svcnum });
+ if ( $cust_svc ) {
+ # except if export_insert failed, export_delete probably won't be
+ # much better
+ local $FS::svc_Common::noexport_hack = 1;
+ my $cleanup_error = $svc_x->delete; # also deletes cust_svc
+ if ( $cleanup_error ) { # and if THAT fails, then run away
+ $dbh->rollback if $oldAutoCommit;
+ return $cleanup_error;
+ }
}
- }
- }
- push @svc_errors, $svc_error if $svc_error;
- }
+ } # svc_fatal
+ } # svc_error
+ } #foreach $h_cust_svc
#these are pretty rare, but should handle them
# - dsl_device (mac addresses)
$hash{'resume'} = $resume_date;
}
+ $options{options} ||= {};
+
my $new = new FS::cust_pkg ( \%hash );
- $error = $new->replace( $self, options => { $self->options } );
+ $error = $new->replace( $self, options => { $self->options,
+ %{ $options{options} },
+ }
+ );
if ( $error ) {
$dbh->rollback if $oldAutoCommit;
return $error;
Unsuspends all services (see L<FS::cust_svc> and L<FS::part_svc>) in this
package, then unsuspends the package itself (clears the susp field and the
-adjourn field if it is in the past).
+adjourn field if it is in the past). If the suspend reason includes an
+unsuspension package, that package will be ordered.
Available options are:
}
+ my $cust_pkg_reason = $self->last_cust_pkg_reason('susp');
+ my $reason = $cust_pkg_reason ? $cust_pkg_reason->reason : '';
+
my %hash = $self->hash;
my $inactive = time - $hash{'susp'};
return $error;
}
+ 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...
+ });
+
+ $error ||= $self->cust_main->order_pkg( 'cust_pkg' => $unsusp_pkg );
+ }
+
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
if ( $conf->config('unsuspend_email_admin') ) {
my $error = send_email(
'Customer: #'. $self->custnum. ' '. $self->cust_main->name. "\n",
'Package : #'. $self->pkgnum. " (". $self->part_pkg->pkg_comment. ")\n",
( map { "Service : $_\n" } @labels ),
+ ($unsusp_pkg ?
+ "An unsuspension fee was charged: ".
+ $unsusp_pkg->part_pkg->pkg_comment."\n"
+ : ''
+ ),
],
);
qsearchs( 'cust_main', { 'custnum' => $self->custnum } );
}
+=item balance
+
+Returns the balance for this specific package, when using
+experimental package balance.
+
+=cut
+
+sub balance {
+ my $self = shift;
+ $self->cust_main->balance_pkgnum( $self->pkgnum );
+}
+
#these subs are in location_Mixin.pm now... unfortunately the POD doesn't mixin
=item cust_location
grep {
my $part_svc = $_->part_svc;
$part_svc->svcdb eq 'svc_acct'
- && scalar($part_svc->part_export('sqlradius'));
+ && scalar($part_svc->part_export_usage);
} $self->cust_svc
) {
$seconds += $cust_svc->seconds_since_sqlradacct($start, $end);
grep {
my $part_svc = $_->part_svc;
$part_svc->svcdb eq 'svc_acct'
- && scalar($part_svc->part_export('sqlradius'));
+ && scalar($part_svc->part_export_usage);
} $self->cust_svc
) {
$sum += $cust_svc->attribute_since_sqlradacct($start, $end, $attrib);
}
+=item export_pkg_change OLD_CUST_PKG
+
+Calls the "pkg_change" export action for all services attached to this package.
+
+=cut
+
+sub export_pkg_change {
+ my( $self, $old ) = ( shift, shift );
+
+ local $SIG{HUP} = 'IGNORE';
+ local $SIG{INT} = 'IGNORE';
+ local $SIG{QUIT} = 'IGNORE';
+ local $SIG{TERM} = 'IGNORE';
+ local $SIG{TSTP} = 'IGNORE';
+ local $SIG{PIPE} = 'IGNORE';
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ foreach my $svc_x ( map $_->svc_x, $self->cust_svc ) {
+ my $error = $svc_x->export('pkg_change', $self, $old);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ '';
+
+}
+
=item insert_reason
Associates this package with a (suspension or cancellation) reason (see
=item fcc_line
- boolean selects packages containing fcc form 477 telco lines
+boolean; if true, returns only packages with more than 0 FCC phone lines
+
+=item state, country
+
+Limit to packages whose customer is located in the specified state and
+country. For FCC 477 reporting. This will use the customer's service
+address if there is one, but isn't yet smart enough to use the package
+address.
=back
}
}
+ ###
+ # parse country/state
+ ###
+
+ for (qw(state country)) {
+ if ( exists($params->{$_})
+ && uc($params->{$_}) =~ /^([A-Z]{2})$/ )
+ {
+ push @where,
+ "COALESCE(cust_location.$_, cust_main.ship_$_, cust_main.$_) = '$1'";
+ }
+ }
+
+
###
# parse part_pkg
###
my $addl_from = 'LEFT JOIN cust_main USING ( custnum ) '.
'LEFT JOIN part_pkg USING ( pkgpart ) '.
- 'LEFT JOIN pkg_class ON ( part_pkg.classnum = pkg_class.classnum ) ';
+ 'LEFT JOIN pkg_class ON ( part_pkg.classnum = pkg_class.classnum ) '.
+ 'LEFT JOIN cust_location USING ( locationnum ) ';
+
+ my $select;
+ my $count_query;
+ if ( $params->{'select_zip5'} ) {
+ my $zip = 'COALESCE(cust_location.zip, cust_main.ship_zip, cust_main.zip)';
+
+ $select = "DISTINCT substr($zip,1,5) as zip";
+ $orderby = "ORDER BY substr($zip,1,5)";
+ $count_query = "SELECT COUNT( DISTINCT substr($zip,1,5) )";
+ } else {
+ $select = join(', ',
+ 'cust_pkg.*',
+ ( map "part_pkg.$_", qw( pkg freq ) ),
+ 'pkg_class.classname',
+ 'cust_main.custnum AS cust_main_custnum',
+ FS::UI::Web::cust_sql_fields(
+ $params->{'cust_fields'}
+ ),
+ );
+ $count_query = 'SELECT COUNT(*)';
+ }
- my $count_query = "SELECT COUNT(*) FROM cust_pkg $addl_from $extra_sql";
+ $count_query .= " FROM cust_pkg $addl_from $extra_sql";
my $sql_query = {
'table' => 'cust_pkg',
'hashref' => {},
- 'select' => join(', ',
- 'cust_pkg.*',
- ( map "part_pkg.$_", qw( pkg freq ) ),
- 'pkg_class.classname',
- 'cust_main.custnum AS cust_main_custnum',
- FS::UI::Web::cust_sql_fields(
- $params->{'cust_fields'}
- ),
- ),
+ 'select' => $select,
'extra_sql' => $extra_sql,
'order_by' => $orderby,
'addl_from' => $addl_from,