X-Git-Url: http://git.freeside.biz/gitweb/?a=blobdiff_plain;f=FS%2FFS%2Fcust_main%2FPackages.pm;h=a7418f7f0ce31c3054cf1f7f8e48ae580b648b16;hb=4ad88e4863417a9004b991ffbaffa05d520bf1e9;hp=316ae37506799c6ca8f831426bb6379a7bdf165e;hpb=4bbf4035783020c31df0358980ca54de5893851d;p=freeside.git diff --git a/FS/FS/cust_main/Packages.pm b/FS/FS/cust_main/Packages.pm index 316ae3750..a7418f7f0 100644 --- a/FS/FS/cust_main/Packages.pm +++ b/FS/FS/cust_main/Packages.pm @@ -1,15 +1,15 @@ package FS::cust_main::Packages; use strict; -use vars qw( $DEBUG $me ); use List::Util qw( min ); use FS::UID qw( dbh ); -use FS::Record qw( qsearch ); +use FS::Record qw( qsearch qsearchs ); use FS::cust_pkg; use FS::cust_svc; +use FS::contact; # for attach_pkgs +use FS::cust_location; # -$DEBUG = 0; -$me = '[FS::cust_main::Packages]'; +our ($DEBUG, $me) = (0, '[FS::cust_main::Packages]'); =head1 NAME @@ -17,7 +17,7 @@ FS::cust_main::Packages - Packages mixin for cust_main =head1 SYNOPSIS -=head1 DESRIPTION +=head1 DESCRIPTION These methods are available on FS::cust_main objects; @@ -29,6 +29,9 @@ These methods are available on FS::cust_main objects; Orders a single package. +Note that if the package definition has supplemental packages, those will +be ordered as well. + Options may be passed as a list of key/value pairs or as a hash reference. Options are: @@ -40,7 +43,8 @@ FS::cust_pkg object =item cust_location -Optional FS::cust_location object +Optional FS::cust_location object. If not specified, the customer's +ship_location will be used. =item svcs @@ -57,7 +61,7 @@ action completes (such as running the customer's credit card successfully). Optional subject for a ticket created and attached to this customer -=item ticket_subject +=item ticket_queue Optional queue name for ticket additions @@ -83,7 +87,7 @@ sub order_pkg { if exists($opt->{'depend_jobnum'}) && $opt->{'depend_jobnum'}; my %insert_params = map { $opt->{$_} ? ( $_ => $opt->{$_} ) : () } - qw( ticket_subject ticket_queue ); + qw( ticket_subject ticket_queue allow_pkgpart import ); local $SIG{HUP} = 'IGNORE'; local $SIG{INT} = 'IGNORE'; @@ -96,14 +100,45 @@ sub order_pkg { local $FS::UID::AutoCommit = 0; my $dbh = dbh; - if ( $opt->{'cust_location'} && - ( ! $cust_pkg->locationnum || $cust_pkg->locationnum == -1 ) ) { - my $error = $opt->{'cust_location'}->insert; + if ( $opt->{'contactnum'} and $opt->{'contactnum'} != -1 ) { + + $cust_pkg->contactnum($opt->{'contactnum'}); + + } elsif ( $opt->{'contact'} ) { + + if ( ! $opt->{'contact'}->contactnum ) { + # not inserted yet + my $error = $opt->{'contact'}->insert; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return "inserting contact (transaction rolled back): $error"; + } + } + $cust_pkg->contactnum($opt->{'contact'}->contactnum); + + #} else { + # + # $cust_pkg->contactnum(); + + } + + if ( $opt->{'locationnum'} and $opt->{'locationnum'} != -1 ) { + + $cust_pkg->locationnum($opt->{'locationnum'}); + + } elsif ( $opt->{'cust_location'} ) { + + my $error = $opt->{'cust_location'}->find_or_insert; if ( $error ) { $dbh->rollback if $oldAutoCommit; return "inserting cust_location (transaction rolled back): $error"; } $cust_pkg->locationnum($opt->{'cust_location'}->locationnum); + + } else { + + $cust_pkg->locationnum($self->ship_locationnum); + } $cust_pkg->custnum( $self->custnum ); @@ -137,12 +172,40 @@ sub order_pkg { } } + # add supplemental packages, if any are needed + my $part_pkg = FS::part_pkg->by_key($cust_pkg->pkgpart); + foreach my $link ($part_pkg->supp_part_pkg_link) { + #warn "inserting supplemental package ".$link->dst_pkgpart; + my $pkg = FS::cust_pkg->new({ + 'pkgpart' => $link->dst_pkgpart, + 'pkglinknum' => $link->pkglinknum, + 'custnum' => $self->custnum, + 'main_pkgnum' => $cust_pkg->pkgnum, + # try to prevent as many surprises as possible + 'pkgbatch' => $cust_pkg->pkgbatch, + 'start_date' => $cust_pkg->start_date, + 'order_date' => $cust_pkg->order_date, + 'expire' => $cust_pkg->expire, + 'adjourn' => $cust_pkg->adjourn, + 'contract_end' => $cust_pkg->contract_end, + 'refnum' => $cust_pkg->refnum, + 'discountnum' => $cust_pkg->discountnum, + 'waive_setup' => $cust_pkg->waive_setup, + 'allow_pkgpart' => $opt->{'allow_pkgpart'}, + }); + $error = $self->order_pkg('cust_pkg' => $pkg, + 'locationnum' => $cust_pkg->locationnum); + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return "inserting supplemental package: $error"; + } + } + $dbh->commit or die $dbh->errstr if $oldAutoCommit; ''; #no error } -#deprecated #=item order_pkgs HASHREF [ , SECONDSREF ] [ , OPTION => VALUE ... ] =item order_pkgs HASHREF [ , OPTION => VALUE ... ] Like the insert method on an existing record, this method orders multiple @@ -163,7 +226,8 @@ Services can be new, in which case they are inserted, or existing unaudited services, in which case they are linked to the newly-created package. Currently available options are: I, I, I, -I, I, and I. +I, I, I, I, and +I. If I is set, all provisioning jobs will have a dependancy on the supplied jobnum (they will not run until the specific job completes). @@ -180,14 +244,14 @@ If I, I, I, or I is provided, the scalars (provided by references) will be incremented by the values of the prepaid card.` +I and I are flags passed to L->insert. + =cut sub order_pkgs { my $self = shift; my $cust_pkgs = shift; - my $seconds_ref = ref($_[0]) ? shift : ''; #deprecated my %options = @_; - $seconds_ref ||= $options{'seconds_ref'}; local($DEBUG) = $FS::cust_main::DEBUG if $FS::cust_main::DEBUG > $DEBUG; @@ -213,10 +277,8 @@ sub order_pkgs { my $error = $self->order_pkg( 'cust_pkg' => $cust_pkg, 'svcs' => $cust_pkgs->{$cust_pkg}, - 'seconds_ref' => $seconds_ref, - map { $_ => $options{$_} } qw( upbytes_ref downbytes_ref totalbytes_ref - depend_jobnum - ) + map { $_ => $options{$_} } + qw( seconds_ref upbytes_ref downbytes_ref totalbytes_ref depend_jobnum allow_pkgpart import ) ); if ( $error ) { $dbh->rollback if $oldAutoCommit; @@ -229,6 +291,108 @@ sub order_pkgs { ''; #no error } +=item attach_pkgs + +Merges this customer's package's into the target customer and then cancels them. + +=cut + +sub attach_pkgs { + my( $self, $new_custnum ) = @_; + + #mostly false laziness w/ merge + + return "Can't attach packages to self" if $self->custnum == $new_custnum; + + my $new_cust_main = qsearchs( 'cust_main', { 'custnum' => $new_custnum } ) + or return "Invalid new customer number: $new_custnum"; + + return 'Access denied: "Merge customer across agents" access right required to merge into a customer of a different agent' + if $self->agentnum != $new_cust_main->agentnum + && ! $FS::CurrentUser::CurrentUser->access_right('Merge customer across agents'); + + 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; + + if ( qsearch('agent', { 'agent_custnum' => $self->custnum } ) ) { + $dbh->rollback if $oldAutoCommit; + return "Can't merge a master agent customer"; + } + + #use FS::access_user + if ( qsearch('access_user', { 'user_custnum' => $self->custnum } ) ) { + $dbh->rollback if $oldAutoCommit; + return "Can't merge a master employee customer"; + } + + if ( qsearch('cust_pay_pending', { 'custnum' => $self->custnum, + 'status' => { op=>'!=', value=>'done' }, + } + ) + ) { + $dbh->rollback if $oldAutoCommit; + return "Can't merge a customer with pending payments"; + } + + #end of false laziness + + #pull in contact + + my %contact_hash = ( 'first' => $self->first, + 'last' => $self->get('last'), + 'custnum' => $new_custnum, + 'disabled' => '', + ); + + my $contact = qsearchs( 'contact', \%contact_hash) + || new FS::contact \%contact_hash; + unless ( $contact->contactnum ) { + my $error = $contact->insert; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; + } + } + + foreach my $cust_pkg ( $self->ncancelled_pkgs ) { + + my $cust_location = $cust_pkg->cust_location || $self->ship_location; + my %loc_hash = $cust_location->hash; + $loc_hash{'locationnum'} = ''; + $loc_hash{'custnum'} = $new_custnum; + $loc_hash{'disabled'} = ''; + my $new_cust_location = qsearchs( 'cust_location', \%loc_hash) + || new FS::cust_location \%loc_hash; + + my $pkg_or_error = $cust_pkg->change( { + 'keep_dates' => 1, + 'cust_main' => $new_cust_main, + 'contactnum' => $contact->contactnum, + 'cust_location' => $new_cust_location, + } ); + + my $error = ref($pkg_or_error) ? '' : $pkg_or_error; + + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; + } + + } + + $dbh->commit or die $dbh->errstr if $oldAutoCommit; + ''; #no error + +} + =item all_pkgs [ OPTION => VALUE... | EXTRA_QSEARCH_PARAMS_HASHREF ] Returns all packages (see L) for this customer. @@ -356,6 +520,7 @@ Returns all suspended packages (see L) for this customer. sub suspended_pkgs { my $self = shift; + return $self->num_suspended_pkgs unless wantarray; grep { $_->susp } $self->ncancelled_pkgs; } @@ -382,19 +547,55 @@ this customer. sub unsuspended_pkgs { my $self = shift; + return $self->num_unsuspended_pkgs unless wantarray; grep { ! $_->susp } $self->ncancelled_pkgs; } +=item active_pkgs + +Returns all unsuspended (and uncancelled) packages (see L) for +this customer that are active (recurring). + +=cut + +sub active_pkgs { + my $self = shift; + grep { my $part_pkg = $_->part_pkg; + $part_pkg->freq ne '' && $part_pkg->freq ne '0'; + } + $self->unsuspended_pkgs; +} + +=item billing_pkgs + +Returns active packages, and also any suspended packages which are set to +continue billing while suspended. + +=cut + +sub billing_pkgs { + my $self = shift; + grep { my $part_pkg = $_->part_pkg; + $part_pkg->freq ne '' && $part_pkg->freq ne '0' + && ( ! $_->susp || $_->option('suspend_bill',1) + || ( $part_pkg->option('suspend_bill', 1) + && ! $_->option('no_suspend_bill',1) + ) + ); + } + $self->ncancelled_pkgs; +} + =item next_bill_date Returns the next date this customer will be billed, as a UNIX timestamp, or -undef if no active package has a next bill date. +undef if no billing package has a next bill date. =cut sub next_bill_date { my $self = shift; - min( map $_->get('bill'), grep $_->get('bill'), $self->unsuspended_pkgs ); + min( map $_->get('bill'), grep $_->get('bill'), $self->billing_pkgs ); } =item num_cancelled_pkgs @@ -412,6 +613,16 @@ sub num_ncancelled_pkgs { shift->num_pkgs("( cust_pkg.cancel IS NULL OR cust_pkg.cancel = 0 )"); } +sub num_suspended_pkgs { + shift->num_pkgs(" ( cust_pkg.cancel IS NULL OR cust_pkg.cancel = 0 ) + AND cust_pkg.susp IS NOT NULL AND cust_pkg.susp != 0 "); +} + +sub num_unsuspended_pkgs { + shift->num_pkgs(" ( cust_pkg.cancel IS NULL OR cust_pkg.cancel = 0 ) + AND ( cust_pkg.susp IS NULL OR cust_pkg.susp = 0 ) "); +} + sub num_pkgs { my( $self ) = shift; my $sql = scalar(@_) ? shift : '';