X-Git-Url: http://git.freeside.biz/gitweb/?a=blobdiff_plain;f=FS%2FFS%2Fcust_pkg.pm;h=059384911237c614822257b687ed8ada0e9a9523;hb=2eb4ef92fbea43f55fd705d604fda5c815f9eb9c;hp=60d42cfea4c3242bad920225b392eb39072186e9;hpb=5b7eefb9f9f6f0fb6bcb6ef3767cbbabd638c29f;p=freeside.git diff --git a/FS/FS/cust_pkg.pm b/FS/FS/cust_pkg.pm index 60d42cfea..059384911 100644 --- a/FS/FS/cust_pkg.pm +++ b/FS/FS/cust_pkg.pm @@ -343,6 +343,8 @@ sub insert { local $SIG{TSTP} = 'IGNORE'; local $SIG{PIPE} = 'IGNORE'; + $self->susp( $self->order_date ) if $self->susp eq 'now'; + my $oldAutoCommit = $FS::UID::AutoCommit; local $FS::UID::AutoCommit = 0; my $dbh = dbh; @@ -654,7 +656,7 @@ sub check { || $self->ut_numbern('dundate') || $self->ut_enum('no_auto', [ '', 'Y' ]) || $self->ut_enum('waive_setup', [ '', 'Y' ]) - || $self->ut_numbern('agent_pkgid') + || $self->ut_textn('agent_pkgid') || $self->ut_enum('recur_show_zero', [ '', 'Y', 'N', ]) || $self->ut_enum('setup_show_zero', [ '', 'Y', 'N', ]) || $self->ut_foreign_keyn('main_pkgnum', 'cust_pkg', 'pkgnum') @@ -1566,16 +1568,19 @@ sub unsuspend { my $conf = new FS::Conf; - if ( $inactive > 0 && - ( $hash{'bill'} || $hash{'setup'} ) && - ( $opt{'adjust_next_bill'} || - $conf->exists('unsuspend-always_adjust_next_bill_date') || - $self->part_pkg->option('unsuspend_adjust_bill', 1) ) - ) { - - $hash{'bill'} = ( $hash{'bill'} || $hash{'setup'} ) + $inactive; - - } + $hash{'bill'} = ( $hash{'bill'} || $hash{'setup'} ) + $inactive + if $inactive > 0 + && ( $hash{'bill'} || $hash{'setup'} ) + && ( $opt{'adjust_next_bill'} + || $conf->exists('unsuspend-always_adjust_next_bill_date') + || $self->part_pkg->option('unsuspend_adjust_bill', 1) + ) + && ! $self->option('suspend_bill',1) + && ( ! $self->part_pkg->option('suspend_bill',1) + || $self->option('no_suspend_bill',1) + ) + && $hash{'order_date'} != $hash{'susp'} + ; $hash{'susp'} = ''; $hash{'adjourn'} = '' if $hash{'adjourn'} and $hash{'adjourn'} < time; @@ -2752,7 +2757,7 @@ sub set_cust_pkg_detail { =item cust_event -Returns the new-style customer billing events (see L) for this invoice. +Returns the customer billing events (see L) for this invoice. =cut @@ -2769,19 +2774,41 @@ sub cust_event { =item num_cust_event -Returns the number of new-style customer billing events (see L) for this invoice. +Returns the number of customer billing events (see L) for this package. =cut #false laziness w/cust_bill.pm sub num_cust_event { my $self = shift; - my $sql = - "SELECT COUNT(*) FROM cust_event JOIN part_event USING ( eventpart ) ". - " WHERE tablenum = ? AND eventtable = 'cust_pkg'"; + my $sql = "SELECT COUNT(*) ". $self->_from_cust_event_where; + $self->_prep_ex($sql, $self->pkgnum)->fetchrow_arrayref->[0]; +} + +=item exists_cust_event + +Returns true if there are customer billing events (see L) for this package. More efficient than using num_cust_event. + +=cut + +sub exists_cust_event { + my $self = shift; + my $sql = "SELECT 1 ". $self->_from_cust_event_where. " LIMIT 1"; + my $row = $self->_prep_ex($sql, $self->pkgnum)->fetchrow_arrayref; + $row ? $row->[0] : ''; +} + +sub _from_cust_event_where { + #my $self = shift; + " FROM cust_event JOIN part_event USING ( eventpart ) ". + " WHERE tablenum = ? AND eventtable = 'cust_pkg' "; +} + +sub _prep_ex { + my( $self, $sql, @args ) = @_; my $sth = dbh->prepare($sql) or die dbh->errstr. " preparing $sql"; - $sth->execute($self->pkgnum) or die $sth->errstr. " executing $sql"; - $sth->fetchrow_arrayref->[0]; + $sth->execute(@args) or die $sth->errstr. " executing $sql"; + $sth; } =item cust_svc [ SVCPART ] (old, deprecated usage) @@ -2813,7 +2840,7 @@ sub cust_svc_unsorted { sub cust_svc_unsorted_arrayref { my $self = shift; - return () unless $self->num_cust_svc(@_); + return [] unless $self->num_cust_svc(@_); my %opt = (); if ( @_ && $_[0] =~ /^\d+/ ) { @@ -2991,17 +3018,35 @@ following extra fields: =over 4 -=item num_cust_svc (count) +=item num_cust_svc + +(count) -=item num_avail (quantity - count) +=item num_avail -=item cust_pkg_svc (services) - array reference containing the provisioned services, as cust_svc objects +(quantity - count) + +=item cust_pkg_svc + +(services) - array reference containing the provisioned services, as cust_svc objects =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. +Accepts two options: + +=over 4 + +=item summarize_size + +If true, will omit the extra cust_pkg_svc option for objects where num_cust_svc +is this size or greater. + +=item hide_discontinued + +If true, will omit looking for services that are no longer avaialble in the +package definition. + +=back =cut @@ -3030,16 +3075,18 @@ sub part_svc { $part_svc; } $self->part_pkg->pkg_svc; - #extras - push @part_svc, map { - my $part_svc = $_; - my $num_cust_svc = $self->num_cust_svc($part_svc->svcpart); - $part_svc->{'Hash'}{'num_cust_svc'} = $num_cust_svc; #speak no evail - $part_svc->{'Hash'}{'num_avail'} = 0; #0-$num_cust_svc ? - $part_svc->{'Hash'}{'cust_pkg_svc'} = - $num_cust_svc ? [ $self->cust_svc($part_svc->svcpart) ] : []; - $part_svc; - } $self->extra_part_svc; + unless ( $opt{hide_discontinued} ) { + #extras + push @part_svc, map { + my $part_svc = $_; + my $num_cust_svc = $self->num_cust_svc($part_svc->svcpart); + $part_svc->{'Hash'}{'num_cust_svc'} = $num_cust_svc; #speak no evail + $part_svc->{'Hash'}{'num_avail'} = 0; #0-$num_cust_svc ? + $part_svc->{'Hash'}{'cust_pkg_svc'} = + $num_cust_svc ? [ $self->cust_svc($part_svc->svcpart) ] : []; + $part_svc; + } $self->extra_part_svc; + } @part_svc; @@ -3106,6 +3153,8 @@ Returns a short status string for this package, currently: =over 4 +=item on hold + =item not yet billed =item one-time charge @@ -3126,6 +3175,7 @@ sub status { my $freq = length($self->freq) ? $self->freq : $self->part_pkg->freq; return 'cancelled' if $self->get('cancel'); + return 'on hold' if $self->susp && ! $self->setup; return 'suspended' if $self->susp; return 'not yet billed' unless $self->setup; return 'one-time charge' if $freq =~ /^(0|$)/; @@ -3152,8 +3202,9 @@ Class method that returns the list of possible status strings for packages =cut tie my %statuscolor, 'Tie::IxHash', + 'on hold' => '7E0079', #purple! 'not yet billed' => '009999', #teal? cyan? - 'one-time charge' => '000000', + 'one-time charge' => '0000CC', #blue #'000000', 'active' => '00CC00', 'suspended' => 'FF9900', 'cancelled' => 'FF0000', @@ -3166,6 +3217,11 @@ sub statuses { keys %statuscolor; } +sub statuscolors { + #my $self = shift; + \%statuscolor; +} + =item statuscolor Returns a hex triplet color string for this package's status. @@ -3433,7 +3489,16 @@ Returns the L object for tax_locationnum. sub tax_location { my $self = shift; - FS::cust_location->by_key( $self->tax_locationnum ) + my $conf = FS::Conf->new; + if ( $conf->exists('tax-pkg_address') and $self->locationnum ) { + return FS::cust_location->by_key($self->locationnum); + } + elsif ( $conf->exists('tax-ship_address') ) { + return $self->cust_main->ship_location; + } + else { + return $self->cust_main->bill_location; + } } =item seconds_since TIMESTAMP @@ -3574,7 +3639,7 @@ sub transfer { return ('Package does not exist: '.$dest_pkgnum) unless $dest; foreach my $pkg_svc ( $dest->part_pkg->pkg_svc ) { - $target{$pkg_svc->svcpart} = $pkg_svc->quantity; + $target{$pkg_svc->svcpart} = $pkg_svc->quantity * ( $dest->quantity || 1 ); } foreach my $cust_svc ($dest->cust_svc) { @@ -4014,7 +4079,7 @@ sub apply_usage { minutes => min($cust_pkg_usage->minutes, $minutes), }); $cust_pkg_usage->set('minutes', - sprintf('%.0f', $cust_pkg_usage->minutes - $cdr_cust_pkg_usage->minutes) + $cust_pkg_usage->minutes - $cdr_cust_pkg_usage->minutes ); $error = $cust_pkg_usage->replace || $cdr_cust_pkg_usage->insert; $minutes -= $cdr_cust_pkg_usage->minutes; @@ -4214,6 +4279,21 @@ sub inactive_sql { " AND ( cust_pkg.susp IS NULL OR cust_pkg.susp = 0 ) "; } +=item on_hold_sql + +Returns an SQL expression identifying on-hold packages. + +=cut + +sub on_hold_sql { + #$_[0]->recurring_sql(). ' AND '. + " + ( cust_pkg.cancel IS NULL OR cust_pkg.cancel = 0 ) + AND cust_pkg.susp IS NOT NULL AND cust_pkg.susp != 0 + AND ( cust_pkg.setup IS NULL OR cust_pkg.setup = 0 ) + "; +} + =item susp_sql =item suspended_sql @@ -4227,6 +4307,7 @@ sub susp_sql { " ( cust_pkg.cancel IS NULL OR cust_pkg.cancel = 0 ) AND cust_pkg.susp IS NOT NULL AND cust_pkg.susp != 0 + AND cust_pkg.setup IS NOT NULL AND cust_pkg.setup != 0 "; } @@ -4252,6 +4333,7 @@ Returns an SQL expression to give the package status as a string. sub status_sql { "CASE WHEN cust_pkg.cancel IS NOT NULL THEN 'cancelled' + WHEN ( cust_pkg.susp IS NOT NULL AND cust_pkg.setup IS NULL ) THEN 'on hold' WHEN cust_pkg.susp IS NOT NULL THEN 'suspended' WHEN cust_pkg.setup IS NULL THEN 'not yet billed' WHEN ".onetime_sql()." THEN 'one-time charge' @@ -4270,13 +4352,15 @@ Valid parameters are =item agentnum -=item magic +=item status -active, inactive, suspended, cancel (or cancelled) +on hold, active, inactive (or one-time charge), suspended, canceled (or cancelled) -=item status +=item magic -active, inactive, suspended, one-time charge, inactive, cancel (or cancelled) +Equivalent to "status", except that "canceled"/"cancelled" will exclude +packages that were changed into a new package with the same pkgpart (i.e. +location or quantity changes). =item custom @@ -4444,6 +4528,12 @@ sub search { push @where, FS::cust_pkg->inactive_sql(); + } elsif ( $params->{'magic'} =~ /^on[ _]hold$/ + || $params->{'status'} =~ /^on[ _]hold$/ ) { + + push @where, FS::cust_pkg->on_hold_sql(); + + } elsif ( $params->{'magic'} eq 'suspended' || $params->{'status'} eq 'suspended' ) { @@ -4455,6 +4545,19 @@ sub search { push @where, FS::cust_pkg->cancelled_sql(); } + + ### special case: "magic" is used in detail links from browse/part_pkg, + # where "cancelled" has the restriction "and not replaced with a package + # of the same pkgpart". Be consistent with that. + ### + + if ( $params->{'magic'} =~ /^cancell?ed$/ ) { + my $new_pkgpart = "SELECT pkgpart FROM cust_pkg AS cust_pkg_next ". + "WHERE cust_pkg_next.change_pkgnum = cust_pkg.pkgnum"; + # ...may not exist, if this was just canceled and not changed; in that + # case give it a "new pkgpart" that never equals the old pkgpart + push @where, "COALESCE(($new_pkgpart), 0) != cust_pkg.pkgpart"; + } ### # parse package class