LP#1380803 Update PO summary amounts
[working/Evergreen.git] / Open-ILS / src / perlmods / lib / OpenILS / Application / Acq / Financials.pm
index acdcd31..5d74b11 100644 (file)
@@ -17,9 +17,9 @@ my $U = 'OpenILS::Application::AppUtils';
 # ----------------------------------------------------------------------------
 
 __PACKAGE__->register_method(
-       method => 'create_funding_source',
-       api_name        => 'open-ils.acq.funding_source.create',
-       signature => {
+    method => 'create_funding_source',
+    api_name    => 'open-ils.acq.funding_source.create',
+    signature => {
         desc => 'Creates a new funding_source',
         params => [
             {desc => 'Authentication token', type => 'string'},
@@ -41,9 +41,9 @@ sub create_funding_source {
 
 
 __PACKAGE__->register_method(
-       method => 'delete_funding_source',
-       api_name        => 'open-ils.acq.funding_source.delete',
-       signature => {
+    method => 'delete_funding_source',
+    api_name    => 'open-ils.acq.funding_source.delete',
+    signature => {
         desc => 'Deletes a funding_source',
         params => [
             {desc => 'Authentication token', type => 'string'},
@@ -65,10 +65,10 @@ sub delete_funding_source {
 }
 
 __PACKAGE__->register_method(
-       method => 'retrieve_funding_source',
-       api_name        => 'open-ils.acq.funding_source.retrieve',
+    method => 'retrieve_funding_source',
+    api_name    => 'open-ils.acq.funding_source.retrieve',
     authoritative => 1,
-       signature => {
+    signature => {
         desc => 'Retrieves a new funding_source',
         params => [
             {desc => 'Authentication token', type => 'string'},
@@ -100,10 +100,10 @@ sub retrieve_funding_source {
 }
 
 __PACKAGE__->register_method(
-       method => 'retrieve_org_funding_sources',
-       api_name        => 'open-ils.acq.funding_source.org.retrieve',
+    method => 'retrieve_org_funding_sources',
+    api_name    => 'open-ils.acq.funding_source.org.retrieve',
     stream => 1,
-       signature => {
+    signature => {
         desc => 'Retrieves all the funding_sources associated with an org unit that the requestor has access to see',
         params => [
             {desc => 'Authentication token', type => 'string'},
@@ -156,9 +156,9 @@ sub retrieve_funding_source_summary_impl {
 
 
 __PACKAGE__->register_method(
-       method => 'create_funding_source_credit',
-       api_name        => 'open-ils.acq.funding_source_credit.create',
-       signature => {
+    method => 'create_funding_source_credit',
+    api_name    => 'open-ils.acq.funding_source_credit.create',
+    signature => {
         desc => 'Create a new funding source credit',
         params => [
             {desc => 'Authentication token', type => 'string'},
@@ -188,9 +188,9 @@ sub create_funding_source_credit {
 # ---------------------------------------------------------------
 
 __PACKAGE__->register_method(
-       method => 'create_fund',
-       api_name        => 'open-ils.acq.fund.create',
-       signature => {
+    method => 'create_fund',
+    api_name    => 'open-ils.acq.fund.create',
+    signature => {
         desc => 'Creates a new fund',
         params => [
             {desc => 'Authentication token', type => 'string'},
@@ -212,9 +212,9 @@ sub create_fund {
 
 
 __PACKAGE__->register_method(
-       method => 'delete_fund',
-       api_name        => 'open-ils.acq.fund.delete',
-       signature => {
+    method => 'delete_fund',
+    api_name    => 'open-ils.acq.fund.delete',
+    signature => {
         desc => 'Deletes a fund',
         params => [
             {desc => 'Authentication token', type => 'string'},
@@ -236,10 +236,10 @@ sub delete_fund {
 }
 
 __PACKAGE__->register_method(
-       method => 'retrieve_fund',
-       api_name        => 'open-ils.acq.fund.retrieve',
+    method => 'retrieve_fund',
+    api_name    => 'open-ils.acq.fund.retrieve',
     authoritative => 1,
-       signature => {
+    signature => {
         desc => 'Retrieves a new fund',
         params => [
             {desc => 'Authentication token', type => 'string'},
@@ -272,10 +272,10 @@ sub retrieve_fund {
 }
 
 __PACKAGE__->register_method(
-       method => 'retrieve_org_funds',
-       api_name        => 'open-ils.acq.fund.org.retrieve',
+    method => 'retrieve_org_funds',
+    api_name    => 'open-ils.acq.fund.org.retrieve',
     stream => 1,
-       signature => {
+    signature => {
         desc => 'Retrieves all the funds associated with an org unit',
         params => [
             {desc => 'Authentication token', type => 'string'},
@@ -292,8 +292,8 @@ __PACKAGE__->register_method(
 );
 
 __PACKAGE__->register_method(
-       method => 'retrieve_org_funds',
-       api_name        => 'open-ils.acq.fund.org.years.retrieve');
+    method => 'retrieve_org_funds',
+    api_name    => 'open-ils.acq.fund.org.years.retrieve');
 
 
 sub retrieve_org_funds {
@@ -305,7 +305,7 @@ sub retrieve_org_funds {
 
     my $limit_perm = ($$options{limit_perm}) ? $$options{limit_perm} : 'ADMIN_FUND';
     return OpenILS::Event->new('BAD_PARAMS') 
-        unless $limit_perm =~ /(ADMIN|MANAGE|VIEW)_FUND/;
+        unless $limit_perm =~ /(ADMIN|MANAGE|VIEW)_(ACQ_)?FUND/;
 
     $filter->{org}  = $filter->{org} || 
         $U->user_has_work_perm_at($e, $limit_perm, {descendants =>1});
@@ -345,10 +345,10 @@ sub retrieve_org_funds {
 }
 
 __PACKAGE__->register_method(
-       method => 'retrieve_fund_summary',
-       api_name        => 'open-ils.acq.fund.summary.retrieve',
+    method => 'retrieve_fund_summary',
+    api_name    => 'open-ils.acq.fund.summary.retrieve',
     authoritative => 1,
-       signature => {
+    signature => {
         desc => 'Returns a summary of credits/debits/encumbrances for a fund',
         params => [
             {desc => 'Authentication token', type => 'string'},
@@ -389,9 +389,9 @@ sub retrieve_fund_summary_impl {
 }
 
 __PACKAGE__->register_method(
-       method => 'transfer_money_between_funds',
-       api_name        => 'open-ils.acq.funds.transfer_money',
-       signature => {
+    method => 'transfer_money_between_funds',
+    api_name    => 'open-ils.acq.funds.transfer_money',
+    signature => {
         desc => 'Method for transfering money between funds',
         params => [
             {desc => 'Authentication token', type => 'string'},
@@ -415,23 +415,23 @@ sub transfer_money_between_funds {
     return $e->die_event unless $e->allowed(['ADMIN_FUND','MANAGE_FUND'], $dfund->org, $dfund);
 
     if (!defined $dfund_amount) {
-        my $ratio = 1;
+
         if ($ofund->currency_type ne $dfund->currency_type) {
-            my $exchange_rate = $e->json_query({
-                "select"=>{"acqexr"=>["ratio"]}, 
-                "from"=>"acqexr", 
-                "where"=>{
-                    "from_currency"=>$ofund->currency_type,
-                    "to_currency"=>$dfund->currency_type
-                }
-            });
-            if (scalar(@$exchange_rate)<1) {
-                $logger->error('Unable to find exchange rate for ' . $ofund->currency_type . ' to ' . $dfund->currency_type);
-                return $e->die_event;
-            }
-            $ratio = @{$exchange_rate}[0]->{ratio};
+
+            $dfund_amount = $e->json_query({
+                from => [
+                    'acq.exchange_ratio',
+                    $ofund->currency_type,
+                    $dfund->currency_type,
+                    $ofund_amount
+                ]
+            })->[0]->{'acq.exchange_ratio'};
+
+        } else {
+
+            $dfund_amount = $ofund_amount;
         }
-        $dfund_amount = $ofund_amount * $ratio;
+
     } else {
         return $e->die_event unless $e->allowed("ACQ_XFER_MANUAL_DFUND_AMOUNT");
     }
@@ -455,9 +455,9 @@ sub transfer_money_between_funds {
 # ---------------------------------------------------------------
 
 __PACKAGE__->register_method(
-       method => 'create_fund_alloc',
-       api_name        => 'open-ils.acq.fund_allocation.create',
-       signature => {
+    method => 'create_fund_alloc',
+    api_name    => 'open-ils.acq.fund_allocation.create',
+    signature => {
         desc => 'Creates a new fund_allocation',
         params => [
             {desc => 'Authentication token', type => 'string'},
@@ -489,9 +489,9 @@ sub create_fund_alloc {
 
 
 __PACKAGE__->register_method(
-       method => 'delete_fund_alloc',
-       api_name        => 'open-ils.acq.fund_allocation.delete',
-       signature => {
+    method => 'delete_fund_alloc',
+    api_name    => 'open-ils.acq.fund_allocation.delete',
+    signature => {
         desc => 'Deletes a fund_allocation',
         params => [
             {desc => 'Authentication token', type => 'string'},
@@ -521,10 +521,10 @@ sub delete_fund_alloc {
 }
 
 __PACKAGE__->register_method(
-       method => 'retrieve_fund_alloc',
-       api_name        => 'open-ils.acq.fund_allocation.retrieve',
+    method => 'retrieve_fund_alloc',
+    api_name    => 'open-ils.acq.fund_allocation.retrieve',
     authoritative => 1,
-       signature => {
+    signature => {
         desc => 'Retrieves a new fund_allocation',
         params => [
             {desc => 'Authentication token', type => 'string'},
@@ -552,10 +552,10 @@ sub retrieve_fund_alloc {
 
 
 __PACKAGE__->register_method(
-       method => 'retrieve_funding_source_allocations',
-       api_name        => 'open-ils.acq.funding_source.allocations.retrieve',
+    method => 'retrieve_funding_source_allocations',
+    api_name    => 'open-ils.acq.funding_source.allocations.retrieve',
     authoritative => 1,
-       signature => {
+    signature => {
         desc => 'Retrieves a new fund_allocation',
         params => [
             {desc => 'Authentication token', type => 'string'},
@@ -586,10 +586,10 @@ sub retrieve_funding_source_allocations {
 # ----------------------------------------------------------------------------
 
 __PACKAGE__->register_method(
-       method => 'retrieve_all_currency_type',
-       api_name        => 'open-ils.acq.currency_type.all.retrieve',
+    method => 'retrieve_all_currency_type',
+    api_name    => 'open-ils.acq.currency_type.all.retrieve',
     stream => 1,
-       signature => {
+    signature => {
         desc => 'Retrieves all currency_type objects',
         params => [
             {desc => 'Authentication token', type => 'string'},
@@ -607,9 +607,9 @@ sub retrieve_all_currency_type {
 }
 
 __PACKAGE__->register_method(
-       method => 'create_lineitem_assets',
-       api_name        => 'open-ils.acq.lineitem.assets.create',
-       signature => {
+    method => 'create_lineitem_assets',
+    api_name    => 'open-ils.acq.lineitem.assets.create',
+    signature => {
         desc => q/Creates the bibliographic data, volume, and copies associated with a lineitem./,
         params => [
             {desc => 'Authentication token', type => 'string'},
@@ -729,10 +729,10 @@ sub create_purchase_order_impl {
 
 
 __PACKAGE__->register_method(
-       method => 'retrieve_all_user_purchase_order',
-       api_name        => 'open-ils.acq.purchase_order.user.all.retrieve',
+    method => 'retrieve_all_user_purchase_order',
+    api_name    => 'open-ils.acq.purchase_order.user.all.retrieve',
     stream => 1,
-       signature => {
+    signature => {
         desc => 'Retrieves a purchase order',
         params => [
             {desc => 'Authentication token', type => 'string'},
@@ -762,7 +762,7 @@ sub retrieve_all_user_purchase_order {
 
     # grab purchase orders I have 
     my $perm_orgs = $U->user_has_work_perm_at($e, 'MANAGE_PROVIDER', {descendants =>1});
-       return OpenILS::Event->new('PERM_FAILURE', ilsperm => 'MANAGE_PROVIDER')
+    return OpenILS::Event->new('PERM_FAILURE', ilsperm => 'MANAGE_PROVIDER')
         unless @$perm_orgs;
     my $provider_ids = $e->search_acq_provider({owner => $perm_orgs}, {idlist=>1});
     my $po_ids = $e->search_acq_purchase_order({provider => $provider_ids}, {idlist=>1});
@@ -789,10 +789,10 @@ sub retrieve_all_user_purchase_order {
 
 
 __PACKAGE__->register_method(
-       method => 'search_purchase_order',
-       api_name        => 'open-ils.acq.purchase_order.search',
+    method => 'search_purchase_order',
+    api_name    => 'open-ils.acq.purchase_order.search',
     stream => 1,
-       signature => {
+    signature => {
         desc => 'Search for a purchase order',
         params => [
             {desc => 'Authentication token', type => 'string'},
@@ -821,6 +821,7 @@ __PACKAGE__->register_method(
         method    => 'retrieve_purchase_order',
         api_name  => 'open-ils.acq.purchase_order.retrieve',
         stream    => 1,
+        authoritative => 1,
         signature => {
                       desc      => 'Retrieves a purchase order',
                       params    => [
@@ -870,46 +871,95 @@ sub po_perm_failure {
 sub build_price_summary {
     my ($e, $po_id) = @_;
 
-    # TODO: Add summary value for estimated amount (pre-encumber)
-
-    # fetch the fund debits for this purchase order
-    my $debits = $e->json_query({
-        "select" => {"acqfdeb" => [qw/encumbrance amount/]},
-        "from" => {
-            "acqlid" => {
-                "jub" => {
-                    "fkey" => "lineitem",
-                    "field" => "id",
-                    "join" => {
-                        "acqpo" => {
-                            "fkey" => "purchase_order", "field" => "id"
+    # amounts for lineitems / lineitem_details
+    my $li_data = $e->json_query({
+        select => {
+            jub => [
+                'estimated_unit_price', 
+                {column => 'id', alias => 'li_id'}
+            ],
+            acqlid => [
+                # lineitem_detail.id is needed to ensure we have one 
+                # "row" of data for every copy, regardless of whether
+                # a fund_debit exists for each copy.
+                {column => 'id', alias => 'lid_id'}
+            ],
+            acqfdeb => [
+                'encumbrance', 
+                {column => 'amount', alias => 'debit_amount'}
+            ]
+        }, 
+        from => {
+            jub => {
+                acqlid => {
+                    fkey => 'id',
+                    field => 'lineitem',
+                    join => {
+                        acqfdeb => {
+                            type => 'left',
+                            fkey => 'fund_debit',
+                            field => 'id'
                         }
                     }
-                },
-                "acqfdeb" => {"fkey" => "fund_debit", "field" => "id"}
+                }
             }
         },
-        "where" => {"+acqpo" => {"id" => $po_id}}
+        where => {'+jub' => {purchase_order => $po_id}}
     });
 
-    # add any debits for non-bib po_items
-    push(@$debits, @{
-        $e->json_query({
-            "select" => {"acqfdeb" => [qw/encumbrance amount/]},
-            "from" => {acqpoi => 'acqfdeb'},
-            "where" => {"+acqpoi" => {"purchase_order" => $po_id}}
-        })
+    # amounts for po_item's
+    my $item_data = $e->json_query({
+        select => {
+            acqpoi => ['estimated_cost'],
+            acqfdeb => [
+                'encumbrance', 
+                {column => 'amount', alias => 'debit_amount'}
+            ]
+        },
+        from => {
+            acqpoi => {
+                acqfdeb => {
+                    type => 'left',
+                    fkey => 'fund_debit',
+                    field => 'id'
+                }
+            }
+        },
+        where => {'+acqpoi' => {purchase_order => $po_id}}
     });
+                   
+    # sum amounts debited (for activated PO's) and amounts estimated 
+    # (for pending PO's) for all lineitem_details and po_items.
+
+    my ($enc, $spent, $estimated) = (0, 0, 0);
+
+    for my $deb (@$li_data, @$item_data) {
+
+        if (defined $deb->{debit_amount}) { # could be $0
+            # we have a debit, treat it as authoritative.
+
+            # estimated amount includes all amounts encumbered or spent
+            $estimated += $deb->{debit_amount};
+
+            if($U->is_true($deb->{encumbrance})) {
+                $enc += $deb->{debit_amount};
+            } else {
+                $spent += $deb->{debit_amount};
+            }
 
-    my ($enc, $spent) = (0, 0);
-    for my $deb (@$debits) {
-        if($U->is_true($deb->{encumbrance})) {
-            $enc += $deb->{amount};
         } else {
-            $spent += $deb->{amount};
+            # PO is not activated, so sum estimated costs.
+            # There will be one $deb object for every lineitem_detail 
+            # and po_item.  Adding the estimated costs for all gives 
+            # us the total esimated amount.
+
+            $estimated += (
+                $deb->{estimated_unit_price} || $deb->{estimated_cost} || 0
+            );
         }
     }
-    ($enc, $spent);
+
+    return ($enc, $spent, $estimated);
 }
 
 
@@ -971,9 +1021,10 @@ sub retrieve_purchase_order_impl {
     }
 
     if($$options{flesh_price_summary}) {
-        my ($enc, $spent) = build_price_summary($e, $po_id);
+        my ($enc, $spent, $estimated) = build_price_summary($e, $po_id);
         $po->amount_encumbered($enc);
         $po->amount_spent($spent);
+        $po->amount_estimated($estimated);
     }
 
     return $po;
@@ -981,8 +1032,8 @@ sub retrieve_purchase_order_impl {
 
 
 __PACKAGE__->register_method(
-       method => 'format_po',
-       api_name        => 'open-ils.acq.purchase_order.format'
+    method => 'format_po',
+    api_name    => 'open-ils.acq.purchase_order.format'
 );
 
 sub format_po {
@@ -998,8 +1049,8 @@ sub format_po {
 }
 
 __PACKAGE__->register_method(
-       method => 'format_lineitem',
-       api_name        => 'open-ils.acq.lineitem.format'
+    method => 'format_lineitem',
+    api_name    => 'open-ils.acq.lineitem.format'
 );
 
 sub format_lineitem {
@@ -1139,12 +1190,12 @@ sub po_events {
 }
 
 __PACKAGE__->register_method (
-       method          => 'update_po_events',
+    method      => 'update_po_events',
     api_name    => 'open-ils.acq.purchase_order.event.cancel.batch',
     stream      => 1,
 );
 __PACKAGE__->register_method (
-       method          => 'update_po_events',
+    method      => 'update_po_events',
     api_name    => 'open-ils.acq.purchase_order.event.reset.batch',
     stream      => 1,
 );
@@ -1191,10 +1242,10 @@ sub update_po_events {
 
 
 __PACKAGE__->register_method (
-       method          => 'process_fiscal_rollover',
+    method      => 'process_fiscal_rollover',
     api_name    => 'open-ils.acq.fiscal_rollover.combined',
     stream      => 1,
-       signature => {
+    signature => {
         desc => q/
             Performs a combined fiscal fund rollover process.
 
@@ -1221,6 +1272,7 @@ __PACKAGE__->register_method (
             {desc => 'Fund Year to roll over', type => 'integer'},
             {desc => 'Org unit ID', type => 'integer'},
             {desc => 'Include Descendant Orgs (boolean)', type => 'integer'},
+            {desc => 'Option hash: limit, offset, encumb_only', type => 'object'},
         ],
         return => {desc => 'Returns a stream of all related funds for the next year including fund summary for each'}
     }
@@ -1228,10 +1280,10 @@ __PACKAGE__->register_method (
 );
 
 __PACKAGE__->register_method (
-       method          => 'process_fiscal_rollover',
+    method      => 'process_fiscal_rollover',
     api_name    => 'open-ils.acq.fiscal_rollover.combined.dry_run',
     stream      => 1,
-       signature => {
+    signature => {
         desc => q/
             @see open-ils.acq.fiscal_rollover.combined
             This is the dry-run version.  The action is performed,
@@ -1242,10 +1294,10 @@ __PACKAGE__->register_method (
 );
 
 __PACKAGE__->register_method (
-       method          => 'process_fiscal_rollover',
+    method      => 'process_fiscal_rollover',
     api_name    => 'open-ils.acq.fiscal_rollover.propagate',
     stream      => 1,
-       signature => {
+    signature => {
         desc => q/
             @see open-ils.acq.fiscal_rollover.combined
             This version performs fund propagation only.  I.e, creation of
@@ -1256,10 +1308,10 @@ __PACKAGE__->register_method (
 );
 
 __PACKAGE__->register_method (
-       method          => 'process_fiscal_rollover',
+    method      => 'process_fiscal_rollover',
     api_name    => 'open-ils.acq.fiscal_rollover.propagate.dry_run',
     stream      => 1,
-       signature => { desc => q/ 
+    signature => { desc => q/ 
         @see open-ils.acq.fiscal_rollover.propagate 
         This is the dry-run version.  The action is performed,
         new fund information is returned, then all changes are rolled back.
@@ -1277,6 +1329,7 @@ sub process_fiscal_rollover {
     $options ||= {};
 
     my $combined = ($self->api_name =~ /combined/); 
+    my $encumb_only = $U->is_true($options->{encumb_only}) ? 't' : 'f';
 
     my $org_ids = ($descendants) ? 
         [   
@@ -1308,22 +1361,16 @@ sub process_fiscal_rollover {
                 ($descendants) ? 
                     'acq.rollover_funds_by_org_tree' :
                     'acq.rollover_funds_by_org_unit',
-                $year, $e->requestor->id, $org_id
+                $year, $e->requestor->id, $org_id, $encumb_only
             ]
         });
     }
 
     # Fetch all funds for the specified org units for the subsequent year
-    my $fund_ids = $e->search_acq_fund([
-        {
-            year => int($year) + 1, 
+    my $fund_ids = $e->search_acq_fund(
+        [{  year => int($year) + 1, 
             org => $org_ids,
-            propagate => 't'
-        }, {
-            limit => $$options{limit} || 20,
-            offset => $$options{offset} || 0,
-        }
-        ], 
+            propagate => 't' }], 
         {idlist => 1}
     );
 
@@ -1338,7 +1385,7 @@ sub process_fiscal_rollover {
             my $sum = $e->json_query({
                 select => {acqftr => [{column => 'dest_amount', transform => 'sum'}]}, 
                 from => 'acqftr', 
-                where => {dest_fund => $fund->id, note => 'Rollover'}
+                where => {dest_fund => $fund->id, note => { like => 'Rollover%' } }
             })->[0];
 
             $amount = $sum->{dest_amount} if $sum;
@@ -1351,6 +1398,43 @@ sub process_fiscal_rollover {
     return undef;
 }
 
+__PACKAGE__->register_method(
+    method => 'org_fiscal_year',
+    api_name    => 'open-ils.acq.org_unit.current_fiscal_year',
+    signature => {
+        desc => q/
+            Returns the current fiscal year for the given org unit.
+            If no fiscal year is configured, the current calendar
+            year is returned.
+        /,
+        params => [
+            {desc => 'Authentication token', type => 'string'},
+            {desc => 'Org unit ID', type => 'number'}
+        ],
+        return => {desc => 'Year as a string (e.g. "2012")'}
+    }
+);
+
+sub org_fiscal_year {
+    my($self, $conn, $auth, $org_id) = @_;
+
+    my $e = new_editor(authtoken => $auth);
+    return $e->event unless $e->checkauth;
+
+    my $year = $e->json_query({
+        select => {acqfy => ['year']},
+        from => {acqfy => {acqfc => {join => 'aou'}}},
+        where => {
+            '+acqfy' => {
+                year_begin => {'<=' => 'now'},
+                year_end => {'>=' => 'now'},
+            },
+            '+aou' => {id => $org_id}
+        }
+    })->[0];
+
+    return $year ? $year->{year} : DateTime->now->year;
+}
 
 1;