From b7fa43b1a9a874997396c7c142ea7793f6056c0c Mon Sep 17 00:00:00 2001 From: erickson Date: Thu, 8 Apr 2010 16:45:44 +0000 Subject: [PATCH] implemented invoice processing to updating encumbered funds (for invoice_entry's) and ad-hoc charges, and prorating fees/taxes, etc. (aka invoice_items) git-svn-id: svn://svn.open-ils.org/ILS/trunk@16174 dcc99617-32d9-48b4-a31d-7c20da2025e4 --- .../perlmods/OpenILS/Application/Acq/Invoice.pm | 118 +++++++++++++++++++-- 1 file changed, 109 insertions(+), 9 deletions(-) diff --git a/Open-ILS/src/perlmods/OpenILS/Application/Acq/Invoice.pm b/Open-ILS/src/perlmods/OpenILS/Application/Acq/Invoice.pm index 3e562060a3..1e66c9761b 100644 --- a/Open-ILS/src/perlmods/OpenILS/Application/Acq/Invoice.pm +++ b/Open-ILS/src/perlmods/OpenILS/Application/Acq/Invoice.pm @@ -43,7 +43,7 @@ sub build_invoice_api { $e->update_acq_invoice($invoice) or return $e->die_event; } } else { - # call only provided the ID + # caller only provided the ID $invoice = $e->retrieve_acq_invoice($invoice) or return $e->die_event; } @@ -55,8 +55,10 @@ sub build_invoice_api { if($entry->isnew) { $e->create_acq_invoice_entry($entry) or return $e->die_event; } elsif($entry->isdeleted) { + # TODO set encumbrance=true for related fund_debit and revert back to estimated price $e->delete_acq_invoice_entry($entry) or return $e->die_event; } elsif($entry->ischanged) { + # TODO: update the related fund_debit $e->update_acq_invoice_entry($entry) or return $e->die_event; } } @@ -68,8 +70,14 @@ sub build_invoice_api { if($item->isnew) { $e->create_acq_invoice_item($item) or return $e->die_event; } elsif($item->isdeleted) { + if($item->fund_debit) { + $e->delete_acq_fund_debit( + $e->retrieve_acq_fund_debit($item->fund_debit) + ) or return $e->die_event; + } $e->delete_acq_invoice_item($item) or return $e->die_event; } elsif($item->ischanged) { + # TODO: update related fund debit $e->update_acq_invoice_item($item) or return $e->die_event; } } @@ -184,7 +192,9 @@ sub process_invoice { } } }, - where => {'+acqfdeb' => {encumbrance => 't'}} + where => {'+acqfdeb' => {encumbrance => 't'}}, + order_by => {'acqlid' => ['recv_time']}, + limit => $entry->phys_item_count }); next unless @$debits; @@ -192,16 +202,24 @@ sub process_invoice { if($entry->phys_item_count > @$debits) { $e->rollback; # We can't invoice for more items than we have debits for - return OpenILS::Event->new('ACQ_INVOICE_ENTRY_COUNT_EXCEEDS_DEBITS', payload => {entry => $entry->id}); + return OpenILS::Event->new( + 'ACQ_INVOICE_ENTRY_COUNT_EXCEEDS_DEBITS', payload => {entry => $entry->id}); + } + + my $item_cost = $entry->cost_billed; + unless($U->is_true($entry->billed_per_item)) { + # cost billed is for the whole set of items. Get the + # per-item cost by dividing the total cost by total invoiced + $item_cost = $item_cost / $entry->inv_item_count; } for my $debit_id (map { $_->{id} } @$debits) { my $debit = $e->retrieve_acq_fund_debit($debit_id); - $debit->amount($entry->cost_billed); + $debit->amount($item_cost); $debit->encumbrance('f'); $e->update_acq_fund_debit($debit) or return $e->die_event; $fund_totals{$debit->fund} ||= 0; - $fund_totals{$debit->fund} += $entry->cost_billed; + $fund_totals{$debit->fund} += $item_cost; } } @@ -210,14 +228,96 @@ sub process_invoice { $logger->info("invoice: total bib cost for invoice = $total_entry_cost"); - # collect amount spent per fund to get percents - for my $item (@{$invoice->items}) { - # prorate and create fund debits as appropriate + # future: cache item types locally + my $item_type = $e->retrieve_acq_invoice_item_type($item->inv_item_type) or return $e->die_event; + + if($U->is_true($item_type->prorate)) { + + # Charge prorated across applicable funds + my $full_item_cost = $item->cost_billed; + my $first_round = 1; + my $largest_debit; + my $total_debited = 0; + + for my $fund_id (keys %fund_totals) { + + my $spent_for_fund = $fund_totals{$fund_id}; + next unless $spent_for_fund > 0; + + my $prorated_amount = ($spent_for_fund / $total_entry_cost) * $full_item_cost; + $logger->info("invoice: attaching prorated amount $prorated_amount to fund $fund_id for invoice $invoice_id"); + + my $debit = Fieldmapper::acq::fund_debit->new; + $debit->fund($fund_id); + $debit->amount($prorated_amount); + $debit->origin_amount($prorated_amount); + $debit->origin_currency_type($e->retrieve_acq_fund($fund_id)->currency_type); # future: cache funds locally + $debit->encumbrance('f'); + $debit->debit_type('prorated_charge'); + $e->create_acq_fund_debit($debit) or return $e->die_event; + $total_debited += $prorated_amount; + $largest_debit = $debit if !$largest_debit or $debit->amount > $largest_debit->amount; + + if($first_round) { + + # re-purpose the original invoice_item for the first prorated amount + $item->fund_debit($debit->id); + $item->cost_billed($prorated_amount); + $e->update_acq_invoice_item($item) or return $e->die_event; + + } else { + + # for subsequent prorated amounts, create a new invoice_item + my $new_item = $item->clone; + $new_item->clear_id; + $new_item->fund_debit($debit->id); + $new_item->cost_billed($prorated_amount); + $e->create_acq_invoice_item($new_item) or return $e->die_event; + } + + $first_round = 0; + } + + # make sure the percentages didn't leave a small sliver of money over/under-debited + if($total_debited != $full_item_cost) { + $logger->info("invoice: found prorate descrepency. total_debited=$total_debited; total_cost=$full_item_cost; difference ". ($full_item_cost - $total_debited)); + # tweak the largest debit to smooth out the difference + $largest_debit = $e->retrieve_acq_fund_debit($largest_debit); # get latest copy + $largest_debit->amount( $largest_debit->amount + ($full_item_cost - $total_debited) ); + $largest_debit->origin_amount($largest_debit->amount); + $e->update_acq_fund_debit($largest_debit) or return $e->die_event; + } + + } else { # not prorated + + # Direct charge against a fund + + next if $item->fund_debit; + + unless($item->fund) { + $e->rollback; + return OpenILS::Event->new('ACQ_INVOICE_ITEM_REQUIRES_FUND', payload => {item => $item->id}); + } + + my $debit = Fieldmapper::acq::fund_debit->new; + $debit->fund($item->fund); + $debit->amount($item->cost_billed); + $debit->origin_amount($item->cost_billed); + $debit->origin_currency_type($e->retrieve_acq_fund($item->fund)->currency_type); # future: cache funds locally + $debit->encumbrance('f'); + $debit->debit_type('direct_charge'); + $e->create_acq_fund_debit($debit) or return $e->die_event; + + $item->fund_debit($debit->id); + $e->update_acq_invoice_item($item) or return $e->die_event; + } } - $e->rollback; + $invoice = fetch_invoice_impl($e, $invoice_id); + $e->commit; + return $invoice; } -- 2.11.0