X-Git-Url: http://git.freeside.biz/gitweb/?a=blobdiff_plain;f=FS%2FFS%2Fcust_pkg.pm;h=54119b50efbd721274cd44e71999d9e595d03f26;hb=6b8acf1157b5ec40877fbced43e80ad90d91fe2e;hp=872a6446a78401a50baa0abb36252a04edc392ef;hpb=dbe1f22d63052dbbfe8b3cb578a0d831213ebb75;p=freeside.git diff --git a/FS/FS/cust_pkg.pm b/FS/FS/cust_pkg.pm index 872a6446a..54119b50e 100644 --- a/FS/FS/cust_pkg.pm +++ b/FS/FS/cust_pkg.pm @@ -8,7 +8,7 @@ use Carp qw(cluck); use Scalar::Util qw( blessed ); use List::Util qw(max); use Tie::IxHash; -use Time::Local qw( timelocal_nocheck ); +use Time::Local qw( timelocal timelocal_nocheck ); use MIME::Entity; use FS::UID qw( getotaker dbh ); use FS::Misc qw( send_email ); @@ -266,7 +266,9 @@ sub insert { my $error = $self->check_pkgpart; return $error if $error; - if ( $self->part_pkg->option('start_1st', 1) && !$self->start_date ) { + my $part_pkg = $self->part_pkg; + + if ( $part_pkg->option('start_1st', 1) && !$self->start_date ) { my ($sec,$min,$hour,$mday,$mon,$year) = (localtime(time) )[0,1,2,3,4,5]; $mon += 1 unless $mday == 1; until ( $mon < 12 ) { $mon -= 12; $year++; } @@ -274,13 +276,21 @@ sub insert { } foreach my $action ( qw(expire adjourn contract_end) ) { - my $months = $self->part_pkg->option("${action}_months",1); + my $months = $part_pkg->option("${action}_months",1); if($months and !$self->$action) { my $start = $self->start_date || $self->setup || time; - $self->$action( $self->part_pkg->add_freq($start, $months) ); + $self->$action( $part_pkg->add_freq($start, $months) ); } } + my $free_days = $part_pkg->option('free_days',1); + if ( $free_days && $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; + $self->start_date($start_date); + } + $self->order_date(time); local $SIG{HUP} = 'IGNORE'; @@ -600,6 +610,8 @@ sub check { || $self->ut_enum('no_auto', [ '', 'Y' ]) || $self->ut_enum('waive_setup', [ '', 'Y' ]) || $self->ut_numbern('agent_pkgid') + || $self->ut_enum('recur_show_zero', [ '', 'Y', 'N', ]) + || $self->ut_enum('setup_show_zero', [ '', 'Y', 'N', ]) ; return $error if $error; @@ -763,7 +775,7 @@ sub cancel { #schwartz map { $_->[0] } sort { $a->[1] <=> $b->[1] } - map { [ $_, $_->svc_x->table_info->{'cancel_weight'} ]; } + map { [ $_, $_->svc_x ? $_->svc_x->table_info->{'cancel_weight'} : -1 ]; } qsearch( 'cust_svc', { 'pkgnum' => $self->pkgnum } ) ) { my $part_svc = $cust_svc->part_svc; @@ -988,6 +1000,19 @@ sub suspend { } } + my %hash = $self->hash; + if ( $date ) { + $hash{'adjourn'} = $date; + } else { + $hash{'susp'} = $suspend_time; + } + my $new = new FS::cust_pkg ( \%hash ); + $error = $new->replace( $self, options => { $self->options } ); + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; + } + unless ( $date ) { my @labels = (); @@ -1043,19 +1068,6 @@ sub suspend { } - my %hash = $self->hash; - if ( $date ) { - $hash{'adjourn'} = $date; - } else { - $hash{'susp'} = $suspend_time; - } - my $new = new FS::cust_pkg ( \%hash ); - $error = $new->replace( $self, options => { $self->options } ); - if ( $error ) { - $dbh->rollback if $oldAutoCommit; - return $error; - } - $dbh->commit or die $dbh->errstr if $oldAutoCommit; ''; #no errors @@ -1338,12 +1350,16 @@ sub change { $hash{$_} = '' foreach qw(setup bill last_bill); } + # allow $opt->{'locationnum'} = '' to specifically set it to null + # (i.e. customer default location) + $opt->{'locationnum'} = $self->locationnum if !exists($opt->{'locationnum'}); + # Create the new package. my $cust_pkg = new FS::cust_pkg { custnum => $self->custnum, pkgpart => ( $opt->{'pkgpart'} || $self->pkgpart ), refnum => ( $opt->{'refnum'} || $self->refnum ), - locationnum => ( $opt->{'locationnum'} || $self->locationnum ), + locationnum => ( $opt->{'locationnum'} ), %hash, }; @@ -1724,11 +1740,13 @@ sub num_cust_event { $sth->fetchrow_arrayref->[0]; } -=item cust_svc [ SVCPART ] +=item cust_svc [ SVCPART ] (old, deprecated usage) + +=item cust_svc [ OPTION => VALUE ... ] (current usage) Returns the services for this package, as FS::cust_svc objects (see -L). If a svcpart is specified, return only the matching -services. +L). Available options are svcpart and svcdb. If either is +spcififed, returns only the matching services. =cut @@ -1737,9 +1755,25 @@ sub cust_svc { return () unless $self->num_cust_svc(@_); - if ( @_ ) { - return qsearch( 'cust_svc', { 'pkgnum' => $self->pkgnum, - 'svcpart' => shift, } ); + my %opt = (); + if ( @_ && $_[0] =~ /^\d+/ ) { + $opt{svcpart} = shift; + } elsif ( @_ && ref($_[0]) eq 'HASH' ) { + %opt = %{ $_[0] }; + } elsif ( @_ ) { + %opt = @_; + } + + my %search = ( + 'table' => 'cust_svc', + 'hashref' => { 'pkgnum' => $self->pkgnum }, + ); + if ( $opt{svcpart} ) { + $search{hashref}->{svcpart} = $opt{'svcpart'}; + } + if ( $opt{'svcdb'} ) { + $search{addl_from} = ' LEFT JOIN part_svc USING ( svcpart ) '; + $search{hashref}->{svcdb} = $opt{'svcdb'}; } cluck "cust_pkg->cust_svc called" if $DEBUG > 2; @@ -1747,9 +1781,7 @@ sub cust_svc { #if ( $self->{'_svcnum'} ) { # values %{ $self->{'_svcnum'}->cache }; #} else { - $self->_sort_cust_svc( - [ qsearch( 'cust_svc', { 'pkgnum' => $self->pkgnum } ) ] - ); + $self->_sort_cust_svc( [ qsearch(\%search) ] ); #} } @@ -1789,7 +1821,7 @@ sub h_cust_svc { FS::h_cust_svc->sql_h_search(@_), ) ] ); - if ( $mode eq 'I' ) { + if ( defined($mode) && $mode eq 'I' ) { my %hidden_svcpart = map { $_->svcpart => $_->hidden } $self->part_svc; return grep { !$hidden_svcpart{$_->svcpart} } @cust_svc; } else { @@ -1817,10 +1849,12 @@ sub _sort_cust_svc { } -=item num_cust_svc [ SVCPART ] +=item num_cust_svc [ SVCPART ] (old, deprecated usage) -Returns the number of provisioned services for this package. If a svcpart is -specified, counts only the matching services. +=item num_cust_svc [ OPTION => VALUE ... ] (current usage) + +Returns the number of services for this package. Available options are svcpart +and svcdb. If either is spcififed, returns only the matching services. =cut @@ -1835,11 +1869,31 @@ sub num_cust_svc { cluck "cust_pkg->num_cust_svc called, _num_cust_svc:".$self->{'_num_cust_svc'} if $DEBUG > 2; - my $sql = 'SELECT COUNT(*) FROM cust_svc WHERE pkgnum = ?'; - $sql .= ' AND svcpart = ?' if @_; + my %opt = (); + if ( @_ && $_[0] =~ /^\d+/ ) { + $opt{svcpart} = shift; + } elsif ( @_ && ref($_[0]) eq 'HASH' ) { + %opt = %{ $_[0] }; + } elsif ( @_ ) { + %opt = @_; + } + + my $select = 'SELECT COUNT(*) FROM cust_svc '; + my $where = ' WHERE pkgnum = ? '; + my @param = ($self->pkgnum); - my $sth = dbh->prepare($sql) or die dbh->errstr; - $sth->execute($self->pkgnum, @_) or die $sth->errstr; + if ( $opt{'svcpart'} ) { + $where .= ' AND svcpart = ? '; + push @param, $opt{'svcpart'}; + } + if ( $opt{'svcdb'} ) { + $select .= ' LEFT JOIN part_svc USING ( svcpart ) '; + $where .= ' AND svcdb = ? '; + push @param, $opt{'svcdb'}; + } + + my $sth = dbh->prepare("$select $where") or die dbh->errstr; + $sth->execute(@param) or die $sth->errstr; $sth->fetchrow_arrayref->[0]; } @@ -1870,7 +1924,7 @@ sub available_part_svc { $self->part_pkg->pkg_svc; } -=item part_svc +=item part_svc [ OPTION => VALUE ... ] Returns a list of FS::part_svc objects representing provisioned and available services included in this package. Each FS::part_svc object also has the @@ -1884,15 +1938,20 @@ following extra fields: =item cust_pkg_svc (services) - array reference containing the provisioned services, as cust_svc objects -svcnum -label -> ($cust_svc->label)[1] - =back +Accepts one option: summarize_size. If specified and non-zero, will omit the +extra cust_pkg_svc option for objects where num_cust_svc is this size or +greater. + =cut +#svcnum +#label -> ($cust_svc->label)[1] + sub part_svc { my $self = shift; + my %opt = @_; #XXX some sort of sort order besides numeric by svcpart... my @part_svc = sort { $a->svcpart <=> $b->svcpart } map { @@ -1903,7 +1962,9 @@ sub part_svc { $part_svc->{'Hash'}{'num_avail'} = max( 0, $pkg_svc->quantity - $num_cust_svc ); $part_svc->{'Hash'}{'cust_pkg_svc'} = - $num_cust_svc ? [ $self->cust_svc($part_svc->svcpart) ] : []; + $num_cust_svc ? [ $self->cust_svc($part_svc->svcpart) ] : [] + unless exists($opt{summarize_size}) && $opt{summarize_size} > 0 + && $num_cust_svc >= $opt{summarize_size}; $part_svc->{'Hash'}{'hidden'} = $pkg_svc->hidden; $part_svc; } $self->part_pkg->pkg_svc; @@ -1935,7 +1996,7 @@ sub extra_part_svc { my $self = shift; my $pkgnum = $self->pkgnum; - my $pkgpart = $self->pkgpart; + #my $pkgpart = $self->pkgpart; # qsearch( { # 'table' => 'part_svc', @@ -1954,23 +2015,27 @@ sub extra_part_svc { # 'extra_param' => [ [$self->pkgpart=>'int'], [$self->pkgnum=>'int'] ], # } ); -#seems to benchmark slightly faster... +#seems to benchmark slightly faster... (or did?) + + my @pkgparts = map $_->pkgpart, $self->part_pkg->self_and_svc_linked; + my $pkgparts = join(',', @pkgparts); + qsearch( { #'select' => 'DISTINCT ON (svcpart) part_svc.*', #MySQL doesn't grok DISINCT ON 'select' => 'DISTINCT part_svc.*', 'table' => 'part_svc', 'addl_from' => - 'LEFT JOIN pkg_svc ON ( pkg_svc.svcpart = part_svc.svcpart - AND pkg_svc.pkgpart = ? + "LEFT JOIN pkg_svc ON ( pkg_svc.svcpart = part_svc.svcpart + AND pkg_svc.pkgpart IN ($pkgparts) AND quantity > 0 ) LEFT JOIN cust_svc ON ( cust_svc.svcpart = part_svc.svcpart ) LEFT JOIN cust_pkg USING ( pkgnum ) - ', + ", 'hashref' => {}, 'extra_sql' => "WHERE pkgsvcnum IS NULL AND cust_pkg.pkgnum = ? ", - 'extra_param' => [ [$self->pkgpart=>'int'], [$self->pkgnum=>'int'] ], + 'extra_param' => [ [$self->pkgnum=>'int'] ], } ); } @@ -2629,7 +2694,8 @@ All svc_accts which are part of this package have their values reset. sub set_usage { my ($self, $valueref, %opt) = @_; - foreach my $cust_svc ($self->cust_svc){ + #only svc_acct can set_usage for now + foreach my $cust_svc ( $self->cust_svc( 'svcdb'=>'svc_acct' ) ) { my $svc_x = $cust_svc->svc_x; $svc_x->set_usage($valueref, %opt) if $svc_x->can("set_usage"); @@ -2649,7 +2715,8 @@ All svc_accts which are part of this package have their values incremented. sub recharge { my ($self, $valueref) = @_; - foreach my $cust_svc ($self->cust_svc){ + #only svc_acct can set_usage for now + foreach my $cust_svc ( $self->cust_svc( 'svcdb'=>'svc_acct' ) ) { my $svc_x = $cust_svc->svc_x; $svc_x->recharge($valueref) if $svc_x->can("recharge"); @@ -2940,45 +3007,56 @@ sub search { # parse package class ### - #false lazinessish w/graph/cust_bill_pkg.cgi - my $classnum = 0; - my @pkg_class = (); - if ( exists($params->{'classnum'}) - && $params->{'classnum'} =~ /^(\d*)$/ - ) - { - $classnum = $1; - if ( $classnum ) { #a specific class - push @where, "part_pkg.classnum = $classnum"; - - #@pkg_class = ( qsearchs('pkg_class', { 'classnum' => $classnum } ) ); - #die "classnum $classnum not found!" unless $pkg_class[0]; - #$title .= $pkg_class[0]->classname.' '; - - } elsif ( $classnum eq '' ) { #the empty class - - push @where, "part_pkg.classnum IS NULL"; - #$title .= 'Empty class '; - #@pkg_class = ( '(empty class)' ); - } elsif ( $classnum eq '0' ) { - #@pkg_class = qsearch('pkg_class', {} ); # { 'disabled' => '' } ); - #push @pkg_class, '(empty class)'; - } else { - die "illegal classnum"; + if ( exists($params->{'classnum'}) ) { + + my @classnum = (); + if ( ref($params->{'classnum'}) ) { + + if ( ref($params->{'classnum'}) eq 'HASH' ) { + @classnum = grep $params->{'classnum'}{$_}, keys %{ $params->{'classnum'} }; + } elsif ( ref($params->{'classnum'}) eq 'ARRAY' ) { + @classnum = @{ $params->{'classnum'} }; + } else { + die 'unhandled classnum ref '. $params->{'classnum'}; + } + + + } elsif ( $params->{'classnum'} =~ /^(\d*)$/ && $1 ne '0' ) { + @classnum = ( $1 ); } + + if ( @classnum ) { + + my @c_where = (); + my @nums = grep $_, @classnum; + push @c_where, 'part_pkg.classnum IN ('. join(',',@nums). ')' if @nums; + my $null = scalar( grep { $_ eq '' } @classnum ); + push @c_where, 'part_pkg.classnum IS NULL' if $null; + + if ( scalar(@c_where) == 1 ) { + push @where, @c_where; + } elsif ( @c_where ) { + push @where, ' ( '. join(' OR ', @c_where). ' ) '; + } + warn $where[-1]; + + } + + } - #eslaf ### # parse package report options ### my @report_option = (); - if ( exists($params->{'report_option'}) - && $params->{'report_option'} =~ /^([,\d]*)$/ - ) - { - @report_option = split(',', $1); + if ( exists($params->{'report_option'}) ) { + if ( ref($params->{'report_option'}) eq 'ARRAY' ) { + @report_option = @{ $params->{'report_option'} }; + } elsif ( $params->{'report_option'} =~ /^([,\d]*)$/ ) { + @report_option = split(',', $1); + } + } if (@report_option) { @@ -2991,7 +3069,27 @@ sub search { } @report_option; } - #eslaf + foreach my $any ( grep /^report_option_any/, keys %$params ) { + + my @report_option_any = (); + if ( ref($params->{$any}) eq 'ARRAY' ) { + @report_option_any = @{ $params->{$any} }; + } elsif ( $params->{$any} =~ /^([,\d]*)$/ ) { + @report_option_any = split(',', $1); + } + + if (@report_option_any) { + # this will result in the empty set for the dangling comma case as it should + push @where, ' ( '. join(' OR ', + map{ "0 < ( SELECT count(*) FROM part_pkg_option + WHERE part_pkg_option.pkgpart = part_pkg.pkgpart + AND optionname = 'report_option_$_' + AND optionvalue = '1' )" + } @report_option_any + ). ' ) '; + } + + } ### # parse custom @@ -3003,7 +3101,8 @@ sub search { # parse fcc_line ### - push @where, "part_pkg.fcc_ds0s > 0" if $params->{fcc_line}; + push @where, "(part_pkg.fcc_ds0s > 0 OR pkg_class.fcc_ds0s > 0)" + if $params->{fcc_line}; ### # parse censustract @@ -3017,6 +3116,21 @@ sub search { } ### + # parse censustract2 + ### + if ( exists($params->{'censustract2'}) + && $params->{'censustract2'} =~ /^(\d*)$/ + ) + { + if ($1) { + push @where, "cust_main.censustract LIKE '$1%'"; + } else { + push @where, + "( cust_main.censustract = '' OR cust_main.censustract IS NULL )"; + } + } + + ### # parse part_pkg ### @@ -3281,6 +3395,15 @@ sub _location_sql_where { "; } +sub _X_show_zero { + my( $self, $what ) = @_; + + my $what_show_zero = $what. '_show_zero'; + length($self->$what_show_zero()) + ? ($self->$what_show_zero() eq 'Y') + : $self->part_pkg->$what_show_zero(); +} + =head1 SUBROUTINES =over 4