FS::Record );
use strict;
-use vars qw( $conf $unsuspendauto $me $DEBUG
+use vars qw( $conf $me $DEBUG
$otaker_upgrade_kludge $ignore_empty_reasonnum
);
use List::Util qw( min );
use Date::Format;
use FS::UID qw( dbh );
-use FS::Misc qw(send_email);
use FS::Record qw( qsearch qsearchs dbdef );
use FS::CurrentUser;
use FS::cust_pkg;
$FS::UID::callback{'FS::cust_credit'} = sub {
$conf = new FS::Conf;
- $unsuspendauto = $conf->exists('unsuspendauto');
};
our %reasontype_map = ( 'referral_credit_type' => 'Referral Credit',
'cancel_credit_type' => 'Cancellation Credit',
- 'signup_credit_type' => 'Self-Service Credit',
);
=head1 NAME
$dbh->commit or die $dbh->errstr if $oldAutoCommit;
- #false laziness w/ cust_pay::insert
- if ( $unsuspendauto && $old_balance && $cust_main->balance <= 0 ) {
- my @errors = $cust_main->unsuspend;
- #return
- # side-fx with nested transactions? upstack rolls back?
- warn "WARNING:Errors unsuspending customer ". $cust_main->custnum. ": ".
- join(' / ', @errors)
- if @errors;
- }
- #eslaf
+ # possibly trigger package unsuspend, doesn't abort transaction on failure
+ $self->unsuspend_balance if $old_balance;
$dbh->commit or die $dbh->errstr if $oldAutoCommit;
return $error;
}
- if ( !$opt{void} and $conf->config('deletecredits') ne '' ) {
-
- my $cust_main = $self->cust_main;
-
- my $error = send_email(
- 'from' => $conf->invoice_from_full($self->cust_main->agentnum),
- #invoice_from??? well as good as any
- 'to' => $conf->config('deletecredits'),
- 'subject' => 'FREESIDE NOTIFICATION: Credit deleted',
- 'body' => [
- "This is an automatic message from your Freeside installation\n",
- "informing you that the following credit has been deleted:\n",
- "\n",
- 'crednum: '. $self->crednum. "\n",
- 'custnum: '. $self->custnum.
- " (". $cust_main->last. ", ". $cust_main->first. ")\n",
- 'amount: $'. sprintf("%.2f", $self->amount). "\n",
- 'date: '. time2str("%a %b %e %T %Y", $self->_date). "\n",
- 'reason: '. $self->reason. "\n",
- ],
- );
-
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return "can't send credit deletion notification: $error";
- }
-
- }
-
$dbh->commit or die $dbh->errstr if $oldAutoCommit;
'';
my $cust_credit_void = new FS::cust_credit_void ( {
map { $_ => $self->get($_) } $self->fields
} );
- $cust_credit_void->set('void_reasonnum', $reason->reasonnum);
+ $cust_credit_void->set('void_reasonnum', $reason->reasonnum) if $reason;
my $error = $cust_credit_void->insert;
if ( $error ) {
$dbh->rollback if $oldAutoCommit;
return $error;
}
- $error = $self->delete(void => 1); # suppress deletecredits warning
+ $error = $self->delete();
if ( $error ) {
$dbh->rollback if $oldAutoCommit;
return $error;
$cust_bill{ $invnum}->custnum == $arg{custnum}
or die "lineitem #$billpkgnum not found\n";
- # calculate credit ratio.
- # (First deduct any existing credits applied to this line item, to avoid
- # rounding errors.)
- my $charged = $cust_bill_pkg->get($setuprecur);
- my $previously_credited =
- $cust_bill_pkg->credited( '', '', setuprecur => $setuprecur) || 0;
+ # tax_Xlocation records don't distinguish setup and recur, so calculate
+ # the fraction of setup+recur (after deducting credits) that's setup. This
+ # will also be the fraction of tax (after deducting credits) that's tax on
+ # setup.
+ my ($setup, $recur);
+ $setup = $cust_bill_pkg->get('setup') || 0;
+ if ($setup) {
+ $setup -= $cust_bill_pkg->credited('', '', setuprecur => 'setup') || 0;
+ }
+ $recur = $cust_bill_pkg->get('recur') || 0;
+ if ($recur) {
+ $recur -= $cust_bill_pkg->credited('', '', setuprecur => 'recur') || 0;
+ }
+ my $setup_ratio = $setup / ($setup + $recur);
+
+ # Calculate the fraction of tax to credit: it's the fraction of this charge
+ # (either setup or recur) that's being credited.
+ my $charged = ($setuprecur eq 'setup') ? $setup : $recur;
+ next if $charged == 0; # shouldn't happen, but still...
- $charged -= $previously_credited;
if ($charged < $amount) {
$error = "invoice #$invnum: tried to credit $amount, but only $charged was charged";
last;
}
- my $ratio = $amount / $charged;
+ my $credit_ratio = $amount / $charged;
# gather taxes that apply to the selected item
foreach my $table (
foreach ($tax_link->cust_credit_bill_pkg) {
$tax_amount -= $_->amount;
}
- my $tax_credit = sprintf('%.2f', $tax_amount * $ratio);
+ # split tax amount based on setuprecur
+ # (this method ensures that, if you credit both setup and recur tax,
+ # it always equals the entire tax despite any rounding)
+ my $setup_tax = sprintf('%.2f', $tax_amount * $setup_ratio);
+ if ( $setuprecur eq 'setup' ) {
+ $tax_amount = $setup_tax;
+ } else {
+ $tax_amount = $tax_amount - $setup_tax;
+ }
+ my $tax_credit = sprintf('%.2f', $tax_amount * $credit_ratio);
my $pkey = $tax_link->get($tax_link->primary_key);
push @taxlines, {
table => $table,
=cut
-use Data::Dumper; #XXX
-
#maybe i should just be an insert with extra args instead of a class method
sub credit_lineitems {
my( $class, %arg ) = @_;
# determine the tax adjustments
my %tax_adjust = $class->calculate_tax_adjustment(%arg);
- warn Dumper \%arg;
foreach my $billpkgnum ( @{$arg{billpkgnums}} ) {
my $setuprecur = shift @{$arg{setuprecurs}};
my $amount = shift @{$arg{amounts}};
}
+### refund_to_unapply/unapply_refund false laziness with FS::cust_pay
+
+=item refund_to_unapply
+
+Returns L<FS::cust_credit_refund> objects that will be deleted by L</unapply_refund>
+(all currently applied refunds that aren't closed.)
+Returns empty list if credit itself is closed.
+
+=cut
+
+sub refund_to_unapply {
+ my $self = shift;
+ return () if $self->closed;
+ qsearch({
+ 'table' => 'cust_credit_refund',
+ 'hashref' => { 'crednum' => $self->crednum },
+ 'addl_from' => 'LEFT JOIN cust_refund USING (refundnum)',
+ 'extra_sql' => "AND cust_refund.closed IS NULL AND cust_refund.source_paynum IS NULL",
+ });
+}
+
+=item unapply_refund
+
+Deletes all objects returned by L</refund_to_unapply>.
+
+=cut
+
+sub unapply_refund {
+ my $self = shift;
+
+ local $SIG{HUP} = 'IGNORE';
+ local $SIG{INT} = 'IGNORE';
+ local $SIG{QUIT} = 'IGNORE';
+ local $SIG{TERM} = 'IGNORE';
+ local $SIG{TSTP} = 'IGNORE';
+ local $SIG{PIPE} = 'IGNORE';
+
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+
+ foreach my $cust_credit_refund ($self->refund_to_unapply) {
+ my $error = $cust_credit_refund->delete;
+ if ($error) {
+ dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
+ dbh->commit or die dbh->errstr if $oldAutoCommit;
+ return '';
+}
+
=back
=head1 SUBROUTINES