X-Git-Url: http://git.freeside.biz/gitweb/?p=freeside.git;a=blobdiff_plain;f=FS%2FFS%2Fcust_credit.pm;h=e4b1fc07d013b5646fa3cc5ff2b28b1652e71cae;hp=01ee89dc04ed39b174d11ef2661043f3111c03fe;hb=34c878349988d97957f1d53427896a4d70afb392;hpb=09f899143460b0e99388ef007ff262f9a5e80203 diff --git a/FS/FS/cust_credit.pm b/FS/FS/cust_credit.pm index 01ee89dc0..e4b1fc07d 100644 --- a/FS/FS/cust_credit.pm +++ b/FS/FS/cust_credit.pm @@ -3,13 +3,12 @@ use base qw( FS::otaker_Mixin FS::cust_main_Mixin FS::reason_Mixin 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; @@ -35,13 +34,11 @@ $ignore_empty_reasonnum = 0; $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 @@ -212,16 +209,8 @@ sub insert { $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; @@ -277,35 +266,6 @@ sub delete { 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; ''; @@ -355,6 +315,7 @@ sub check { || $self->ut_foreign_keyn('commission_agentnum', 'agent', 'agentnum') || $self->ut_foreign_keyn('commission_salesnum', 'sales', 'salesnum') || $self->ut_foreign_keyn('commission_pkgnum', 'cust_pkg', 'pkgnum') + || $self->ut_foreign_keyn('commission_invnum', 'cust_bill', 'invnum') ; return $error if $error; @@ -408,14 +369,14 @@ sub void { 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; @@ -754,19 +715,31 @@ sub calculate_tax_adjustment { $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 ( @@ -781,7 +754,16 @@ sub calculate_tax_adjustment { 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, @@ -822,8 +804,6 @@ Example: =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 ) = @_; @@ -887,7 +867,6 @@ sub credit_lineitems { # 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}}; @@ -1041,6 +1020,58 @@ sub credit_lineitems { } +### refund_to_unapply/unapply_refund false laziness with FS::cust_pay + +=item refund_to_unapply + +Returns L objects that will be deleted by L +(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. + +=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 @@ -1057,12 +1088,47 @@ use FS::cust_credit_bill; sub process_batch_import { my $job = shift; + # some false laziness with FS::cust_pay::process_batch_import + my $hashcb = sub { + my %hash = @_; + my $custnum = $hash{'custnum'}; + my $agent_custid = $hash{'agent_custid'}; + # translate agent_custid into regular custnum + if ($custnum && $agent_custid) { + die "can't specify both custnum and agent_custid\n"; + } elsif ($agent_custid) { + # here is the agent virtualization + my $extra_sql = ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql; + my %search; + $search{'agent_custid'} = $agent_custid + if $agent_custid; + $search{'custnum'} = $custnum + if $custnum; + my $cust_main = qsearchs({ + 'table' => 'cust_main', + 'hashref' => \%search, + 'extra_sql' => $extra_sql, + }); + die "can't find customer with" . + ($custnum ? " custnum $custnum" : '') . + ($agent_custid ? " agent_custid $agent_custid" : '') . "\n" + unless $cust_main; + die "mismatched customer number\n" + if $custnum && ($custnum ne $cust_main->custnum); + $custnum = $cust_main->custnum; + } + $hash{'custnum'} = $custnum; + delete($hash{'agent_custid'}); + return %hash; + }; + my $opt = { 'table' => 'cust_credit', 'params' => [ '_date', 'credbatch' ], 'formats' => { 'simple' => - [ 'custnum', 'amount', 'reasonnum', 'invnum' ], + [ 'custnum', 'amount', 'reasonnum', 'invnum', 'agent_custid' ], }, 'default_csv' => 1, + 'format_hash_callbacks' => { 'simple' => $hashcb }, 'postinsert_callback' => sub { my $cust_credit = shift; #my ($cust_credit, $param ) = @_;