X-Git-Url: http://git.freeside.biz/gitweb/?p=freeside.git;a=blobdiff_plain;f=FS%2FFS%2Fcust_credit.pm;h=4860315e8b8920237ecefab6ed12065d00177d74;hp=032953532fa7e507b38ee66b826f1532d47ef053;hb=d03ff57c2254deb9607c802db7454aa894e080fc;hpb=5da68ff1a7c638e30cbafbc9b0749f1e82b333df diff --git a/FS/FS/cust_credit.pm b/FS/FS/cust_credit.pm index 032953532..4860315e8 100644 --- a/FS/FS/cust_credit.pm +++ b/FS/FS/cust_credit.pm @@ -3,7 +3,7 @@ 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 ); @@ -34,7 +34,6 @@ $ignore_empty_reasonnum = 0; $FS::UID::callback{'FS::cust_credit'} = sub { $conf = new FS::Conf; - $unsuspendauto = $conf->exists('unsuspendauto'); }; @@ -210,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; @@ -324,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; @@ -723,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 ( @@ -750,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, @@ -770,7 +783,11 @@ sub calculate_tax_adjustment { ); } -=item credit_lineitems +=item credit_lineitems OPTIONS + +Creates a credit to a group of line items, with a specified amount applied +to each. This will also calculate the tax adjustments for those amounts and +credit the appropriate tax line items. Example: @@ -789,6 +806,16 @@ Example: ); +C, C, C are required and are parallel +arrays. Each one indicates an amount of credit to be applied to either the +setup or recur portion of a (non-tax) line item. + +C, C<_date>, C, and C will be set on the +credit before it's inserted. + +C is the total amount. If unspecified, the credit will be the sum +of the per-line-item amounts and their tax adjustments. + =cut #maybe i should just be an insert with extra args instead of a class method @@ -828,10 +855,18 @@ sub credit_lineitems { my $error = ''; + # first, determine the tax adjustments + my %tax_adjust = $class->calculate_tax_adjustment(%arg); + # and determine the amount automatically if it wasn't specified + if ( !exists( $arg{amount} ) ) { + $arg{amount} = sprintf('%.2f', $tax_adjust{subtotal} + $tax_adjust{taxtotal}); + } + + # create the credit my $cust_credit = new FS::cust_credit ( { map { $_ => $arg{$_} } #fields('cust_credit') - qw( custnum _date amount reasonnum addlinfo ), #pkgnum eventnum + qw( custnum _date amount reason reasonnum addlinfo ), #pkgnum eventnum } ); $error = $cust_credit->insert; if ( $error ) { @@ -851,9 +886,6 @@ sub credit_lineitems { my %cust_credit_bill_pkg = (); my %unapplied_payments = (); #invoice numbers, and then billpaynums - # determine the tax adjustments - my %tax_adjust = $class->calculate_tax_adjustment(%arg); - foreach my $billpkgnum ( @{$arg{billpkgnums}} ) { my $setuprecur = shift @{$arg{setuprecurs}}; my $amount = shift @{$arg{amounts}}; @@ -1007,6 +1039,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 @@ -1023,12 +1107,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 ) = @_;