=over 4
-=item reason - can be set to a cancellation reason (see L<FS:reason>),
+=item reason - can be set to a cancellation reason (see L<FS:reason>),
either a reasonnum of an existing reason, or passing a hashref will create
a new reason. The hashref should have the following keys:
- typenum - Reason type (see L<FS::reason_type>
}
}
+ # if a reasonnum was passed, get the actual reason object so we can check
+ # unused_credit
+ # (passing a reason hashref is still allowed, but it can't be used with
+ # the fancy behavioral options.)
+
+ my $reason;
+ if ($options{'reason'} =~ /^\d+$/) {
+ $reason = FS::reason->by_key($options{'reason'});
+ }
+
my %hash = $self->hash;
if ( $date ) {
$hash{'adjourn'} = $date;
return $error;
}
- unless ( $date ) {
+ unless ( $date ) { # then we are suspending now
+
# credit remaining time if appropriate
- if ( $self->part_pkg->option('unused_credit_suspend', 1) ) {
+ # (if required by the package def, or the suspend reason)
+ my $unused_credit = $self->part_pkg->option('unused_credit_suspend',1)
+ || ( defined($reason) && $reason->unused_credit );
+
+ if ( $unused_credit ) {
+ warn "crediting unused time on pkg#".$self->pkgnum."\n" if $DEBUG;
my $error = $self->credit_remaining('suspend', $suspend_time);
if ($error) {
$dbh->rollback if $oldAutoCommit;
=cut
+# Implementation note:
+#
+# If you pkgpart-change a package that has been billed, and it's set to give
+# credit on package change, then this method gets called and then the new
+# package will have no last_bill date. Therefore the customer will be credited
+# only once (per billing period) even if there are multiple package changes.
+#
+# If you location-change a package that has been billed, this method will NOT
+# be called and the new package WILL have the last bill date of the old
+# package.
+#
+# If the new package is then canceled within the same billing cycle,
+# credit_remaining needs to run calc_remain on the OLD package to determine
+# the amount of unused time to credit.
+
sub credit_remaining {
# Add a credit for remaining service
my ($self, $mode, $time) = @_;
and $next_bill > 0 # the package has a next bill date
and $next_bill >= $time # which is in the future
) {
- my $remaining_value = $self->calc_remain('time' => $time);
+ my $remaining_value = 0;
+
+ my $remain_pkg = $self;
+ $remaining_value = $remain_pkg->calc_remain('time' => $time);
+
+ # we may have to walk back past some package changes to get to the
+ # one that actually has unused time
+ while ( $remaining_value == 0 ) {
+ if ( $remain_pkg->change_pkgnum ) {
+ $remain_pkg = FS::cust_pkg->by_key($remain_pkg->change_pkgnum);
+ } else {
+ # the package has really never been billed
+ return;
+ }
+ $remaining_value = $remain_pkg->calc_remain('time' => $time);
+ }
+
if ( $remaining_value > 0 ) {
warn "Crediting for $remaining_value on package ".$self->pkgnum."\n"
if $DEBUG;
my $conf = new FS::Conf;
- $hash{'bill'} = ( $hash{'bill'} || $hash{'setup'} ) + $inactive
- if $inactive > 0
+ # increment next bill date if certain conditions are met:
+ # - it was due to be billed at some point
+ # - either the global or local config says to do this
+ my $adjust_bill = 0;
+ 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'}
- ;
+ ) {
+ $adjust_bill = 1;
+ }
+
+ # but not if:
+ # - the package billed during suspension
+ # - or it was ordered on hold
+ # - or the customer was credited for the unused time
+
+ if ( $self->option('suspend_bill',1)
+ or ( $self->part_pkg->option('suspend_bill',1)
+ and ! $self->option('no_suspend_bill',1)
+ )
+ or $hash{'order_date'} == $hash{'susp'}
+ or $self->part_pkg->option('unused_credit_suspend')
+ or ( defined($reason) and $reason->unused_credit )
+ ) {
+ $adjust_bill = 0;
+ }
+
+ # then add the length of time suspended to the bill date
+ if ( $adjust_bill ) {
+ $hash{'bill'} = ( $hash{'bill'} || $hash{'setup'} ) + $inactive
+ }
$hash{'susp'} = '';
$hash{'adjourn'} = '' if $hash{'adjourn'} and $hash{'adjourn'} < time;
}
if ( !$self->get('setup') ) {
- # not yet billed, so allow amount and quantity
+ # not yet billed, so allow amount, setup_cost, quantity and start_date
+
+ if ( exists($opt{'amount'})
+ and $part_pkg->option('setup_fee') != $opt{'amount'}
+ and $opt{'amount'} > 0 ) {
+
+ $pkg_opt{'setup_fee'} = $opt{'amount'};
+ $pkg_opt_modified = 1;
+ }
+
+ if ( exists($opt{'setup_cost'})
+ and $part_pkg->setup_cost != $opt{'setup_cost'}
+ and $opt{'setup_cost'} > 0 ) {
+
+ $part_pkg->set('setup_cost', $opt{'setup_cost'});
+ }
+
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 ( exists($opt{'amount'})
- and $part_pkg->option('setup_fee') != $opt{'amount'}
- and $opt{'amount'} > 0 ) {
- $pkg_opt{'setup_fee'} = $opt{'amount'};
- $pkg_opt_modified = 1;
-
- }
} # else simply ignore them; the UI shouldn't allow editing the fields
my $error;
$reasonnum = $reason->reasonnum;
} else {
- return "Unparsable reason: ". $options{'reason'};
+ return "Unparseable reason: ". $options{'reason'};
}
my $cust_pkg_reason =
'agentnum' => $agentnum,
'detail' => 1
);
- my $row = $report->[$rownum]
+ my $pkgnums = $report->{detail}->[$rownum]
or die "row $rownum is past the end of the report";
- my $pkgnums = $row->[-1] || '0';
# '0' so that if there are no pkgnums (empty string) it will create
# a valid query that returns nothing
warn "PKGNUMS:\n$pkgnums\n\n"; # XXX debug