X-Git-Url: http://git.freeside.biz/gitweb/?a=blobdiff_plain;f=FS%2FFS%2Fcust_pkg.pm;h=5779d5c10613449ed92b9580c80a671aaa44070a;hb=3ef95c9cc6b8e9331879fcbf32962e88e3bc2c23;hp=75102ce87502d7cba0a91cf07ffbcff29a08c952;hpb=17d4ef635337a5ac7f5755fac01b19668c56f992;p=freeside.git diff --git a/FS/FS/cust_pkg.pm b/FS/FS/cust_pkg.pm index 75102ce87..5779d5c10 100644 --- a/FS/FS/cust_pkg.pm +++ b/FS/FS/cust_pkg.pm @@ -265,7 +265,8 @@ The following options are available: =item change If set true, supresses actions that should only be taken for new package -orders. (Currently this includes: intro periods when delay_setup is on.) +orders. (Currently this includes: intro periods when delay_setup is on, +auto-adding a 1st start date, auto-adding expiration/adjourn/contract_end dates) =item options @@ -298,37 +299,42 @@ sub insert { my $part_pkg = $self->part_pkg; - # if the package def says to start only on the first of the month: - 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++; } - $self->start_date( timelocal_nocheck(0,0,0,1,$mon,$year) ); - } - - # set up any automatic expire/adjourn/contract_end timers - # based on the start date - foreach my $action ( qw(expire adjourn contract_end) ) { - my $months = $part_pkg->option("${action}_months",1); - if($months and !$self->$action) { - my $start = $self->start_date || $self->setup || time; - $self->$action( $part_pkg->add_freq($start, $months) ); + if ( ! $options{'change'} ) { + + # if the package def says to start only on the first of the month: + 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++; } + $self->start_date( timelocal_nocheck(0,0,0,1,$mon,$year) ); + } + + # set up any automatic expire/adjourn/contract_end timers + # based on the start date + foreach my $action ( qw(expire adjourn contract_end) ) { + my $months = $part_pkg->option("${action}_months",1); + if($months and !$self->$action) { + my $start = $self->start_date || $self->setup || time; + $self->$action( $part_pkg->add_freq($start, $months) ); + } + } + + # if this package has "free days" and delayed setup fee, then + # set start date that many days in the future. + # (this should have been set in the UI, but enforce it here) + if ( ! $options{'change'} + && $part_pkg->option('free_days',1) + && $part_pkg->option('delay_setup',1) + #&& ! $self->start_date + ) + { + $self->start_date( $part_pkg->default_start_date ); } - } - # if this package has "free days" and delayed setup fee, tehn - # set start date that many days in the future. - # (this should have been set in the UI, but enforce it here) - if ( ! $options{'change'} - && ( my $free_days = $part_pkg->option('free_days',1) ) - && $part_pkg->option('delay_setup',1) - #&& ! $self->start_date - ) - { - $self->start_date( $part_pkg->default_start_date ); } - $self->order_date(time); + # set order date unless this was previously a different package + $self->order_date(time) unless $self->change_pkgnum; local $SIG{HUP} = 'IGNORE'; local $SIG{INT} = 'IGNORE'; @@ -1808,7 +1814,7 @@ sub change { $error = $opt->{'cust_location'}->find_or_insert; if ( $error ) { $dbh->rollback if $oldAutoCommit; - return "inserting cust_location (transaction rolled back): $error"; + return "creating location record: $error"; } $opt->{'locationnum'} = $opt->{'cust_location'}->locationnum; } @@ -1845,6 +1851,9 @@ sub change { $hash{$date} = $self->getfield($date); } } + # always keep this date, regardless of anything + # (the date of the package change is in a different field) + $hash{'order_date'} = $self->getfield('order_date'); # allow $opt->{'locationnum'} = '' to specifically set it to null # (i.e. customer default location) @@ -1860,10 +1869,10 @@ sub change { if ( $opt->{cust_main} ) { my $cust_main = $opt->{cust_main}; unless ( $cust_main->custnum ) { - my $error = $cust_main->insert; + my $error = $cust_main->insert( @{ $opt->{cust_main_insert_args}||[] } ); if ( $error ) { $dbh->rollback if $oldAutoCommit; - return "inserting cust_main (transaction rolled back): $error"; + return "inserting customer record: $error"; } } $custnum = $cust_main->custnum; @@ -1898,7 +1907,7 @@ sub change { } if ($error) { $dbh->rollback if $oldAutoCommit; - return $error; + return "inserting new package: $error"; } # Transfer services and cancel old package. @@ -1907,7 +1916,7 @@ sub change { if ($error and $error == 0) { # $old_pkg->transfer failed. $dbh->rollback if $oldAutoCommit; - return $error; + return "transferring $error"; } if ( $error > 0 && $conf->exists('cust_pkg-change_svcpart') ) { @@ -1916,7 +1925,7 @@ sub change { if ($error and $error == 0) { # $old_pkg->transfer failed. $dbh->rollback if $oldAutoCommit; - return $error; + return "converting $error"; } } @@ -1928,7 +1937,7 @@ sub change { # Transfers were successful, but we still had services left on the old # package. We can't change the package under this circumstances, so abort. $dbh->rollback if $oldAutoCommit; - return "Unable to transfer all services from package ". $self->pkgnum; + return "unable to transfer all services"; } #reset usage if changing pkgpart @@ -1943,7 +1952,7 @@ sub change { if ($error) { $dbh->rollback if $oldAutoCommit; - return "Error setting usage values: $error"; + return "setting usage values: $error"; } } else { # if NOT changing pkgpart, transfer any usage pools over @@ -1952,7 +1961,7 @@ sub change { $error = $usage->replace; if ( $error ) { $dbh->rollback if $oldAutoCommit; - return "Error transferring usage pools: $error"; + return "transferring usage pools: $error"; } } } @@ -1969,7 +1978,7 @@ sub change { $error = $new_discount->insert; if ( $error ) { $dbh->rollback if $oldAutoCommit; - return "Error transferring discounts: $error"; + return "transferring discounts: $error"; } } } @@ -1982,7 +1991,7 @@ sub change { $error = $new_detail->insert; if ( $error ) { $dbh->rollback if $oldAutoCommit; - return "Error transferring package notes: $error"; + return "transferring package notes: $error"; } } @@ -2061,7 +2070,7 @@ sub change { ); if ($error) { $dbh->rollback if $oldAutoCommit; - return $error; + return "canceling old package: $error"; } if ( $conf->exists('cust_pkg-change_pkgpart-bill_now') ) { @@ -2071,7 +2080,7 @@ sub change { ); if ( $error ) { $dbh->rollback if $oldAutoCommit; - return $error; + return "billing new package: $error"; } } @@ -2265,13 +2274,17 @@ sub set_salesnum { =item modify_charge OPTIONS -Change the properties of a one-time charge. Currently the only properties -that can be changed this way are those that have no impact on billing -calculations: +Change the properties of a one-time charge. The following properties can +be changed this way: - pkg: the package description - classnum: the package class - additional: arrayref of additional invoice details to add to this package +and, I: +- start_date: the date when it will be billed +- amount: the setup fee to be charged +- quantity: the multiplier for the setup fee + If you pass 'adjust_commission' => 1, and the classnum changes, and there are commission credits linked to this charge, they will be recalculated. @@ -2305,14 +2318,51 @@ sub modify_charge { } my $old_classnum; - if ( exists($opt{'classnum'}) and $part_pkg->classnum ne $opt{'classnum'} ) { + if ( exists($opt{'classnum'}) and $part_pkg->classnum ne $opt{'classnum'} ) + { # remember it $old_classnum = $part_pkg->classnum; $part_pkg->set('classnum', $opt{'classnum'}); } + if ( !$self->get('setup') ) { + # not yet billed, so allow amount and quantity + if ( exists($opt{'quantity'}) + and $opt{'quantity'} != $self->quantity + and $opt{'quantity'} > 0 ) { + + $self->set('quantity', $opt{'quantity'}); + } + if ( exists($opt{'start_date'}) + and $opt{'start_date'} != $self->start_date ) { + + $self->set('start_date', $opt{'start_date'}); + } + if ($self->modified) { # for quantity or start_date change + my $error = $self->replace; + return $error if $error; + } + + if ( exists($opt{'amount'}) + and $part_pkg->option('setup_fee') != $opt{'amount'} + and $opt{'amount'} > 0 ) { + + $pkg_opt{'setup_fee'} = $opt{'amount'}; + # standard for one-time charges is to set comment = (formatted) amount + # update it to avoid confusion + my $conf = FS::Conf->new; + $part_pkg->set('comment', + ($conf->config('money_char') || '$') . + sprintf('%.2f', $opt{'amount'}) + ); + } + } # else simply ignore them; the UI shouldn't allow editing the fields + my $error = $part_pkg->replace( options => \%pkg_opt ); - return $error if $error; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; + } if (defined $old_classnum) { # fix invoice grouping records @@ -2705,14 +2755,30 @@ sub num_cust_event { =item cust_svc [ OPTION => VALUE ... ] (current usage) +=item cust_svc_unsorted [ OPTION => VALUE ... ] + Returns the services for this package, as FS::cust_svc objects (see L). Available options are svcpart and svcdb. If either is spcififed, returns only the matching services. +As an optimization, use the cust_svc_unsorted version if you are not displaying +the results. + =cut sub cust_svc { my $self = shift; + cluck "cust_pkg->cust_svc called" if $DEBUG > 2; + $self->_sort_cust_svc( $self->cust_svc_unsorted_arrayref(@_) ); +} + +sub cust_svc_unsorted { + my $self = shift; + @{ $self->cust_svc_unsorted_arrayref(@_) }; +} + +sub cust_svc_unsorted_arrayref { + my $self = shift; return () unless $self->num_cust_svc(@_); @@ -2737,13 +2803,7 @@ sub cust_svc { $search{extra_sql} = ' AND svcdb = '. dbh->quote( $opt{'svcdb'} ); } - cluck "cust_pkg->cust_svc called" if $DEBUG > 2; - - #if ( $self->{'_svcnum'} ) { - # values %{ $self->{'_svcnum'}->cache }; - #} else { - $self->_sort_cust_svc( [ qsearch(\%search) ] ); - #} + [ qsearch(\%search) ]; } @@ -3516,14 +3576,15 @@ sub transfer { } } + 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 $target{$cust_svc->svcpart}--; my $new = new FS::cust_svc { $cust_svc->hash }; $new->pkgnum($dest_pkgnum); - my $error = $new->replace($cust_svc); - return $error if $error; + $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"; @@ -3543,14 +3604,17 @@ sub transfer { my $new = new FS::cust_svc { $cust_svc->hash }; $new->svcpart($change_svcpart); $new->pkgnum($dest_pkgnum); - my $error = $new->replace($cust_svc); - return $error if $error; + $error = $new->replace($cust_svc); } else { $remaining++; } } else { $remaining++ } + if ( $error ) { + my @label = $cust_svc->label; + return "service $label[1]: $error"; + } } return $remaining; } @@ -4553,12 +4617,18 @@ sub search { ); if( exists($params->{'active'} ) ) { - # This overrides all the other date-related fields + # This overrides all the other date-related fields, and includes packages + # that were active at some time during the interval. It excludes: + # - packages that were set up after the end of the interval + # - packages that were canceled before the start of the interval + # - packages that were suspended before the start of the interval + # and are still suspended now my($beginning, $ending) = @{$params->{'active'}}; push @where, "cust_pkg.setup IS NOT NULL", "cust_pkg.setup <= $ending", "(cust_pkg.cancel IS NULL OR cust_pkg.cancel >= $beginning )", + "(cust_pkg.susp IS NULL OR cust_pkg.susp >= $beginning )", "NOT (".FS::cust_pkg->onetime_sql . ")"; } else {