=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 @cust_credit_source_bill_pkg = ();
+ my $remaining_value = 0;
+
+ my $remain_pkg = $self;
+ $remaining_value = $remain_pkg->calc_remain(
+ 'time' => $time,
+ 'cust_credit_source_bill_pkg' => \@cust_credit_source_bill_pkg,
+ );
+
+ # 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,
+ 'cust_credit_source_bill_pkg' => \@cust_credit_source_bill_pkg,
+ );
+ }
+
if ( $remaining_value > 0 ) {
warn "Crediting for $remaining_value on package ".$self->pkgnum."\n"
if $DEBUG;
$remaining_value,
'Credit for unused time on '. $self->part_pkg->pkg,
'reason_type' => $reason_type,
+ 'cust_credit_source_bill_pkg' => \@cust_credit_source_bill_pkg,
);
return "Error crediting customer \$$remaining_value for unused time".
" on ". $self->part_pkg->pkg. ": $error"
}
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;
if $DEBUG;
my ($end, $start, $mode) = @_;
+
+ local($FS::Record::qsearch_qualify_columns) = 0;
+
my @cust_svc = $self->_sort_cust_svc(
[ qsearch( 'h_cust_svc',
{ 'pkgnum' => $self->pkgnum, },
FS::h_cust_svc->sql_h_search(@_),
) ]
);
+
if ( defined($mode) && $mode eq 'I' ) {
my %hidden_svcpart = map { $_->svcpart => $_->hidden } $self->part_svc;
return grep { !$hidden_svcpart{$_->svcpart} } @cust_svc;
$reasonnum = $reason->reasonnum;
} else {
- return "Unparsable reason: ". $options{'reason'};
+ return "Unparseable reason: ". $options{'reason'};
}
my $cust_pkg_reason =