Merging acq-experiment to trunk, since rel_1_4 has been branched.
authorerickson <erickson@dcc99617-32d9-48b4-a31d-7c20da2025e4>
Mon, 20 Oct 2008 19:42:01 +0000 (19:42 +0000)
committererickson <erickson@dcc99617-32d9-48b4-a31d-7c20da2025e4>
Mon, 20 Oct 2008 19:42:01 +0000 (19:42 +0000)
The majority of this is new ACQ-related code, with a few modifications of existing code.

Truncated svnmerge commit message:

Merged revisions 8236,8238-8244,8246-8249,8251-8252,8254-8257,8260,8266-8269,8271,8273,8276-8286,8288-8290,8303,8313,8317-8318,8320-8321,8323-8324,8326,8328-8330,8332-8335,8337-8340,8342,8345,8349-8350,8355-8360,8362-8378,8385-8386,8388,8390-8391,8393-8397,8399-8401,8404,8406-8413,8415-8422,8424-8425,8427-8430,8451,8453-8454,8456,8458,8467,8469-8470,8476,8478,8482-8487,8495,8497,8510,8534,8539-8541,8574,8576,8578-8579,8581,8593-8599,8601,8603,8605,8607-8608,8617,8620-8623,8629,8631,8633,8635,8637-8639,8646-8647,8649-8651,8657-8660,8662,8664,8668,8670-8671,8675-8676,8678,8681-8688,8692,8694-8695,8697-8698,8706,8712,8714-8720,8722,8724,8727-8730,8732-8736,8744-8748,8756-8758,8762-8764,8768-8770,8773-8785,8789-8793,8795-8797,8799-8800,8803-8807,8814-8816,8821-8822,8826-8828,8831-8834,8836,8838-8839,8841,8843,8864-8867,8876-8880,8882,8885,8929,8932,8934-8935,8937-8941,8945-8947,8957-8958,8965,8978-8984,8996-8998,9005-9006,9109,9136,9139,9153,9167,9188,9202,9205,9237-9240,9246,9249,9251,9262-9264,9267-9268,9273-9274,9283,9290,9315-9317,9324,9326,9331-9332,9337,9341,9344,9346,9348,9351,9355,9382-9384,9404-9411,9416-9423,9438,9446,9463,9480,9487-9488,9508-9510,9516-9519,9531,9537-9538,9550-9563,9572-9576,9578-9583,9585-9586,9588,9590-9592,9596-9598,9602-9605,9607-9611,9613,9617-9621,9623-9624,9628,9630,9633,9636-9640,9643-9648,9651,9656-9668,9673-9674,9676,9679-9683,9687-9692,9702,9709-9713,9722,9735,9810,9812,9818-9821,9894,9905,9907,9911,9913,9915-9917,9921-9924,9928-9930,9935,9937-9940,9944,9946,9997,10006-10007,10017-10018,10026,10034-10039,10043-10045,10053-10055,10057-10058,10062,10086,10089-10090,10094-10111,10113-10114,10116-10118,10120,10122,10146,10149,10160,10163,10172,10175-10176,10180-10186,10196-10198,10234-10235,10239,10309,10316-10318,10322,10327-10330,10342,10349-10356,10460-10462,10567,10703,10720,10826,10834-10836,10854-10855,10857,10867 via svnmerge from
svn://svn.open-ils.org/ILS/branches/acq-experiment

git-svn-id: svn://svn.open-ils.org/ILS/trunk@10879 dcc99617-32d9-48b4-a31d-7c20da2025e4

77 files changed:
Open-ILS/examples/apache/eg_vhost.conf
Open-ILS/examples/fm_IDL.xml
Open-ILS/examples/oils_web.xml [new file with mode: 0644]
Open-ILS/src/extras/ils_events.xml
Open-ILS/src/perlmods/OpenILS/Application/Acq.pm [new file with mode: 0644]
Open-ILS/src/perlmods/OpenILS/Application/Acq/Financials.pm [new file with mode: 0644]
Open-ILS/src/perlmods/OpenILS/Application/Acq/Lineitem.pm [new file with mode: 0644]
Open-ILS/src/perlmods/OpenILS/Application/Acq/Picklist.pm [new file with mode: 0644]
Open-ILS/src/perlmods/OpenILS/Application/Acq/Provider.pm [new file with mode: 0644]
Open-ILS/src/perlmods/OpenILS/Const.pm
Open-ILS/src/perlmods/OpenILS/Utils/MFHD.pm [new file with mode: 0755]
Open-ILS/src/perlmods/OpenILS/Utils/MFHD/Caption.pm [new file with mode: 0755]
Open-ILS/src/perlmods/OpenILS/Utils/MFHD/Holding.pm [new file with mode: 0755]
Open-ILS/src/perlmods/OpenILS/Utils/MFHDParser.pm [new file with mode: 0644]
Open-ILS/src/perlmods/OpenILS/WWW/EGWeb.pm [new file with mode: 0644]
Open-ILS/src/sql/Pg/200.schema.acq.sql [new file with mode: 0644]
Open-ILS/src/sql/Pg/210.schema.serials.sql [new file with mode: 0644]
Open-ILS/src/sql/Pg/950.data.seed-values.sql
Open-ILS/src/sql/Pg/build-db.sh
Open-ILS/src/support-scripts/test-scripts/acq_fund.py [new file with mode: 0644]
Open-ILS/web/css/skin/default.css [new file with mode: 0644]
Open-ILS/web/css/skin/default/acq.css [new file with mode: 0644]
Open-ILS/web/css/skin/default/admin.css [new file with mode: 0644]
Open-ILS/web/css/theme/default.css [new file with mode: 0644]
Open-ILS/web/css/theme/default/acq.css [new file with mode: 0644]
Open-ILS/web/css/theme/default/admin.css [new file with mode: 0644]
Open-ILS/web/images/eg_logo.jpg [new file with mode: 0644]
Open-ILS/web/images/eg_tiny_logo.jpg [new file with mode: 0644]
Open-ILS/web/js/dojo/openils/Util.js [new file with mode: 0644]
Open-ILS/web/js/dojo/openils/acq/CurrencyType.js [new file with mode: 0644]
Open-ILS/web/js/dojo/openils/acq/Fund.js [new file with mode: 0644]
Open-ILS/web/js/dojo/openils/acq/FundingSource.js [new file with mode: 0644]
Open-ILS/web/js/dojo/openils/acq/Lineitem.js [new file with mode: 0644]
Open-ILS/web/js/dojo/openils/acq/LineitemAttr.js [new file with mode: 0644]
Open-ILS/web/js/dojo/openils/acq/PO.js [new file with mode: 0644]
Open-ILS/web/js/dojo/openils/acq/Picklist.js [new file with mode: 0644]
Open-ILS/web/js/dojo/openils/acq/Provider.js [new file with mode: 0644]
Open-ILS/web/js/dojo/openils/editors.js [new file with mode: 0644]
Open-ILS/web/js/dojo/openils/widget/FundSelector.js [new file with mode: 0644]
Open-ILS/web/js/dojo/openils/widget/ProviderSelector.js [new file with mode: 0644]
Open-ILS/web/js/ui/base.js [new file with mode: 0644]
Open-ILS/web/js/ui/default/acq/common/jubgrid.js [new file with mode: 0644]
Open-ILS/web/js/ui/default/acq/financial/list_currency_types.js [new file with mode: 0644]
Open-ILS/web/js/ui/default/acq/financial/list_funding_sources.js [new file with mode: 0644]
Open-ILS/web/js/ui/default/acq/financial/list_funds.js [new file with mode: 0644]
Open-ILS/web/js/ui/default/acq/financial/list_providers.js [new file with mode: 0644]
Open-ILS/web/js/ui/default/acq/financial/view_fund.js [new file with mode: 0644]
Open-ILS/web/js/ui/default/acq/financial/view_funding_source.js [new file with mode: 0644]
Open-ILS/web/js/ui/default/acq/financial/view_provider.js [new file with mode: 0644]
Open-ILS/web/js/ui/default/acq/picklist/bib_search.js [new file with mode: 0644]
Open-ILS/web/js/ui/default/acq/picklist/view_list.js [new file with mode: 0644]
Open-ILS/web/js/ui/default/acq/po/li_search.js [new file with mode: 0644]
Open-ILS/web/js/ui/default/acq/po/search.js [new file with mode: 0644]
Open-ILS/web/js/ui/default/acq/po/view_po.js [new file with mode: 0644]
Open-ILS/web/js/ui/default/acq/receiving/process.js [new file with mode: 0644]
Open-ILS/web/js/ui/default/acq/settings/li_attr.js [new file with mode: 0644]
Open-ILS/web/templates/base.tt2 [new file with mode: 0644]
Open-ILS/web/templates/default/acq/common/jubgrid.tt2 [new file with mode: 0644]
Open-ILS/web/templates/default/acq/financial/list_currency_types.tt2 [new file with mode: 0644]
Open-ILS/web/templates/default/acq/financial/list_funding_sources.tt2 [new file with mode: 0644]
Open-ILS/web/templates/default/acq/financial/list_funds.tt2 [new file with mode: 0644]
Open-ILS/web/templates/default/acq/financial/list_providers.tt2 [new file with mode: 0644]
Open-ILS/web/templates/default/acq/financial/view_fund.tt2 [new file with mode: 0644]
Open-ILS/web/templates/default/acq/financial/view_funding_source.tt2 [new file with mode: 0644]
Open-ILS/web/templates/default/acq/financial/view_provider.tt2 [new file with mode: 0644]
Open-ILS/web/templates/default/acq/picklist/bib_search.tt2 [new file with mode: 0644]
Open-ILS/web/templates/default/acq/picklist/list.tt2 [new file with mode: 0644]
Open-ILS/web/templates/default/acq/picklist/view.tt2 [new file with mode: 0644]
Open-ILS/web/templates/default/acq/po/li_search.tt2 [new file with mode: 0644]
Open-ILS/web/templates/default/acq/po/search.tt2 [new file with mode: 0644]
Open-ILS/web/templates/default/acq/po/view.tt2 [new file with mode: 0644]
Open-ILS/web/templates/default/acq/receiving/process.tt2 [new file with mode: 0644]
Open-ILS/web/templates/default/acq/settings/li_attr.tt2 [new file with mode: 0644]
Open-ILS/web/templates/default/base.tt2 [new file with mode: 0644]
Open-ILS/web/templates/default/footer.tt2 [new file with mode: 0644]
Open-ILS/web/templates/default/header.tt2 [new file with mode: 0644]
Open-ILS/web/templates/default/menu.tt2 [new file with mode: 0644]

index b2b2f39..e353a84 100644 (file)
@@ -353,3 +353,23 @@ RewriteMap openurl prg:/openils/bin/openurl_map.pl
 RewriteCond %{QUERY_STRING} (^.*$)
 RewriteRule ^/openurl$ ${openurl:%1} [NE,PT]
 
+
+
+# General Evergreen web template processor
+<Location /eg>
+    SetHandler perl-script
+    PerlHandler OpenILS::WWW::EGWeb
+    Options +ExecCGI
+    PerlSendHeader On
+    allow from all
+</Location>
+# Note: the template processor will decline handling anything it does not
+# have an explicit configuration for, which means it will fall back to 
+# Apache to serve the file.  However, in the interest of speed, go ahead 
+# and tell Apache to avoid asking OpenILS::WWW::EGWeb for static content.
+# Add more exemptions as needed.
+<LocationMatch ^/eg/.*(\.js|\.css|\.html|\.xhtml|\.xml|\.jpg|\.png|\.gif)$>
+    SetHandler None
+</LocationMatch>
+
+
index d7f73ae..60b0034 100644 (file)
@@ -1,5 +1,4 @@
 <?xml version="1.0" encoding="UTF-8" ?>
-
 <!--
 
 Copyright (C) 2006-2008 Georgia Public Library Service
@@ -806,7 +805,9 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
                </links>
         <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
             <actions>
-                <create permission="TRANSIT_COPY"/>
+                <create permission="TRANSIT_COPY" context_field="owner">
+                    <context link="target_copy" field="circ_lib"/>
+                </create>
                 <retrieve/>
                 <update permission="UPDATE_TRANSIT" context_field="dest|source"/>
                 <delete permission="DELETE_TRANSIT" context_field="dest|source"/>
@@ -3315,6 +3316,525 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
             </actions>
         </permacrud>
        </class>
+
+       <class id="acqct" controller="open-ils.cstore open-ils.reporter-store" oils_obj:fieldmapper="acq::currency_type" oils_persist:tablename="acq.currency_type">
+               <fields oils_persist:primary="code">
+                       <field name="isnew" oils_obj:array_position="0" oils_persist:virtual="true" />
+                       <field name="ischanged" oils_obj:array_position="1" oils_persist:virtual="true" />
+                       <field name="isdeleted" oils_obj:array_position="2" oils_persist:virtual="true" />
+                       <field reporter:label="Currency Code" name="code" oils_obj:array_position="3" oils_persist:virtual="false" reporter:datatype="text" />
+                       <field reporter:label="Currency Label" name="label" oils_obj:array_position="4" oils_persist:virtual="false" reporter:datatype="text" oils_persist:i18n="true" />
+               </fields>
+               <links/>
+       </class>
+
+       <class id="acqexr" controller="open-ils.cstore open-ils.reporter-store" oils_obj:fieldmapper="acq::exchange_rate" oils_persist:tablename="acq.exchange_rate">
+               <fields oils_persist:primary="id" oils_persist:sequence="acq.exchange_rate_id_seq">
+                       <field name="isnew" oils_obj:array_position="0" oils_persist:virtual="true" />
+                       <field name="ischanged" oils_obj:array_position="1" oils_persist:virtual="true" />
+                       <field name="isdeleted" oils_obj:array_position="2" oils_persist:virtual="true" />
+                       <field reporter:label="Exchange Rate ID" name="id" oils_obj:array_position="3" oils_persist:virtual="false" reporter:datatype="id" />
+                       <field reporter:label="From Currency" name="from_currency" oils_obj:array_position="4" oils_persist:virtual="false" reporter:datatype="link" />
+                       <field reporter:label="To Currency" name="to_currency" oils_obj:array_position="5" oils_persist:virtual="false" reporter:datatype="link" />
+                       <field reporter:label="Ratio" name="ratio" oils_obj:array_position="6" oils_persist:virtual="false" />
+               </fields>
+               <links>
+                       <link field="from_currency" reltype="has_a" key="code" map="" class="acqct"/>
+                       <link field="to_currency" reltype="has_a" key="code" map="" class="acqct"/>
+               </links>
+       </class>
+
+       <class id="acqpro" controller="open-ils.cstore open-ils.reporter-store" oils_obj:fieldmapper="acq::provider" oils_persist:tablename="acq.provider">
+               <fields oils_persist:primary="id" oils_persist:sequence="acq.provider_id_seq">
+                       <field name="isnew" oils_obj:array_position="0" oils_persist:virtual="true" />
+                       <field name="ischanged" oils_obj:array_position="1" oils_persist:virtual="true" />
+                       <field name="isdeleted" oils_obj:array_position="2" oils_persist:virtual="true" />
+                       <field reporter:label="Provider ID" name="id" oils_obj:array_position="3" oils_persist:virtual="false" reporter:datatype="id" />
+                       <field reporter:label="Provider Name" name="name" oils_obj:array_position="4" oils_persist:virtual="false" reporter:datatype="text" oils_persist:i18n="true" />
+                       <field reporter:label="Owner" name="owner" oils_obj:array_position="5" oils_persist:virtual="false" reporter:datatype="org_unit" />
+                       <field reporter:label="Currency" name="currency_type" oils_obj:array_position="6" oils_persist:virtual="false" oils_persist:primitive="string" reporter:datatype="link" />
+                       <field reporter:label="Code" name="code" oils_obj:array_position="7" oils_persist:virtual="false" reporter:datatype="text" />
+               </fields>
+               <links>
+                       <link field="currency_type" reltype="has_a" key="code" map="" class="acqct"/>
+                       <link field="owner" reltype="has_a" key="id" map="" class="aou"/>
+               </links>
+       </class>
+
+       <class id="acqfs" controller="open-ils.cstore open-ils.reporter-store" oils_obj:fieldmapper="acq::funding_source" oils_persist:tablename="acq.funding_source">
+               <fields oils_persist:primary="id" oils_persist:sequence="acq.funding_source_id_seq">
+                       <field name="isnew" oils_obj:array_position="0" oils_persist:virtual="true" />
+                       <field name="ischanged" oils_obj:array_position="1" oils_persist:virtual="true" />
+                       <field name="isdeleted" oils_obj:array_position="2" oils_persist:virtual="true" />
+                       <field reporter:label="Funding Source ID" name="id" oils_obj:array_position="3" oils_persist:virtual="false" reporter:datatype="id" />
+                       <field reporter:label="Funding Source Name" name="name" oils_obj:array_position="4" oils_persist:virtual="false" reporter:datatype="text" oils_persist:i18n="true" />
+                       <field reporter:label="Owner" name="owner" oils_obj:array_position="5" oils_persist:virtual="false" reporter:datatype="org_unit" />
+                       <field reporter:label="Currency" name="currency_type" oils_obj:array_position="6" oils_persist:virtual="false" oils_persist:primitive="string" reporter:datatype="link" />
+                       <field reporter:label="Code" name="code" oils_obj:array_position="7" oils_persist:virtual="false" reporter:datatype="text" />
+                       <field name="summary" oils_obj:array_position="8" oils_persist:virtual="true"/>
+                       <field reporter:label="Allocations" name="allocations" oils_obj:array_position="9" oils_persist:virtual="true" reporter:datatype="link"/>
+                       <field reporter:label="Credits" name="credits" oils_obj:array_position="10" oils_persist:virtual="true" reporter:datatype="link"/>
+               </fields>
+               <links>
+                       <link field="currency_type" reltype="has_a" key="code" map="" class="acqct"/>
+                       <link field="owner" reltype="has_a" key="id" map="" class="aou"/>
+            <link field="allocations" reltype="has_many" map="" key="funding_source" class="acqfa"/>
+            <link field="credits" reltype="has_many" key="funding_source" map="" class="acqfscred"/>
+               </links>
+        <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
+            <actions>
+                <create   permission="CREATE_ACQ_FUNDING_SOURCE" context_field="owner"/>
+                <retrieve permission="VIEW_ACQ_FUNDING_SOURCE"   context_field="owner"/>
+                <update   permission="UPDATE_ACQ_FUNDING_SOURCE" context_field="owner"/>
+                <delete   permission="DELETE_ACQ_FUNDING_SOURCE" context_field="owner"/>
+            </actions>
+        </permacrud>
+       </class>
+
+       <class id="acqfscred" controller="open-ils.cstore open-ils.reporter-store" oils_obj:fieldmapper="acq::funding_source_credit" oils_persist:tablename="acq.funding_source_credit">
+               <fields oils_persist:primary="id" oils_persist:sequence="acq.funding_source_credit_id_seq">
+                       <field name="isnew" oils_obj:array_position="0" oils_persist:virtual="true" />
+                       <field name="ischanged" oils_obj:array_position="1" oils_persist:virtual="true" />
+                       <field name="isdeleted" oils_obj:array_position="2" oils_persist:virtual="true" />
+                       <field reporter:label="Credit ID" name="id" oils_obj:array_position="3" oils_persist:virtual="false" reporter:datatype="id" />
+                       <field reporter:label="Funding Source ID" name="funding_source" oils_obj:array_position="4" oils_persist:virtual="false" reporter:datatype="link" />
+                       <field reporter:label="Amount" name="amount" oils_obj:array_position="5" oils_persist:virtual="false" reporter:datatype="money" />
+                       <field reporter:label="Note" name="note" oils_obj:array_position="6" oils_persist:virtual="false" reporter:datatype="text" />
+               </fields>
+               <links>
+                       <link field="funding_source" reltype="has_a" key="id" map="" class="acqfs"/>
+               </links>
+       </class>
+
+       <class id="acqfdeb" controller="open-ils.cstore open-ils.reporter-store" oils_obj:fieldmapper="acq::fund_debit" oils_persist:tablename="acq.fund_debit">
+               <fields oils_persist:primary="id" oils_persist:sequence="acq.fund_debit_id_seq">
+                       <field name="isnew" oils_obj:array_position="0" oils_persist:virtual="true" />
+                       <field name="ischanged" oils_obj:array_position="1" oils_persist:virtual="true" />
+                       <field name="isdeleted" oils_obj:array_position="2" oils_persist:virtual="true" />
+                       <field reporter:label="Debit ID" name="id" oils_obj:array_position="3" oils_persist:virtual="false" reporter:datatype="id" />
+                       <field reporter:label="Fund ID" name="fund" oils_obj:array_position="4" oils_persist:virtual="false" reporter:datatype="link" />
+                       <field reporter:label="Origin Amount" name="origin_amount" oils_obj:array_position="5" oils_persist:virtual="false" reporter:datatype="money" />
+                       <field reporter:label="Origin Currency" name="origin_currency_type" oils_obj:array_position="6" oils_persist:virtual="false" reporter:datatype="link" />
+                       <field reporter:label="Amount" name="amount" oils_obj:array_position="7" oils_persist:virtual="false" reporter:datatype="money" />
+                       <field reporter:label="Encumbrance" name="encumbrance" oils_obj:array_position="8" oils_persist:virtual="false" reporter:datatype="text" />
+                       <field reporter:label="Debit Type" name="debit_type" oils_obj:array_position="9" oils_persist:virtual="false" reporter:datatype="text" />
+               </fields>
+               <links>
+                       <link field="fund" reltype="has_a" key="id" map="" class="acqf"/>
+                       <link field="origin_currency_type" reltype="has_a" key="code" map="" class="acqct"/>
+               </links>
+       </class>
+
+       <class id="acqf" controller="open-ils.cstore open-ils.reporter-store" oils_obj:fieldmapper="acq::fund" oils_persist:tablename="acq.fund">
+               <fields oils_persist:primary="id" oils_persist:sequence="acq.fund_id_seq">
+                       <field name="isnew" oils_obj:array_position="0" oils_persist:virtual="true" />
+                       <field name="ischanged" oils_obj:array_position="1" oils_persist:virtual="true" />
+                       <field name="isdeleted" oils_obj:array_position="2" oils_persist:virtual="true" />
+                       <field reporter:label="Fund ID" name="id" oils_obj:array_position="3" oils_persist:virtual="false" reporter:datatype="id" />
+                       <field reporter:label="Org Unit" name="org" oils_obj:array_position="4" oils_persist:virtual="false" reporter:datatype="org_unit" />
+                       <field reporter:label="Name" name="name" oils_obj:array_position="5" oils_persist:virtual="false" reporter:datatype="text" />
+                       <field reporter:label="Year" name="year" oils_obj:array_position="6" oils_persist:virtual="false" reporter:datatype="int" />
+                       <field reporter:label="Currency Type" name="currency_type" oils_obj:array_position="7" oils_persist:virtual="false" reporter:datatype="text" />
+                       <field reporter:label="Code" name="code" oils_obj:array_position="8" oils_persist:virtual="false" reporter:datatype="text" />
+                       <field name="summary" oils_obj:array_position="9" oils_persist:virtual="true"/>
+                       <field reporter:label="Allocations" name="allocations" oils_obj:array_position="10" oils_persist:virtual="true" reporter:datatype="link"/>
+                       <field reporter:label="Debits" name="debits" oils_obj:array_position="11" oils_persist:virtual="true" reporter:datatype="link"/>
+               </fields>
+               <links>
+                       <link field="org" reltype="has_a" key="id" map="" class="aou"/>
+                       <link field="currency_type" reltype="has_a" key="code" map="" class="acqct"/>
+            <link field="allocations" reltype="has_many" key="fund" map="" class="acqfa"/>
+            <link field="debits" reltype="has_many" key="fund" map="" class="acqfdeb"/>
+               </links>
+       </class>
+
+       <class id="acqfat" controller="open-ils.cstore open-ils.reporter-store" oils_obj:fieldmapper="acq::fund_allocation_total" oils_persist:readonly="true" oils_persist:tablename="acq.fund_allocation_total">
+               <fields oils_persist:primary="fund">
+                       <field name="isnew" oils_obj:array_position="0" oils_persist:virtual="true" />
+                       <field name="ischanged" oils_obj:array_position="1" oils_persist:virtual="true" />
+                       <field name="isdeleted" oils_obj:array_position="2" oils_persist:virtual="true" />
+                       <field reporter:label="Fund ID" name="fund" oils_obj:array_position="3" oils_persist:virtual="false" reporter:datatype="link" />
+                       <field reporter:label="Total Allocation Amount" name="amount" oils_obj:array_position="4" oils_persist:virtual="false" reporter:datatype="money" />
+               </fields>
+               <links>
+                       <link field="fund" reltype="has_a" key="id" map="" class="acqf"/>
+               </links>
+       </class>
+
+       <class id="acqfdt" controller="open-ils.cstore open-ils.reporter-store" oils_obj:fieldmapper="acq::fund_debit_total" oils_persist:readonly="true" oils_persist:tablename="acq.fund_debit_total">
+               <fields oils_persist:primary="fund">
+                       <field name="isnew" oils_obj:array_position="0" oils_persist:virtual="true" />
+                       <field name="ischanged" oils_obj:array_position="1" oils_persist:virtual="true" />
+                       <field name="isdeleted" oils_obj:array_position="2" oils_persist:virtual="true" />
+                       <field reporter:label="Fund ID" name="fund" oils_obj:array_position="3" oils_persist:virtual="false" reporter:datatype="link" />
+                       <field reporter:label="Total Debit Amount" name="amount" oils_obj:array_position="4" oils_persist:virtual="false" reporter:datatype="money" />
+               </fields>
+               <links>
+                       <link field="fund" reltype="has_a" key="id" map="" class="acqf"/>
+               </links>
+       </class>
+
+       <class id="acqfet" controller="open-ils.cstore open-ils.reporter-store" oils_obj:fieldmapper="acq::fund_encumbrance_total" oils_persist:readonly="true" oils_persist:tablename="acq.fund_encumbrance_total">
+               <fields oils_persist:primary="fund">
+                       <field name="isnew" oils_obj:array_position="0" oils_persist:virtual="true" />
+                       <field name="ischanged" oils_obj:array_position="1" oils_persist:virtual="true" />
+                       <field name="isdeleted" oils_obj:array_position="2" oils_persist:virtual="true" />
+                       <field reporter:label="Fund ID" name="fund" oils_obj:array_position="3" oils_persist:virtual="false" reporter:datatype="link" />
+                       <field reporter:label="Total Encumbrance Amount" name="amount" oils_obj:array_position="4" oils_persist:virtual="false" reporter:datatype="money" />
+               </fields>
+               <links>
+                       <link field="fund" reltype="has_a" key="id" map="" class="acqf"/>
+               </links>
+       </class>
+
+       <class id="acqfst" controller="open-ils.cstore open-ils.reporter-store" oils_obj:fieldmapper="acq::fund_spent_total" oils_persist:readonly="true" oils_persist:tablename="acq.fund_spent_total">
+               <fields oils_persist:primary="fund">
+                       <field name="isnew" oils_obj:array_position="0" oils_persist:virtual="true" />
+                       <field name="ischanged" oils_obj:array_position="1" oils_persist:virtual="true" />
+                       <field name="isdeleted" oils_obj:array_position="2" oils_persist:virtual="true" />
+                       <field reporter:label="Fund ID" name="fund" oils_obj:array_position="3" oils_persist:virtual="false" reporter:datatype="link" />
+                       <field reporter:label="Total Spent Amount" name="amount" oils_obj:array_position="4" oils_persist:virtual="false" reporter:datatype="money" />
+               </fields>
+               <links>
+                       <link field="fund" reltype="has_a" key="id" map="" class="acqf"/>
+               </links>
+       </class>
+
+       <class id="acqfcb" controller="open-ils.cstore open-ils.reporter-store" oils_obj:fieldmapper="acq::fund_combined_balance" oils_persist:readonly="true" oils_persist:tablename="acq.fund_combined_balance">
+               <fields oils_persist:primary="fund">
+                       <field name="isnew" oils_obj:array_position="0" oils_persist:virtual="true" />
+                       <field name="ischanged" oils_obj:array_position="1" oils_persist:virtual="true" />
+                       <field name="isdeleted" oils_obj:array_position="2" oils_persist:virtual="true" />
+                       <field reporter:label="Fund ID" name="fund" oils_obj:array_position="3" oils_persist:virtual="false" reporter:datatype="link" />
+                       <field reporter:label="Balance after Spent and Encumbered" name="amount" oils_obj:array_position="4" oils_persist:virtual="false" reporter:datatype="money" />
+               </fields>
+               <links>
+                       <link field="fund" reltype="has_a" key="id" map="" class="acqf"/>
+               </links>
+       </class>
+
+       <class id="acqfsrcct" controller="open-ils.cstore open-ils.reporter-store" oils_obj:fieldmapper="acq::funding_source_credit_total" oils_persist:readonly="true" oils_persist:tablename="acq.funding_source_credit_total">
+               <fields oils_persist:primary="funding_source">
+                       <field name="isnew" oils_obj:array_position="0" oils_persist:virtual="true" />
+                       <field name="ischanged" oils_obj:array_position="1" oils_persist:virtual="true" />
+                       <field name="isdeleted" oils_obj:array_position="2" oils_persist:virtual="true" />
+                       <field reporter:label="Funding Source" name="funding_source" oils_obj:array_position="3" oils_persist:virtual="false" reporter:datatype="link" />
+                       <field reporter:label="Total Credits to Funding Source" name="amount" oils_obj:array_position="4" oils_persist:virtual="false" reporter:datatype="money" />
+               </fields>
+               <links>
+                       <link field="funding_source" reltype="has_a" key="id" map="" class="acqfs"/>
+               </links>
+       </class>
+
+       <class id="acqfsrcat" controller="open-ils.cstore open-ils.reporter-store" oils_obj:fieldmapper="acq::funding_source_allocation_total" oils_persist:readonly="true" oils_persist:tablename="acq.funding_source_allocation_total">
+               <fields oils_persist:primary="funding_source">
+                       <field name="isnew" oils_obj:array_position="0" oils_persist:virtual="true" />
+                       <field name="ischanged" oils_obj:array_position="1" oils_persist:virtual="true" />
+                       <field name="isdeleted" oils_obj:array_position="2" oils_persist:virtual="true" />
+                       <field reporter:label="Funding Source" name="funding_source" oils_obj:array_position="3" oils_persist:virtual="false" reporter:datatype="link" />
+                       <field reporter:label="Total Allocated from Funding Source" name="amount" oils_obj:array_position="4" oils_persist:virtual="false" reporter:datatype="money" />
+               </fields>
+               <links>
+                       <link field="funding_source" reltype="has_a" key="id" map="" class="acqfs"/>
+               </links>
+       </class>
+
+       <class id="acqfsrcb" controller="open-ils.cstore open-ils.reporter-store" oils_obj:fieldmapper="acq::funding_source_balance" oils_persist:readonly="true" oils_persist:tablename="acq.funding_source_balance">
+               <fields oils_persist:primary="funding_source">
+                       <field name="isnew" oils_obj:array_position="0" oils_persist:virtual="true" />
+                       <field name="ischanged" oils_obj:array_position="1" oils_persist:virtual="true" />
+                       <field name="isdeleted" oils_obj:array_position="2" oils_persist:virtual="true" />
+                       <field reporter:label="Funding Source" name="funding_source" oils_obj:array_position="3" oils_persist:virtual="false" reporter:datatype="link" />
+                       <field reporter:label="Balance Remaining" name="amount" oils_obj:array_position="4" oils_persist:virtual="false" reporter:datatype="money" />
+               </fields>
+               <links>
+                       <link field="funding_source" reltype="has_a" key="id" map="" class="acqfs"/>
+               </links>
+       </class>
+
+       <class id="acqfsb" controller="open-ils.cstore open-ils.reporter-store" oils_obj:fieldmapper="acq::fund_spent_balance" oils_persist:readonly="true" oils_persist:tablename="acq.fund_spent_balance">
+               <fields oils_persist:primary="fund">
+                       <field name="isnew" oils_obj:array_position="0" oils_persist:virtual="true" />
+                       <field name="ischanged" oils_obj:array_position="1" oils_persist:virtual="true" />
+                       <field name="isdeleted" oils_obj:array_position="2" oils_persist:virtual="true" />
+                       <field reporter:label="Fund ID" name="fund" oils_obj:array_position="3" oils_persist:virtual="false" reporter:datatype="link" />
+                       <field reporter:label="Balance after Spent" name="amount" oils_obj:array_position="4" oils_persist:virtual="false" reporter:datatype="money" />
+               </fields>
+               <links>
+                       <link field="fund" reltype="has_a" key="id" map="" class="acqf"/>
+               </links>
+       </class>
+
+       <class id="acqfa" controller="open-ils.cstore open-ils.reporter-store" oils_obj:fieldmapper="acq::fund_allocation" oils_persist:tablename="acq.fund_allocation">
+               <fields oils_persist:primary="id" oils_persist:sequence="acq.fund_allocation_id_seq">
+                       <field name="isnew" oils_obj:array_position="0" oils_persist:virtual="true" />
+                       <field name="ischanged" oils_obj:array_position="1" oils_persist:virtual="true" />
+                       <field name="isdeleted" oils_obj:array_position="2" oils_persist:virtual="true" />
+                       <field reporter:label="Allocation ID" name="id" oils_obj:array_position="3" oils_persist:virtual="false" reporter:datatype="id" />
+                       <field reporter:label="Fund" name="fund" oils_obj:array_position="4" oils_persist:virtual="false" reporter:datatype="link" />
+                       <field reporter:label="Funding Source" name="funding_source" oils_obj:array_position="5" oils_persist:virtual="false" reporter:datatype="link" />
+                       <field reporter:label="Amount" name="amount" oils_obj:array_position="6" oils_persist:virtual="false" reporter:datatype="money" />
+                       <field reporter:label="Percent" name="percent" oils_obj:array_position="7" oils_persist:virtual="false" reporter:datatype="float" />
+                       <field reporter:label="Allocating User" name="allocator" oils_obj:array_position="8" oils_persist:virtual="false" reporter:datatype="link" />
+                       <field reporter:label="Note" name="note" oils_obj:array_position="9" oils_persist:virtual="false" reporter:datatype="text" />
+               </fields>
+               <links>
+                       <link field="allocator" reltype="has_a" key="id" map="" class="au"/>
+                       <link field="fund" reltype="has_a" key="id" map="" class="acqf"/>
+                       <link field="funding_source" reltype="has_a" key="id" map="" class="acqfs"/>
+               </links>
+       </class>
+
+       <class id="acqpl" controller="open-ils.cstore open-ils.reporter-store" oils_obj:fieldmapper="acq::picklist" oils_persist:tablename="acq.picklist">
+               <fields oils_persist:primary="id" oils_persist:sequence="acq.picklist_id_seq">
+                       <field name="isnew" oils_obj:array_position="0" oils_persist:virtual="true" />
+                       <field name="ischanged" oils_obj:array_position="1" oils_persist:virtual="true" />
+                       <field name="isdeleted" oils_obj:array_position="2" oils_persist:virtual="true" />
+                       <field reporter:label="Picklist ID" name="id" oils_obj:array_position="3" oils_persist:virtual="false" reporter:datatype="id" />
+                       <field reporter:label="Owner" name="owner" oils_obj:array_position="4" oils_persist:virtual="false" reporter:datatype="link" />
+                       <field reporter:label="Org Unit" name="org_unit" oils_obj:array_position="5" oils_persist:virtual="false" reporter:datatype="link" />
+                       <field reporter:label="Name" name="name" oils_obj:array_position="6" oils_persist:virtual="false" reporter:datatype="text" oils_persist:i18n="true" />
+                       <field reporter:label="Creation Time" name="create_time" oils_obj:array_position="7" oils_persist:virtual="false" reporter:datatype="timestamp" />
+                       <field reporter:label="Edit Time" name="edit_time" oils_obj:array_position="8" oils_persist:virtual="false" reporter:datatype="timestamp" />
+                       <field reporter:label="Entries" name="entries" oils_obj:array_position="9" oils_persist:virtual="true" reporter:datatype="link" />
+                       <field reporter:label="Entry Count" name="entry_count" oils_obj:array_position="10" oils_persist:virtual="true"/>
+               </fields>
+               <links>
+                       <link field="owner" reltype="has_a" key="id" map="" class="au"/>
+                       <link field="org_unit" reltype="has_a" key="id" map="" class="aou"/>
+                       <link field="entries" reltype="has_many" key="picklist" map="" class="jub"/>
+               </links>
+       </class>
+
+       <class id="acqpo" controller="open-ils.cstore open-ils.reporter-store" oils_obj:fieldmapper="acq::purchase_order" oils_persist:tablename="acq.purchase_order">
+               <fields oils_persist:primary="id" oils_persist:sequence="acq.purchase_order_id_seq">
+                       <field name="isnew" oils_obj:array_position="0" oils_persist:virtual="true" />
+                       <field name="ischanged" oils_obj:array_position="1" oils_persist:virtual="true" />
+                       <field name="isdeleted" oils_obj:array_position="2" oils_persist:virtual="true" />
+                       <field reporter:label="Purchase Order ID" name="id" oils_obj:array_position="3" oils_persist:virtual="false" reporter:datatype="id" />
+                       <field reporter:label="Owner" name="owner" oils_obj:array_position="4" oils_persist:virtual="false" reporter:datatype="link" />
+                       <field reporter:label="Creation Time" name="create_time" oils_obj:array_position="5" oils_persist:virtual="false" reporter:datatype="timestamp" />
+                       <field reporter:label="Edit Time" name="edit_time" oils_obj:array_position="6" oils_persist:virtual="false" reporter:datatype="timestamp" />
+                       <field reporter:label="Provider" name="provider" oils_obj:array_position="7" oils_persist:virtual="false" reporter:datatype="link" />
+                       <field reporter:label="State" name="state" oils_obj:array_position="8" oils_persist:virtual="false" reporter:datatype="text" />
+                       <field reporter:label="Ordering Agency" name="ordering_agency" oils_obj:array_position="9" oils_persist:virtual="false" reporter:datatype="link" />
+                       <field reporter:label="Line Items" name="lineitems" oils_obj:array_position="10" oils_persist:virtual="true" reporter:datatype="link" />
+                       <field reporter:label="Line Item Count" name="lineitem_count" oils_obj:array_position="11" oils_persist:virtual="true" reporter:datatype="link" />
+               </fields>
+               <links>
+                       <link field="owner" reltype="has_a" key="id" map="" class="au"/>
+                       <link field="default_fund" reltype="has_a" key="id" map="" class="acqf"/>
+                       <link field="provider" reltype="has_a" key="id" map="" class="acqpro"/>
+                       <link field="lineitems" reltype="has_many" key="purchase_order" map="" class="jub"/>
+                       <link field="ordering_agency" reltype="has_a" key="id" map="" class="aou"/>
+               </links>
+       </class>
+
+       <class id="acqpon" controller="open-ils.cstore open-ils.reporter-store" oils_obj:fieldmapper="acq::po_note" oils_persist:tablename="acq.po_note">
+               <fields oils_persist:primary="id" oils_persist:sequence="acq.po_note_id_seq">
+                       <field name="isnew" oils_obj:array_position="0" oils_persist:virtual="true" />
+                       <field name="ischanged" oils_obj:array_position="1" oils_persist:virtual="true" />
+                       <field name="isdeleted" oils_obj:array_position="2" oils_persist:virtual="true" />
+                       <field reporter:label="PO Note ID" name="id" oils_obj:array_position="3" oils_persist:virtual="false" reporter:datatype="id" />
+                       <field reporter:label="Purchase Order" name="purchase_order" oils_obj:array_position="4" oils_persist:virtual="false" reporter:datatype="link" />
+                       <field reporter:label="Creator" name="creator" oils_obj:array_position="5" oils_persist:virtual="false" reporter:datatype="link" />
+                       <field reporter:label="Creation Time" name="create_time" oils_obj:array_position="6" oils_persist:virtual="false" reporter:datatype="timestamp" />
+                       <field reporter:label="Edit Time" name="edit_time" oils_obj:array_position="7" oils_persist:virtual="false" reporter:datatype="timestamp" />
+                       <field reporter:label="Editor" name="editor" oils_obj:array_position="8" oils_persist:virtual="false" reporter:datatype="link" />
+                       <field reporter:label="Vote Value" name="value" oils_obj:array_position="9" oils_persist:virtual="false" reporter:datatype="text" />
+               </fields>
+               <links>
+                       <link field="creator" reltype="has_a" key="id" map="" class="au"/>
+                       <link field="editor" reltype="has_a" key="id" map="" class="au"/>
+                       <link field="purchase_order" reltype="has_a" key="id" map="" class="acqpo"/>
+               </links>
+       </class>
+
+       <class id="jub" controller="open-ils.cstore open-ils.reporter-store" oils_obj:fieldmapper="acq::lineitem" oils_persist:tablename="acq.lineitem">
+               <fields oils_persist:primary="id" oils_persist:sequence="acq.lineitem_id_seq">
+                       <field name="isnew" oils_obj:array_position="0" oils_persist:virtual="true" />
+                       <field name="ischanged" oils_obj:array_position="1" oils_persist:virtual="true" />
+                       <field name="isdeleted" oils_obj:array_position="2" oils_persist:virtual="true" />
+                       <field reporter:label="Lineitem ID" name="id" oils_obj:array_position="3" oils_persist:virtual="false" reporter:datatype="id" />
+                       <field reporter:label="Selecting Org Unit" name="selector" oils_obj:array_position="4" oils_persist:virtual="false" reporter:datatype="link" />
+                       <field reporter:label="Picklist" name="picklist" oils_obj:array_position="5" oils_persist:virtual="false" reporter:datatype="link" />
+                       <field reporter:label="Purchase Order" name="purchase_order" oils_obj:array_position="6" oils_persist:virtual="false" reporter:datatype="link" />
+                       <field reporter:label="Provider" name="provider" oils_obj:array_position="7" oils_persist:virtual="false" reporter:datatype="link" />
+                       <field reporter:label="Creation Time" name="create_time" oils_obj:array_position="8" oils_persist:virtual="false" reporter:datatype="timestamp" />
+                       <field reporter:label="Edit Time" name="edit_time" oils_obj:array_position="9" oils_persist:virtual="false" reporter:datatype="timestamp" />
+                       <field reporter:label="MARC" name="marc" oils_obj:array_position="10" oils_persist:virtual="false" reporter:datatype="text" />
+                       <field reporter:label="Evergreen Bib ID" name="eg_bib_id" oils_obj:array_position="11" oils_persist:virtual="false" reporter:datatype="link" />
+                       <field reporter:label="Source Label" name="source_label" oils_obj:array_position="12" oils_persist:virtual="false" reporter:datatype="text" />
+                       <field reporter:label="Expected Receive Date" name="expected_recv_time" oils_obj:array_position="13" oils_persist:virtual="false" reporter:datatype="timestamp" />
+                       <field reporter:label="State" name="state" oils_obj:array_position="14" oils_persist:virtual="false" reporter:datatype="text" />
+                       <field reporter:label="Item Count" name="item_count" oils_obj:array_position="15" oils_persist:virtual="true" reporter:datatype="int" />
+                       <field reporter:label="Descriptive Attributes" name="attributes" oils_obj:array_position="16" oils_persist:virtual="true" reporter:datatype="link" />
+                       <field reporter:label="Line Item Details" name="lineitem_details" oils_obj:array_position="17" oils_persist:virtual="true" reporter:datatype="link" />
+                       <field reporter:label="Line Item Notes" name="lineitem_notes" oils_obj:array_position="18" oils_persist:virtual="true" reporter:datatype="link" />
+               </fields>
+               <links>
+                       <link field="selector" reltype="has_a" key="id" map="" class="aou"/>
+                       <link field="provider" reltype="has_a" key="id" map="" class="acqpro"/>
+                       <link field="purchase_order" reltype="has_a" key="id" map="" class="acqpo"/>
+                       <link field="picklist" reltype="has_a" key="id" map="" class="acqpl"/>
+                       <link field="eg_bib_id" reltype="has_a" key="id" map="" class="bre"/>
+                       <link field="attributes" reltype="has_many" key="lineitem" map="" class="acqlia"/>
+                       <link field="lineitem_details" reltype="has_many" key="lineitem" map="" class="acqlid"/>
+                       <link field="lineitem_notes" reltype="has_many" key="lineitem" map="" class="acqlin"/>
+               </links>
+       </class>
+
+       <class id="acqlin" controller="open-ils.cstore open-ils.reporter-store" oils_obj:fieldmapper="acq::lineitem_note" oils_persist:tablename="acq.lineitem_note">
+               <fields oils_persist:primary="id" oils_persist:sequence="acq.lineitem_note_id_seq">
+                       <field name="isnew" oils_obj:array_position="0" oils_persist:virtual="true" />
+                       <field name="ischanged" oils_obj:array_position="1" oils_persist:virtual="true" />
+                       <field name="isdeleted" oils_obj:array_position="2" oils_persist:virtual="true" />
+                       <field reporter:label="PO Line Item Note ID" name="id" oils_obj:array_position="3" oils_persist:virtual="false" reporter:datatype="id" />
+                       <field reporter:label="Line Item" name="lineitem" oils_obj:array_position="4" oils_persist:virtual="false" reporter:datatype="link" />
+                       <field reporter:label="Creator" name="creator" oils_obj:array_position="5" oils_persist:virtual="false" reporter:datatype="link" />
+                       <field reporter:label="Creation Time" name="create_time" oils_obj:array_position="6" oils_persist:virtual="false" reporter:datatype="timestamp" />
+                       <field reporter:label="Edit Time" name="edit_time" oils_obj:array_position="7" oils_persist:virtual="false" reporter:datatype="timestamp" />
+                       <field reporter:label="Editor" name="editor" oils_obj:array_position="8" oils_persist:virtual="false" reporter:datatype="link" />
+                       <field reporter:label="Vote Value" name="value" oils_obj:array_position="9" oils_persist:virtual="false" reporter:datatype="text" />
+               </fields>
+               <links>
+                       <link field="creator" reltype="has_a" key="id" map="" class="au"/>
+                       <link field="editor" reltype="has_a" key="id" map="" class="au"/>
+                       <link field="lineitem" reltype="has_a" key="id" map="" class="jub"/>
+               </links>
+       </class>
+
+       <class id="acqlia" controller="open-ils.cstore open-ils.reporter-store" oils_obj:fieldmapper="acq::lineitem_attr" oils_persist:tablename="acq.lineitem_attr">
+               <fields oils_persist:primary="id" oils_persist:sequence="acq.lineitem_attr_id_seq">
+                       <field name="isnew" oils_obj:array_position="0" oils_persist:virtual="true" />
+                       <field name="ischanged" oils_obj:array_position="1" oils_persist:virtual="true" />
+                       <field name="isdeleted" oils_obj:array_position="2" oils_persist:virtual="true" />
+                       <field reporter:label="Attribute Value ID" name="id" oils_obj:array_position="3" oils_persist:virtual="false" reporter:datatype="id" />
+                       <field reporter:label="Lineitem" name="lineitem" oils_obj:array_position="4" oils_persist:virtual="false" reporter:datatype="link" />
+                       <field reporter:label="Type" name="attr_type" oils_obj:array_position="5" oils_persist:virtual="false" reporter:datatype="text" />
+                       <field reporter:label="Name" name="attr_name" oils_obj:array_position="6" oils_persist:virtual="false" reporter:datatype="text" />
+                       <field reporter:label="Value" name="attr_value" oils_obj:array_position="7" oils_persist:virtual="false" reporter:datatype="text" />
+                       <field reporter:label="Definition" name="definition" oils_obj:array_position="8" oils_persist:virtual="false" reporter:datatype="text" />
+               </fields>
+               <links>
+                       <link field="lineitem" reltype="has_a" key="id" map="" class="jub"/>
+                       <link field="definition" reltype="has_a" key="id" map="" class="acqliad"/>
+               </links>
+       </class>
+
+       <class id="acqlid" controller="open-ils.cstore open-ils.reporter-store" oils_obj:fieldmapper="acq::lineitem_detail" oils_persist:tablename="acq.lineitem_detail">
+               <fields oils_persist:primary="id" oils_persist:sequence="acq.lineitem_detail_id_seq">
+                       <field name="isnew" oils_obj:array_position="0" oils_persist:virtual="true" />
+                       <field name="ischanged" oils_obj:array_position="1" oils_persist:virtual="true" />
+                       <field name="isdeleted" oils_obj:array_position="2" oils_persist:virtual="true" />
+                       <field reporter:label="Item Detail ID" name="id" oils_obj:array_position="3" oils_persist:virtual="false" reporter:datatype="id" />
+                       <field reporter:label="PO Line Item" name="lineitem" oils_obj:array_position="4" oils_persist:virtual="false" reporter:datatype="link" />
+                       <field reporter:label="Evergreen Copy ID" name="eg_copy_id" oils_obj:array_position="5" oils_persist:virtual="false" reporter:datatype="link" />
+                       <field reporter:label="Barcode" name="barcode" oils_obj:array_position="6" oils_persist:virtual="false" reporter:datatype="text" />
+                       <field reporter:label="Call Number Label" name="cn_label" oils_obj:array_position="7" oils_persist:virtual="false" reporter:datatype="text" />
+                       <field reporter:label="Actual Receive Date" name="recv_time" oils_obj:array_position="8" oils_persist:virtual="false" reporter:datatype="timestamp" />
+                       <field reporter:label="Fund" name="fund" oils_obj:array_position="9" oils_persist:virtual="false" reporter:datatype="link" />
+                       <field reporter:label="Fund Debit" name="fund_debit" oils_obj:array_position="10" oils_persist:virtual="false" reporter:datatype="link" />
+                       <field reporter:label="Owning Library" name="owning_lib" oils_obj:array_position="11" oils_persist:virtual="false" reporter:datatype="link" />
+                       <field reporter:label="Shelving Location" name="location" oils_obj:array_position="12" oils_persist:virtual="false" reporter:datatype="link" />
+               </fields>
+               <links>
+                       <link field="lineitem" reltype="has_a" key="id" map="" class="jub"/>
+                       <link field="eg_copy_id" reltype="has_a" key="id" map="" class="acp"/>
+                       <link field="fund" reltype="has_a" key="id" map="" class="acqf"/>
+                       <link field="fund_debit" reltype="has_a" key="id" map="" class="acqfdeb"/>
+                       <link field="owning_lib" reltype="has_a" key="id" map="" class="aou"/>
+                       <link field="location" reltype="has_a" key="id" map="" class="acpl"/>
+               </links>
+       </class>
+
+       <class id="acqliad" controller="open-ils.cstore open-ils.reporter-store" oils_obj:fieldmapper="acq::lineitem_attr_definition" oils_persist:tablename="acq.lineitem_attr_definition">
+               <fields oils_persist:primary="id" oils_persist:sequence="acq.lineitem_attr_definition_id_seq">
+                       <field name="isnew" oils_obj:array_position="0" oils_persist:virtual="true" />
+                       <field name="ischanged" oils_obj:array_position="1" oils_persist:virtual="true" />
+                       <field name="isdeleted" oils_obj:array_position="2" oils_persist:virtual="true" />
+                       <field reporter:label="Definition ID" name="id" oils_obj:array_position="3" oils_persist:virtual="false" reporter:datatype="id" />
+                       <field reporter:label="Code" name="code" oils_obj:array_position="4" oils_persist:virtual="false" reporter:datatype="text" />
+                       <field reporter:label="Description" name="description" oils_obj:array_position="5" oils_persist:virtual="false" reporter:datatype="text" oils_persist:i18n="true" />
+                       <field reporter:label="Is Identifier?" name="ident" oils_obj:array_position="6" oils_persist:virtual="false" reporter:datatype="bool"/>
+               </fields>
+               <links/>
+       </class>
+
+       <class id="acqlimad" controller="open-ils.cstore open-ils.reporter-store" oils_obj:fieldmapper="acq::lineitem_marc_attr_definition" oils_persist:tablename="acq.lineitem_marc_attr_definition">
+               <fields oils_persist:primary="id" oils_persist:sequence="acq.lineitem_attr_definition_id_seq">
+                       <field name="isnew" oils_obj:array_position="0" oils_persist:virtual="true" />
+                       <field name="ischanged" oils_obj:array_position="1" oils_persist:virtual="true" />
+                       <field name="isdeleted" oils_obj:array_position="2" oils_persist:virtual="true" />
+                       <field reporter:label="Definition ID" name="id" oils_obj:array_position="3" oils_persist:virtual="false" reporter:datatype="id" />
+                       <field reporter:label="Code" name="code" oils_obj:array_position="4" oils_persist:virtual="false" reporter:datatype="text" />
+                       <field reporter:label="Description" name="description" oils_obj:array_position="5" oils_persist:virtual="false" reporter:datatype="text" oils_persist:i18n="true" />
+                       <field reporter:label="XPath" name="xpath" oils_obj:array_position="6" oils_persist:virtual="false" reporter:datatype="text" />
+                       <field reporter:label="Is Identifier?" name="ident" oils_obj:array_position="7" oils_persist:virtual="false" reporter:datatype="bool"/>
+               </fields>
+               <links/>
+       </class>
+
+       <class id="acqligad" controller="open-ils.cstore open-ils.reporter-store" oils_obj:fieldmapper="acq::lineitem_generated_attr_definition" oils_persist:tablename="acq.lineitem_generated_attr_definition">
+               <fields oils_persist:primary="id" oils_persist:sequence="acq.lineitem_attr_definition_id_seq">
+                       <field name="isnew" oils_obj:array_position="0" oils_persist:virtual="true" />
+                       <field name="ischanged" oils_obj:array_position="1" oils_persist:virtual="true" />
+                       <field name="isdeleted" oils_obj:array_position="2" oils_persist:virtual="true" />
+                       <field reporter:label="Definition ID" name="id" oils_obj:array_position="3" oils_persist:virtual="false" reporter:datatype="id" />
+                       <field reporter:label="Code" name="code" oils_obj:array_position="4" oils_persist:virtual="false" reporter:datatype="text" />
+                       <field reporter:label="Description" name="description" oils_obj:array_position="5" oils_persist:virtual="false" reporter:datatype="text" oils_persist:i18n="true" />
+                       <field reporter:label="XPath" name="xpath" oils_obj:array_position="6" oils_persist:virtual="false" reporter:datatype="text" />
+                       <field reporter:label="Is Identifier?" name="ident" oils_obj:array_position="7" oils_persist:virtual="false" reporter:datatype="bool"/>
+               </fields>
+               <links/>
+       </class>
+
+       <class id="acqlipad" controller="open-ils.cstore open-ils.reporter-store" oils_obj:fieldmapper="acq::lineitem_provider_attr_definition" oils_persist:tablename="acq.lineitem_provider_attr_definition">
+               <fields oils_persist:primary="id" oils_persist:sequence="acq.lineitem_attr_definition_id_seq">
+                       <field name="isnew" oils_obj:array_position="0" oils_persist:virtual="true" />
+                       <field name="ischanged" oils_obj:array_position="1" oils_persist:virtual="true" />
+                       <field name="isdeleted" oils_obj:array_position="2" oils_persist:virtual="true" />
+                       <field reporter:label="Definition ID" name="id" oils_obj:array_position="3" oils_persist:virtual="false" reporter:datatype="id" />
+                       <field reporter:label="Code" name="code" oils_obj:array_position="4" oils_persist:virtual="false" reporter:datatype="text" />
+                       <field reporter:label="Description" name="description" oils_obj:array_position="5" oils_persist:virtual="false" reporter:datatype="text" oils_persist:i18n="true" />
+                       <field reporter:label="XPath" name="xpath" oils_obj:array_position="6" oils_persist:virtual="false" reporter:datatype="text" />
+                       <field reporter:label="Provider" name="provider" oils_obj:array_position="7" oils_persist:virtual="false" reporter:datatype="link" />
+                       <field reporter:label="Is Identifier?" name="ident" oils_obj:array_position="8" oils_persist:virtual="false" reporter:datatype="bool"/>
+               </fields>
+               <links>
+                       <link field="provider" reltype="has_a" key="id" map="" class="acqpro"/>
+               </links>
+       </class>
+
+       <class id="acqliuad" controller="open-ils.cstore open-ils.reporter-store" oils_obj:fieldmapper="acq::lineitem_usr_attr_definition" oils_persist:tablename="acq.lineitem_usr_attr_definition">
+               <fields oils_persist:primary="id" oils_persist:sequence="acq.lineitem_attr_definition_id_seq">
+                       <field name="isnew" oils_obj:array_position="0" oils_persist:virtual="true" />
+                       <field name="ischanged" oils_obj:array_position="1" oils_persist:virtual="true" />
+                       <field name="isdeleted" oils_obj:array_position="2" oils_persist:virtual="true" />
+                       <field reporter:label="Definition ID" name="id" oils_obj:array_position="3" oils_persist:virtual="false" reporter:datatype="id" />
+                       <field reporter:label="Code" name="code" oils_obj:array_position="4" oils_persist:virtual="false" reporter:datatype="text" />
+                       <field reporter:label="Description" name="description" oils_obj:array_position="5" oils_persist:virtual="false" reporter:datatype="text" oils_persist:i18n="true" />
+                       <field reporter:label="User" name="usr" oils_obj:array_position="6" oils_persist:virtual="false" reporter:datatype="link" />
+                       <field reporter:label="Is Identifier?" name="ident" oils_obj:array_position="7" oils_persist:virtual="false" reporter:datatype="bool"/>
+               </fields>
+               <links>
+                       <link field="usr" reltype="has_a" key="id" map="" class="au"/>
+               </links>
+       </class>
+
+       <class id="acqlilad" controller="open-ils.cstore open-ils.reporter-store" oils_obj:fieldmapper="acq::lineitem_local_attr_definition" oils_persist:tablename="acq.lineitem_local_attr_definition">
+               <fields oils_persist:primary="id" oils_persist:sequence="acq.lineitem_attr_definition_id_seq">
+                       <field name="isnew" oils_obj:array_position="0" oils_persist:virtual="true" />
+                       <field name="ischanged" oils_obj:array_position="1" oils_persist:virtual="true" />
+                       <field name="isdeleted" oils_obj:array_position="2" oils_persist:virtual="true" />
+                       <field reporter:label="Definition ID" name="id" oils_obj:array_position="3" oils_persist:virtual="false" reporter:datatype="id" />
+                       <field reporter:label="Code" name="code" oils_obj:array_position="4" oils_persist:virtual="false" reporter:datatype="text" />
+                       <field reporter:label="Description" name="description" oils_obj:array_position="5" oils_persist:virtual="false" reporter:datatype="text" oils_persist:i18n="true" />
+                       <field reporter:label="Is Identifier?" name="ident" oils_obj:array_position="6" oils_persist:virtual="false" reporter:datatype="bool"/>
+               </fields>
+               <links/>
+       </class>
+
        <class id="rof" controller="open-ils.reporter-store" oils_obj:fieldmapper="reporter::output_folder" oils_persist:tablename="reporter.output_folder">
                <fields oils_persist:primary="id" oils_persist:sequence="reporter.output_folder_id_seq">
                        <field name="isnew" oils_obj:array_position="0" oils_persist:virtual="true" />
@@ -3987,3 +4507,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
        <!-- ********************************************************************************************************************* -->
 
 </IDL>
+
+<!--
+    vim:noet:ts=4:sw=4:
+-->
diff --git a/Open-ILS/examples/oils_web.xml b/Open-ILS/examples/oils_web.xml
new file mode 100644 (file)
index 0000000..babb51a
--- /dev/null
@@ -0,0 +1,35 @@
+<oils_web>
+    <!-- This should match the Apache Directory/Location[Match] configuration path -->
+    <base_uri>/eg</base_uri>
+
+    <!-- media_prefix can be a remote server.  
+         E.g. <media_prefix>http://static.example.com/media</media_prefix> -->
+    <media_prefix/>
+
+    <!-- Where templates can be found.  Paths will be checked in the order entered here.
+         It's possible to override individual or sets of templates by putting them into
+         a path in front of the default template path -->
+    <template_paths>
+        <!-- XXX we should really move these out of the default web root -->
+        <path>/openils/var/web/templates</path>
+    </template_paths>
+
+    <handlers>
+        <handler path='acq/picklist/list' template='acq/picklist/list.tt2'/>
+        <handler path='acq/picklist/view' template='acq/picklist/view.tt2'/>
+        <handler path='acq/picklist/bib_search' template='acq/picklist/bib_search.tt2'/>
+        <handler path='acq/fund/list' template='acq/financial/list_funds.tt2'/>
+        <handler path='acq/fund/view' template='acq/financial/view_fund.tt2'/>
+        <handler path='acq/funding_source/list' template='acq/financial/list_funding_sources.tt2'/>
+        <handler path='acq/funding_source/view' template='acq/financial/view_funding_source.tt2'/>
+        <handler path='acq/currency_type/list' template='acq/financial/list_currency_types.tt2'/>
+        <handler path='acq/currency_type/view' template='acq/financial/view_currency_type.tt2'/>
+        <handler path='acq/provider/list' template='acq/financial/list_providers.tt2'/>
+        <handler path='acq/provider/view' template='acq/financial/view_provider.tt2'/>
+        <handler path='acq/po/view' template='acq/po/view.tt2'/>
+        <handler path='acq/po/li_search' template='acq/po/li_search.tt2'/>
+        <handler path='acq/po/search' template='acq/po/search.tt2'/>
+        <handler path='acq/receiving/process' template='acq/receiving/process.tt2'/>
+        <handler path='acq/settings/li_attr' template='acq/settings/li_attr.tt2'/>
+    </handlers>
+</oils_web>
index dd991d6..bda47ff 100644 (file)
        </event>
 
 
-
+       <event code='1841' textcode='ACQ_PICKLIST_NOT_FOUND'>
+               <desc xml:lang='en-US'>The requested acq.picklist was not found</desc>
+       </event>
+       <event code='1843' textcode='ACQ_LINEITEM_ATTR_NOT_FOUND'>
+               <desc xml:lang='en-US'>The requested acq.lineitem_attr was not found</desc>
+       </event>
+       <event code='1844' textcode='ACQ_FUNDING_SOURCE_NOT_FOUND'>
+               <desc xml:lang='en-US'>The requested acq.funding_source was not found</desc>
+       </event>
+       <event code='1845' textcode='ACQ_PROVIDER_NOT_FOUND'>
+               <desc xml:lang='en-US'>The requested acq.provider was not found</desc>
+       </event>
+       <event code='1846' textcode='ACQ_FUNDING_SOURCE_CREDIT_NOT_FOUND'>
+               <desc xml:lang='en-US'>The requested acq.funding_source_credit was not found</desc>
+       </event>
+       <event code='1847' textcode='ACQ_FUNDING_SOURCE_DEDIT_NOT_FOUND'>
+               <desc xml:lang='en-US'>The requested acq.funding_source_dedit was not found</desc>
+       </event>
+       <event code='1848' textcode='ACQ_FUND_NOT_FOUND'>
+               <desc xml:lang='en-US'>The requested acq.fund was not found</desc>
+       </event>
+       <event code='1849' textcode='ACQ_FUND_DEBIT_TOTAL_NOT_FOUND'>
+               <desc xml:lang='en-US'>The requested acq.fund_debit_total was not found</desc>
+       </event>
+       <event code='1850' textcode='ACQ_FUND_ALLOCATION_TOTAL_NOT_FOUND'>
+               <desc xml:lang='en-US'>The requested acq.fund_allocation_total was not found</desc>
+       </event>
+       <event code='1851' textcode='ACQ_FUND_ENCUMBRANCE_TOTAL_NOT_FOUND'>
+               <desc xml:lang='en-US'>The requested acq.fund_encumbrance_total was not found</desc>
+       </event>
+       <event code='1852' textcode='ACQ_FUND_SPENT_TOTAL_NOT_FOUND'>
+               <desc xml:lang='en-US'>The requested acq.fund_spent_total was not found</desc>
+       </event>
+       <event code='1853' textcode='ACQ_FUND_COMBINED_BALANCE_NOT_FOUND'>
+               <desc xml:lang='en-US'>The requested acq.fund_combined_balance was not found</desc>
+       </event>
+       <event code='1854' textcode='ACQ_FUND_SPENT_BALANCE_NOT_FOUND'>
+               <desc xml:lang='en-US'>The requested acq.fund_spent_balance was not found</desc>
+       </event>
+       <event code='1855' textcode='ACQ_FUNDING_SOURCE_CREDIT_TOTAL_NOT_FOUND'>
+               <desc xml:lang='en-US'>The requested acq.funding_source_credit_total was not found</desc>
+       </event>
+       <event code='1856' textcode='ACQ_FUNDING_SOURCE_ALLOCATION_TOTAL_NOT_FOUND'>
+               <desc xml:lang='en-US'>The requested acq.funding_source_allocation_total was not found</desc>
+       </event>
+       <event code='1857' textcode='ACQ_FUNDING_SOURCE_BALANCE_NOT_FOUND'>
+               <desc xml:lang='en-US'>The requested acq.funding_source_balance was not found</desc>
+       </event>
+       <event code='1858' textcode='ACQ_LINEITEM_NOT_FOUND'>
+               <desc xml:lang='en-US'>The requested acq.po_lineitem was not found</desc>
+       </event>
+       <event code='1859' textcode='ACQ_PURCHASE_ORDER_NOT_FOUND'>
+               <desc xml:lang='en-US'>The requested acq.purchase_order was not found</desc>
+       </event>
+       <event code='1870' textcode='ACQ_LINEITEM_DETAIL_NOT_FOUND'>
+               <desc xml:lang='en-US'>The requested acq.lineitem_detail was not found</desc>
+       </event>
+       <event code='1871' textcode='PERMISSION_USR_OBJECT_PERM_MAP_NOT_FOUND'>
+               <desc xml:lang='en-US'>The requested permission.usr_object_perm_map was not found</desc>
+       </event>
+       <event code='1872' textcode='ACQ_LINEITEM_PROVIDER_ATTR_DEFINITION_NOT_FOUND'>
+               <desc xml:lang='en-US'>The requested acq.lineitem_provider_attr_definition was not found</desc>
+       </event>
+       <event code='1873' textcode='ACQ_CURRENCY_TYPE_NOT_FOUND'>
+               <desc xml:lang='en-US'>The requested acq.currency_type was not found</desc>
+       </event>
+       <event code='1875' textcode='ACQ_LINEITEM_GENERATED_ATTR_DEFINITION_NOT_FOUND'>
+               <desc xml:lang='en-US'>The requested acq_lineitem_attr was not found</desc>
+       </event>
+       <event code='1876' textcode='ACQ_LINEITEM_LOCAL_ATTR_DEFINITION_NOT_FOUND'>
+               <desc xml:lang='en-US'>The requested acq_lineitem_attr was not found</desc>
+       </event>
+       <event code='1877' textcode='ACQ_LINEITEM_MARC_ATTR_DEFINITION_NOT_FOUND'>
+               <desc xml:lang='en-US'>The requested acq_lineitem_attr was not found</desc>
+       </event>
+       <event code='1878' textcode='ACQ_LINEITEM_USR_ATTR_DEFINITION_NOT_FOUND'>
+               <desc xml:lang='en-US'>The requested acq_lineitem_attr was not found</desc>
+       </event>
+       <event code='1879' textcode='ACQ_LINEITEM_PROVIDER_ATTR_DEFINITION_NOT_FOUND'>
+               <desc xml:lang='en-US'>The requested acq_lineitem_attr was not found</desc>
+       </event>
 
 
        <event code='1700' textcode='NON_CAT_TYPE_EXISTS'>
                <desc xml:lang="en-US"> A report with the given name and folder already exists</desc>
        </event>
 
+       <event code='10000' textcode='ACQ_LINEITEM_APPROVED'>
+               <desc xml:lang="en-US">The lineitem cannot be altered because it has already been approved</desc>
+       </event>
+       <event code='10001' textcode='ACQ_LINEITEM_NO_COPIES'>
+               <desc xml:lang="en-US">The lineitem has no attached copies</desc>
+       </event>
+       <event code='10002' textcode='ACQ_LINEITEM_DETAIL_NO_FUND'>
+               <desc xml:lang="en-US">The lineitem detail has no associated fund</desc>
+       </event>
+       <event code='10003' textcode='ACQ_LINEITEM_DETAIL_NO_ORG'>
+               <desc xml:lang="en-US">The lineitem detail has no owning_lib</desc>
+       </event>
+       <event code='10004' textcode='ACQ_LINEITEM_NO_PRICE'>
+               <desc xml:lang="en-US">The lineitem has no price</desc>
+       </event>
+       <event code='10005' textcode='ACQ_LINEITEM_NO_PROVIDER'>
+               <desc xml:lang="en-US">The lineitem has no price</desc>
+       </event>
+
+
 
 
        <!-- ================================================================ -->
diff --git a/Open-ILS/src/perlmods/OpenILS/Application/Acq.pm b/Open-ILS/src/perlmods/OpenILS/Application/Acq.pm
new file mode 100644 (file)
index 0000000..789252c
--- /dev/null
@@ -0,0 +1,10 @@
+package OpenILS::Application::Acq;
+use base qw/OpenILS::Application/;
+use strict; use warnings;
+
+use OpenILS::Application::Acq::Picklist;
+use OpenILS::Application::Acq::Financials;
+use OpenILS::Application::Acq::Provider;
+use OpenILS::Application::Acq::Lineitem;
+
+1;
diff --git a/Open-ILS/src/perlmods/OpenILS/Application/Acq/Financials.pm b/Open-ILS/src/perlmods/OpenILS/Application/Acq/Financials.pm
new file mode 100644 (file)
index 0000000..5125386
--- /dev/null
@@ -0,0 +1,855 @@
+package OpenILS::Application::Acq::Financials;
+use base qw/OpenILS::Application/;
+use strict; use warnings;
+
+use OpenSRF::Utils::Logger qw(:logger);
+use OpenILS::Utils::Fieldmapper;
+use OpenILS::Utils::CStoreEditor q/:funcs/;
+use OpenILS::Const qw/:const/;
+use OpenSRF::Utils::SettingsClient;
+use OpenILS::Event;
+use OpenILS::Application::AppUtils;
+my $U = 'OpenILS::Application::AppUtils';
+
+# ----------------------------------------------------------------------------
+# Funding Sources
+# ----------------------------------------------------------------------------
+
+__PACKAGE__->register_method(
+       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'},
+            {desc => 'funding source object to create', type => 'object'}
+        ],
+        return => {desc => 'The ID of the new funding_source'}
+    }
+);
+
+sub create_funding_source {
+    my($self, $conn, $auth, $funding_source) = @_;
+    my $e = new_editor(xact=>1, authtoken=>$auth);
+    return $e->die_event unless $e->checkauth;
+    return $e->die_event unless $e->allowed('ADMIN_FUNDING_SOURCE', $funding_source->owner);
+    $e->create_acq_funding_source($funding_source) or return $e->die_event;
+    $e->commit;
+    return $funding_source->id;
+}
+
+
+__PACKAGE__->register_method(
+       method => 'delete_funding_source',
+       api_name        => 'open-ils.acq.funding_source.delete',
+       signature => {
+        desc => 'Deletes a funding_source',
+        params => [
+            {desc => 'Authentication token', type => 'string'},
+            {desc => 'funding source ID', type => 'number'}
+        ],
+        return => {desc => '1 on success, Event on failure'}
+    }
+);
+
+sub delete_funding_source {
+    my($self, $conn, $auth, $funding_source_id) = @_;
+    my $e = new_editor(xact=>1, authtoken=>$auth);
+    return $e->die_event unless $e->checkauth;
+    my $funding_source = $e->retrieve_acq_funding_source($funding_source_id) or return $e->die_event;
+    return $e->die_event unless $e->allowed('ADMIN_FUNDING_SOURCE', $funding_source->owner, $funding_source);
+    $e->delete_acq_funding_source($funding_source) or return $e->die_event;
+    $e->commit;
+    return 1;
+}
+
+__PACKAGE__->register_method(
+       method => 'retrieve_funding_source',
+       api_name        => 'open-ils.acq.funding_source.retrieve',
+       signature => {
+        desc => 'Retrieves a new funding_source',
+        params => [
+            {desc => 'Authentication token', type => 'string'},
+            {desc => 'funding source ID', type => 'number'}
+        ],
+        return => {desc => 'The funding_source object on success, Event on failure'}
+    }
+);
+
+sub retrieve_funding_source {
+    my($self, $conn, $auth, $funding_source_id, $options) = @_;
+    my $e = new_editor(authtoken=>$auth);
+    return $e->event unless $e->checkauth;
+    $options ||= {};
+
+    my $flesh = {flesh => 1, flesh_fields => {acqfs => []}};
+    push(@{$flesh->{flesh_fields}->{acqfs}}, 'credits') if $$options{flesh_credits};
+    push(@{$flesh->{flesh_fields}->{acqfs}}, 'allocations') if $$options{flesh_allocations};
+
+    my $funding_source = $e->retrieve_acq_funding_source([$funding_source_id, $flesh]) or return $e->event;
+
+    return $e->event unless $e->allowed(
+        ['ADMIN_FUNDING_SOURCE','MANAGE_FUNDING_SOURCE', 'VIEW_FUNDING_SOURCE'], 
+        $funding_source->owner, $funding_source); 
+
+    $funding_source->summary(retrieve_funding_source_summary_impl($e, $funding_source))
+        if $$options{flesh_summary};
+    return $funding_source;
+}
+
+__PACKAGE__->register_method(
+       method => 'retrieve_org_funding_sources',
+       api_name        => 'open-ils.acq.funding_source.org.retrieve',
+    stream => 1,
+       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'},
+            {desc => 'List of org Unit IDs.  If no IDs are provided, this method returns the 
+                full set of funding sources this user has permission to view', type => 'number'},
+            {desc => q/Limiting permission.  this permission is used find the work-org tree from which  
+                the list of orgs is generated if no org ids are provided.  
+                The default is ADMIN_FUNDING_SOURCE/, type => 'string'},
+        ],
+        return => {desc => 'The funding_source objects on success, empty array otherwise'}
+    }
+);
+
+sub retrieve_org_funding_sources {
+    my($self, $conn, $auth, $org_id_list, $options) = @_;
+    my $e = new_editor(authtoken=>$auth);
+    return $e->event unless $e->checkauth;
+    $options ||= {};
+
+    my $limit_perm = ($$options{limit_perm}) ? $$options{limit_perm} : 'ADMIN_FUNDING_SOURCE';
+    return OpenILS::Event->new('BAD_PARAMS') 
+        unless $limit_perm =~ /(ADMIN|MANAGE|VIEW)_FUNDING_SOURCE/;
+
+    my $org_ids = ($org_id_list and @$org_id_list) ? $org_id_list :
+        $U->find_highest_work_orgs($e, $limit_perm, {descendants =>1});
+
+    return [] unless @$org_ids;
+    my $sources = $e->search_acq_funding_source({owner => $org_ids});
+
+    for my $source (@$sources) {
+        $source->summary(retrieve_funding_source_summary_impl($e, $source))
+            if $$options{flesh_summary};
+        $conn->respond($source);
+    }
+
+    return undef;
+}
+
+sub retrieve_funding_source_summary_impl {
+    my($e, $source) = @_;
+    my $at = $e->search_acq_funding_source_allocation_total({funding_source => $source->id})->[0];
+    my $b = $e->search_acq_funding_source_balance({funding_source => $source->id})->[0];
+    my $ct = $e->search_acq_funding_source_credit_total({funding_source => $source->id})->[0];
+    return {
+        allocation_total => ($at) ? $at->amount : 0,
+        balance => ($b) ? $b->amount : 0,
+        credit_total => ($ct) ? $ct->amount : 0,
+    };
+}
+
+
+__PACKAGE__->register_method(
+       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'},
+            {desc => 'funding source credit object', type => 'object'}
+        ],
+        return => {desc => 'The ID of the new funding source credit on success, Event on failure'}
+    }
+);
+
+sub create_funding_source_credit {
+    my($self, $conn, $auth, $fs_credit) = @_;
+    my $e = new_editor(authtoken=>$auth, xact=>1);
+    return $e->event unless $e->checkauth;
+
+    my $fs = $e->retrieve_acq_funding_source($fs_credit->funding_source)
+        or return $e->die_event;
+    return $e->die_event unless $e->allowed(['MANAGE_FUNDING_SOURCE'], $fs->owner, $fs); 
+
+    $e->create_acq_funding_source_credit($fs_credit) or return $e->die_event;
+    $e->commit;
+    return $fs_credit->id;
+}
+
+
+# ---------------------------------------------------------------
+# funds
+# ---------------------------------------------------------------
+
+__PACKAGE__->register_method(
+       method => 'create_fund',
+       api_name        => 'open-ils.acq.fund.create',
+       signature => {
+        desc => 'Creates a new fund',
+        params => [
+            {desc => 'Authentication token', type => 'string'},
+            {desc => 'fund object to create', type => 'object'}
+        ],
+        return => {desc => 'The ID of the newly created fund object'}
+    }
+);
+
+sub create_fund {
+    my($self, $conn, $auth, $fund) = @_;
+    my $e = new_editor(xact=>1, authtoken=>$auth);
+    return $e->die_event unless $e->checkauth;
+    return $e->die_event unless $e->allowed('ADMIN_FUND', $fund->org);
+    $e->create_acq_fund($fund) or return $e->die_event;
+    $e->commit;
+    return $fund->id;
+}
+
+
+__PACKAGE__->register_method(
+       method => 'delete_fund',
+       api_name        => 'open-ils.acq.fund.delete',
+       signature => {
+        desc => 'Deletes a fund',
+        params => {
+            {desc => 'Authentication token', type => 'string'},
+            {desc => 'fund ID', type => 'number'}
+        },
+        return => {desc => '1 on success, Event on failure'}
+    }
+);
+
+sub delete_fund {
+    my($self, $conn, $auth, $fund_id) = @_;
+    my $e = new_editor(xact=>1, authtoken=>$auth);
+    return $e->die_event unless $e->checkauth;
+    my $fund = $e->retrieve_acq_fund($fund_id) or return $e->die_event;
+    return $e->die_event unless $e->allowed('ADMIN_FUND', $fund->org, $fund);
+    $e->delete_acq_fund($fund) or return $e->die_event;
+    $e->commit;
+    return 1;
+}
+
+__PACKAGE__->register_method(
+       method => 'retrieve_fund',
+       api_name        => 'open-ils.acq.fund.retrieve',
+       signature => {
+        desc => 'Retrieves a new fund',
+        params => [
+            {desc => 'Authentication token', type => 'string'},
+            {desc => 'fund ID', type => 'number'}
+        ],
+        return => {desc => 'The fund object on success, Event on failure'}
+    }
+);
+
+sub retrieve_fund {
+    my($self, $conn, $auth, $fund_id, $options) = @_;
+    my $e = new_editor(authtoken=>$auth);
+    return $e->event unless $e->checkauth;
+    $options ||= {};
+
+    my $flesh = {flesh => 2, flesh_fields => {acqf => []}};
+    push(@{$flesh->{flesh_fields}->{acqf}}, 'debits') if $$options{flesh_debits};
+    push(@{$flesh->{flesh_fields}->{acqf}}, 'allocations') if $$options{flesh_allocations};
+    push(@{$flesh->{flesh_fields}->{acqfa}}, 'funding_source') if $$options{flesh_allocation_sources};
+
+    my $fund = $e->retrieve_acq_fund([$fund_id, $flesh]) or return $e->event;
+    return $e->event unless $e->allowed(['ADMIN_FUND','MANAGE_FUND', 'VIEW_FUND'], $fund->org, $fund);
+    $fund->summary(retrieve_fund_summary_impl($e, $fund))
+        if $$options{flesh_summary};
+    return $fund;
+}
+
+__PACKAGE__->register_method(
+       method => 'retrieve_org_funds',
+       api_name        => 'open-ils.acq.fund.org.retrieve',
+    stream => 1,
+       signature => {
+        desc => 'Retrieves all the funds associated with an org unit',
+        params => [
+            {desc => 'Authentication token', type => 'string'},
+            {desc => 'List of org Unit IDs.  If no IDs are provided, this method returns the 
+                full set of funding sources this user has permission to view', type => 'number'},
+            {desc => q/Options hash.  
+                "limit_perm" -- this permission is used find the work-org tree from which  
+                the list of orgs is generated if no org ids are provided.  The default is ADMIN_FUND.
+                "flesh_summary" -- if true, the summary field on each fund is fleshed
+                The default is ADMIN_FUND/, type => 'string'},
+        ],
+        return => {desc => 'The fund objects on success, Event on failure'}
+    }
+);
+
+sub retrieve_org_funds {
+    my($self, $conn, $auth, $org_id_list, $options) = @_;
+    my $e = new_editor(authtoken=>$auth);
+    return $e->event unless $e->checkauth;
+    $options ||= {};
+
+    my $limit_perm = ($$options{limit_perm}) ? $$options{limit_perm} : 'ADMIN_FUND';
+    return OpenILS::Event->new('BAD_PARAMS') 
+        unless $limit_perm =~ /(ADMIN|MANAGE|VIEW)_FUND/;
+
+    my $org_ids = ($org_id_list and @$org_id_list) ? $org_id_list :
+        $U->find_highest_work_orgs($e, $limit_perm, {descendants =>1});
+    return undef unless @$org_ids;
+    my $funds = $e->search_acq_fund({org => $org_ids});
+
+    for my $fund (@$funds) {
+        $fund->summary(retrieve_fund_summary_impl($e, $fund))
+            if $$options{flesh_summary};
+        $conn->respond($fund);
+    }
+
+    return undef;
+}
+
+__PACKAGE__->register_method(
+       method => 'retrieve_fund_summary',
+       api_name        => 'open-ils.acq.fund.summary.retrieve',
+       signature => {
+        desc => 'Returns a summary of credits/debits/encumbrances for a fund',
+        params => [
+            {desc => 'Authentication token', type => 'string'},
+            {desc => 'fund id', type => 'number' }
+        ],
+        return => {desc => 'A hash of summary information, Event on failure'}
+    }
+);
+
+sub retrieve_fund_summary {
+    my($self, $conn, $auth, $fund_id) = @_;
+    my $e = new_editor(authtoken=>$auth);
+    return $e->event unless $e->checkauth;
+    my $fund = $e->retrieve_acq_fund($fund_id) or return $e->event;
+    return $e->event unless $e->allowed('MANAGE_FUND', $fund->org, $fund);
+    return retrieve_fund_summary_impl($e, $fund);
+}
+
+
+sub retrieve_fund_summary_impl {
+    my($e, $fund) = @_;
+
+    my $at = $e->search_acq_fund_allocation_total({fund => $fund->id})->[0];
+    my $dt = $e->search_acq_fund_debit_total({fund => $fund->id})->[0];
+    my $et = $e->search_acq_fund_encumbrance_total({fund => $fund->id})->[0];
+    my $st = $e->search_acq_fund_spent_total({fund => $fund->id})->[0];
+    my $cb = $e->search_acq_fund_combined_balance({fund => $fund->id})->[0];
+    my $sb = $e->search_acq_fund_spent_balance({fund => $fund->id})->[0];
+
+    return {
+        allocation_total => ($at) ? $at->amount : 0,
+        debit_total => ($dt) ? $dt->amount : 0,
+        encumbrance_total => ($et) ? $et->amount : 0,
+        spent_total => ($st) ? $st->amount : 0,
+        combined_balance => ($cb) ? $cb->amount : 0,
+        spent_balance => ($sb) ? $sb->amount : 0,
+    };
+}
+
+
+# ---------------------------------------------------------------
+# fund Allocations
+# ---------------------------------------------------------------
+
+__PACKAGE__->register_method(
+       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'},
+            {desc => 'fund allocation object to create', type => 'object'}
+        ],
+        return => {desc => 'The ID of the new fund_allocation'}
+    }
+);
+
+sub create_fund_alloc {
+    my($self, $conn, $auth, $fund_alloc) = @_;
+    my $e = new_editor(xact=>1, authtoken=>$auth);
+    return $e->die_event unless $e->checkauth;
+
+    # this action is equivalent to both debiting a funding source and crediting a fund
+
+    my $source = $e->retrieve_acq_funding_source($fund_alloc->funding_source)
+        or return $e->die_event;
+    return $e->die_event unless $e->allowed('MANAGE_FUNDING_SOURCE', $source->owner);
+
+    my $fund = $e->retrieve_acq_fund($fund_alloc->fund) or return $e->die_event;
+    return $e->die_event unless $e->allowed('MANAGE_FUND', $fund->org, $fund);
+
+    $fund_alloc->allocator($e->requestor->id);
+    $e->create_acq_fund_allocation($fund_alloc) or return $e->die_event;
+    $e->commit;
+    return $fund_alloc->id;
+}
+
+
+__PACKAGE__->register_method(
+       method => 'delete_fund_alloc',
+       api_name        => 'open-ils.acq.fund_allocation.delete',
+       signature => {
+        desc => 'Deletes a fund_allocation',
+        params => [
+            {desc => 'Authentication token', type => 'string'},
+            {desc => 'fund Alocation ID', type => 'number'}
+        ],
+        return => {desc => '1 on success, Event on failure'}
+    }
+);
+
+sub delete_fund_alloc {
+    my($self, $conn, $auth, $fund_alloc_id) = @_;
+    my $e = new_editor(xact=>1, authtoken=>$auth);
+    return $e->die_event unless $e->checkauth;
+
+    my $fund_alloc = $e->retrieve_acq_fund_allocation($fund_alloc_id) or return $e->die_event;
+
+    my $source = $e->retrieve_acq_funding_source($fund_alloc->funding_source)
+        or return $e->die_event;
+    return $e->die_event unless $e->allowed('MANAGE_FUNDING_SOURCE', $source->owner, $source);
+
+    my $fund = $e->retrieve_acq_fund($fund_alloc->fund) or return $e->die_event;
+    return $e->die_event unless $e->allowed('MANAGE_FUND', $fund->org, $fund);
+
+    $e->delete_acq_fund_allocation($fund_alloc) or return $e->die_event;
+    $e->commit;
+    return 1;
+}
+
+__PACKAGE__->register_method(
+       method => 'retrieve_fund_alloc',
+       api_name        => 'open-ils.acq.fund_allocation.retrieve',
+       signature => {
+        desc => 'Retrieves a new fund_allocation',
+        params => [
+            {desc => 'Authentication token', type => 'string'},
+            {desc => 'fund Allocation ID', type => 'number'}
+        ],
+        return => {desc => 'The fund allocation object on success, Event on failure'}
+    }
+);
+
+sub retrieve_fund_alloc {
+    my($self, $conn, $auth, $fund_alloc_id) = @_;
+    my $e = new_editor(authtoken=>$auth);
+    return $e->event unless $e->checkauth;
+    my $fund_alloc = $e->retrieve_acq_fund_allocation($fund_alloc_id) or return $e->event;
+
+    my $source = $e->retrieve_acq_funding_source($fund_alloc->funding_source)
+        or return $e->die_event;
+    return $e->die_event unless $e->allowed('MANAGE_FUNDING_SOURCE', $source->owner, $source);
+
+    my $fund = $e->retrieve_acq_fund($fund_alloc->fund) or return $e->die_event;
+    return $e->die_event unless $e->allowed('MANAGE_FUND', $fund->org, $fund);
+
+    return $fund_alloc;
+}
+
+
+__PACKAGE__->register_method(
+       method => 'retrieve_funding_source_allocations',
+       api_name        => 'open-ils.acq.funding_source.allocations.retrieve',
+       signature => {
+        desc => 'Retrieves a new fund_allocation',
+        params => [
+            {desc => 'Authentication token', type => 'string'},
+            {desc => 'fund Allocation ID', type => 'number'}
+        ],
+        return => {desc => 'The fund allocation object on success, Event on failure'}
+    }
+);
+
+sub retrieve_funding_source_allocations {
+    my($self, $conn, $auth, $fund_alloc_id) = @_;
+    my $e = new_editor(authtoken=>$auth);
+    return $e->event unless $e->checkauth;
+    my $fund_alloc = $e->retrieve_acq_fund_allocation($fund_alloc_id) or return $e->event;
+
+    my $source = $e->retrieve_acq_funding_source($fund_alloc->funding_source)
+        or return $e->die_event;
+    return $e->die_event unless $e->allowed('MANAGE_FUNDING_SOURCE', $source->owner, $source);
+
+    my $fund = $e->retrieve_acq_fund($fund_alloc->fund) or return $e->die_event;
+    return $e->die_event unless $e->allowed('MANAGE_FUND', $fund->org, $fund);
+
+    return $fund_alloc;
+}
+
+# ----------------------------------------------------------------------------
+# Currency
+# ----------------------------------------------------------------------------
+
+__PACKAGE__->register_method(
+       method => 'retrieve_all_currency_type',
+       api_name        => 'open-ils.acq.currency_type.all.retrieve',
+       signature => {
+        desc => 'Retrieves all currency_type objects',
+        params => [
+            {desc => 'Authentication token', type => 'string'},
+        ],
+        return => {desc => 'List of currency_type objects', type => 'list'}
+    }
+);
+
+sub retrieve_all_currency_type {
+    my($self, $conn, $auth, $fund_alloc_id) = @_;
+    my $e = new_editor(authtoken=>$auth);
+    return $e->event unless $e->checkauth;
+    return $e->event unless $e->allowed('GENERAL_ACQ');
+    return $e->retrieve_all_acq_currency_type();
+}
+
+sub currency_conversion_impl {
+    my($src_currency, $dest_currency, $amount) = @_;
+    my $result = new_editor()->json_query({
+        select => {
+            acqct => [{
+                params => [$dest_currency, $amount],
+                transform => 'acq.exchange_ratio',
+                column => 'code',
+                alias => 'value'
+            }]
+        },
+        where => {code => $src_currency},
+        from => 'acqct'
+    });
+
+    return $result->[0]->{value};
+}
+
+
+# ----------------------------------------------------------------------------
+# Purchase Orders
+# ----------------------------------------------------------------------------
+
+__PACKAGE__->register_method(
+       method => 'create_purchase_order',
+       api_name        => 'open-ils.acq.purchase_order.create',
+       signature => {
+        desc => 'Creates a new purchase order',
+        params => [
+            {desc => 'Authentication token', type => 'string'},
+            {desc => 'purchase_order to create', type => 'object'}
+        ],
+        return => {desc => 'The purchase order id, Event on failure'}
+    }
+);
+
+sub create_purchase_order {
+    my($self, $conn, $auth, $p_order) = @_;
+    my $e = new_editor(xact=>1, authtoken=>$auth);
+    return $e->die_event unless $e->checkauth;
+
+    $p_order->owner($e->requestor->id);
+    $p_order->ordering_agency($e->requestor->ws_ou);
+    return $e->die_event unless 
+        $e->allowed('CREATE_PURCHASE_ORDER', $p_order->ordering_agency);
+
+    my $provider = $e->retrieve_acq_provider($p_order->provider)
+        or return $e->die_event;
+    return $e->die_event unless 
+        $e->allowed('MANAGE_PROVIDER', $provider->owner, $provider);
+
+    $e->create_acq_purchase_order($p_order) or return $e->die_event;
+
+    $e->commit;
+    return $p_order->id;
+}
+
+
+# returns (price, type), where type=1 is local, type=2 is provider, type=3 is marc
+sub get_li_price {
+    my $li = shift;
+    my $attrs = $li->attributes;
+    my ($marc_estimated, $local_estimated, $local_actual, $prov_estimated, $prov_actual);
+
+    for my $attr (@$attrs) {
+        if($attr->attr_name eq 'estimated_price') {
+            $local_estimated = $attr->attr_value 
+                if $attr->attr_type eq 'lineitem_local_attr_definition';
+            $prov_estimated = $attr->attr_value 
+                if $attr->attr_type eq 'lineitem_prov_attr_definition';
+            $marc_estimated = $attr->attr_value
+                if $attr->attr_type eq 'lineitem_marc_attr_definition';
+
+        } elsif($attr->attr_name eq 'actual_price') {
+            $local_actual = $attr->attr_value     
+                if $attr->attr_type eq 'lineitem_local_attr_definition';
+            $prov_actual = $attr->attr_value 
+                if $attr->attr_type eq 'lineitem_prov_attr_definition';
+        }
+    }
+
+    return ($local_actual, 1) if $local_actual;
+    return ($prov_actual, 2) if $prov_actual;
+    return ($local_estimated, 1) if $local_estimated;
+    return ($prov_estimated, 2) if $prov_estimated;
+    return ($marc_estimated, 3);
+}
+
+
+__PACKAGE__->register_method(
+       method => 'create_purchase_order_debits',
+       api_name        => 'open-ils.acq.purchase_order.debits.create',
+       signature => {
+        desc => 'Creates debits associated with a PO',
+        params => [
+            {desc => 'Authentication token', type => 'string'},
+            {desc => 'purchase_order whose debits to create', type => 'number'},
+            {desc => 'arguments hash.  Options include: encumbrance=bool', type => 'object'},
+        ],
+        return => {desc => 'The total amount of all created debits, Event on error'}
+    }
+);
+
+sub create_purchase_order_debits {
+    my($self, $conn, $auth, $po_id, $args) = @_;
+    my $e = new_editor(xact=>1, authtoken=>$auth);
+    return $e->die_event unless $e->checkauth;
+    
+    my $total = 0;
+    my $po = $e->retrieve_acq_purchase_order($po_id) or return $e->die_event;
+    # XXX which perms?
+
+    my $li_ids = $e->search_acq_lineitem(
+        {purchase_order => $po_id},
+        {idlist => 1}
+    );
+
+    for my $li_id (@$li_ids) {
+        my $li = $e->retrieve_acq_lineitem([
+            $li_id,
+            {   flesh => 1,
+                flesh_fields => {jub => ['attributes']},
+            }
+        ]);
+
+        my ($price, $ptype) = get_li_price($li);
+        unless($price) {
+            $e->rollback;
+            return OpenILS::Event->new('ACQ_LINEITEM_NO_PRICE', payload => $li->id);
+        }
+
+        unless($li->provider) {
+            $e->rollback;
+            return OpenILS::Event->new('ACQ_LINEITEM_NO_PROVIDER', payload => $li->id);
+        }
+
+        my $lid_ids = $e->search_acq_lineitem_detail(
+            {lineitem => $li->id}, 
+            {idlist=>1}
+        );
+
+        for my $lid_id (@$lid_ids) {
+            my $lid = $e->retrieve_acq_lineitem_detail([
+                $lid_id,
+                {   flesh => 1, 
+                    flesh_fields => {acqlid => ['fund']}
+                }
+            ]);
+
+            my $debit = Fieldmapper::acq::fund_debit->new;
+            $debit->fund($lid->fund->id);
+            $debit->origin_amount($price);
+
+            if($ptype == 2) { # price from vendor
+                $debit->origin_currency_type($li->provider->currency_type);
+                $debit->amount(currency_conversion_impl(
+                    $li->provider->currency_type, $lid->fund->currency_type, $price));
+            } else {
+                $debit->origin_currency_type($lid->fund->currency_type);
+                $debit->amount($price);
+            }
+
+            $debit->encumbrance($args->{encumbrance});
+            $debit->debit_type('purchase');
+            $e->create_acq_fund_debit($debit) or return $e->die_event;
+
+            # point the lineitem detail at the fund debit object
+            $lid->fund_debit($debit->id);
+            $lid->fund($lid->fund->id);
+            $e->update_acq_lineitem_detail($lid) or return $e->die_event;
+            $total += $debit->amount;
+        }
+    }
+
+    $e->commit;
+    return $total;
+}
+
+
+__PACKAGE__->register_method(
+       method => 'retrieve_all_user_purchase_order',
+       api_name        => 'open-ils.acq.purchase_order.user.all.retrieve',
+    stream => 1,
+       signature => {
+        desc => 'Retrieves a purchase order',
+        params => [
+            {desc => 'Authentication token', type => 'string'},
+            {desc => 'purchase_order to retrieve', type => 'number'},
+            {desc => q/Options hash.  flesh_lineitems: to get the lineitems and lineitem_attrs; 
+                clear_marc: to clear the MARC data from the lineitem (for reduced bandwidth);
+                limit: number of items to return ,defaults to 50;
+                offset: offset in the list of items to return
+                order_by: sort the result, provide one or more colunm names, separated by commas,
+                optionally followed by ASC or DESC as a single string 
+                li_limit : number of lineitems to return if fleshing line items;
+                li_offset : lineitem offset if fleshing line items
+                li_order_by : lineitem sort definition if fleshing line items
+                flesh_lineitem_detail_count : flesh lineitem_detail_count field
+                /,
+                type => 'hash'}
+        ],
+        return => {desc => 'The purchase order, Event on failure'}
+    }
+);
+
+sub retrieve_all_user_purchase_order {
+    my($self, $conn, $auth, $options) = @_;
+    my $e = new_editor(authtoken=>$auth);
+    return $e->event unless $e->checkauth;
+    $options ||= {};
+
+    # grab purchase orders I have 
+    my $perm_orgs = $U->find_highest_work_orgs($e, 'MANAGE_PROVIDER', {descendants =>1});
+       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});
+
+    # grab my purchase orders
+    push(@$po_ids, @{$e->search_acq_purchase_order({owner => $e->requestor->id}, {idlist=>1})});
+
+    return undef unless @$po_ids;
+
+    # now get the db to limit/sort for us
+    $po_ids = $e->search_acq_purchase_order(
+        [   {id => $po_ids}, {
+                limit => $$options{limit} || 50,
+                offset => $$options{offset} || 0,
+                order_by => {acqpo => $$options{order_by} || 'create_time'}
+            }
+        ],
+        {idlist => 1}
+    );
+
+    $conn->respond(retrieve_purchase_order_impl($e, $_, $options)) for @$po_ids;
+    return undef;
+}
+
+
+__PACKAGE__->register_method(
+       method => 'search_purchase_order',
+       api_name        => 'open-ils.acq.purchase_order.search',
+    stream => 1,
+       signature => {
+        desc => 'Search for a purchase order',
+        params => [
+            {desc => 'Authentication token', type => 'string'},
+            {desc => q/Search hash.  Search fields include id, provider/, type => 'hash'}
+        ],
+        return => {desc => 'A stream of POs'}
+    }
+);
+
+sub search_purchase_order {
+    my($self, $conn, $auth, $search, $options) = @_;
+    my $e = new_editor(authtoken=>$auth);
+    return $e->event unless $e->checkauth;
+    my $po_ids = $e->search_acq_purchase_order($search, {idlist=>1});
+    for my $po_id (@$po_ids) {
+        $conn->respond($e->retrieve_acq_purchase_order($po_id))
+            unless po_perm_failure($e, $po_id);
+    }
+
+    return undef;
+}
+
+
+
+__PACKAGE__->register_method(
+       method => 'retrieve_purchase_order',
+       api_name        => 'open-ils.acq.purchase_order.retrieve',
+       signature => {
+        desc => 'Retrieves a purchase order',
+        params => [
+            {desc => 'Authentication token', type => 'string'},
+            {desc => 'purchase_order to retrieve', type => 'number'},
+            {desc => q/Options hash.  flesh_lineitems, to get the lineitems and lineitem_attrs; 
+                clear_marc, to clear the MARC data from the lineitem (for reduced bandwidth)
+                li_limit : number of lineitems to return if fleshing line items;
+                li_offset : lineitem offset if fleshing line items
+                li_order_by : lineitem sort definition if fleshing line items
+                /, 
+                type => 'hash'}
+        ],
+        return => {desc => 'The purchase order, Event on failure'}
+    }
+);
+
+sub retrieve_purchase_order {
+    my($self, $conn, $auth, $po_id, $options) = @_;
+    my $e = new_editor(authtoken=>$auth);
+    return $e->event unless $e->checkauth;
+    return $e->event if po_perm_failure($e, $po_id);
+    return retrieve_purchase_order_impl($e, $po_id, $options);
+}
+
+
+# if the user does not have permission to perform actions on this PO, return the perm failure event
+sub po_perm_failure {
+    my($e, $po_id, $fund_id) = @_;
+    my $po = $e->retrieve_acq_purchase_order($po_id) or return $e->event;
+    my $provider = $e->retrieve_acq_provider($po->provider) or return $e->event;
+    return $e->event unless $e->allowed('MANAGE_PROVIDER', $provider->owner, $provider);
+    if($fund_id) {
+        my $fund = $e->retrieve_acq_fund($po->$fund_id);
+        return $e->event unless $e->allowed('MANAGE_FUND', $fund->org, $fund);
+    }
+    return undef;
+}
+
+sub retrieve_purchase_order_impl {
+    my($e, $po_id, $options) = @_;
+
+    $options ||= {};
+    my $po = $e->retrieve_acq_purchase_order($po_id) or return $e->event;
+
+    if($$options{flesh_lineitems}) {
+        my $items = $e->search_acq_lineitem([
+            {purchase_order => $po_id},
+            {
+                flesh => 1,
+                flesh_fields => {
+                    jub => ['attributes']
+                },
+                limit => $$options{li_limit} || 50,
+                offset => $$options{li_offset} || 0,
+                order_by => {jub => $$options{li_order_by} || 'create_time'}
+            }
+        ]);
+
+        if($$options{clear_marc}) {
+            $_->clear_marc for @$items;
+        }
+
+        $po->lineitems($items);
+    }
+
+    if($$options{flesh_lineitem_count}) {
+        my $items = $e->search_acq_lineitem({purchase_order => $po_id}, {idlist=>1});
+        $po->lineitem_count(scalar(@$items));
+    }
+
+    return $po;
+}
+
+
+1;
+
diff --git a/Open-ILS/src/perlmods/OpenILS/Application/Acq/Lineitem.pm b/Open-ILS/src/perlmods/OpenILS/Application/Acq/Lineitem.pm
new file mode 100644 (file)
index 0000000..f3cfb8d
--- /dev/null
@@ -0,0 +1,903 @@
+package OpenILS::Application::Acq::Picklist;
+use base qw/OpenILS::Application/;
+use strict; use warnings;
+
+use OpenILS::Event;
+use OpenSRF::Utils::Logger qw(:logger);
+use OpenILS::Utils::Fieldmapper;
+use OpenILS::Utils::CStoreEditor q/:funcs/;
+use OpenILS::Const qw/:const/;
+use OpenSRF::Utils::SettingsClient;
+use OpenILS::Application::AppUtils;
+my $U = 'OpenILS::Application::AppUtils';
+
+
+__PACKAGE__->register_method(
+       method => 'create_lineitem',
+       api_name        => 'open-ils.acq.lineitem.create',
+       signature => {
+        desc => 'Creates a lineitem',
+        params => [
+            {desc => 'Authentication token', type => 'string'},
+            {desc => 'The lineitem object to create', type => 'object'},
+        ],
+        return => {desc => 'ID of newly created lineitem on success, Event on error'}
+    }
+);
+
+sub create_lineitem {
+    my($self, $conn, $auth, $li) = @_;
+    my $e = new_editor(xact=>1, authtoken=>$auth);
+    return $e->die_event unless $e->checkauth;
+
+
+    if($li->picklist) {
+        my $picklist = $e->retrieve_acq_picklist($li->picklist)
+            or return $e->die_event;
+
+        if($picklist->owner != $e->requestor->id) {
+            return $e->die_event unless 
+                $e->allowed('CREATE_PICKLIST', $picklist->org_unit, $picklist);
+        }
+    
+        # indicate the picklist was updated
+        $picklist->edit_time('now');
+        $e->update_acq_picklist($picklist) or return $e->die_event;
+    }
+
+    if($li->purchase_order) {
+        my $po = $e->retrieve_acq_purchase_order($li->purchase_order)
+            or return $e->die_event;
+        return $e->die_event unless 
+            $e->allowed('MANAGE_PROVIDER', $po->ordering_agency, $po);
+    }
+
+    $li->selector($e->requestor->id);
+    $e->create_acq_lineitem($li) or return $e->die_event;
+
+    $e->commit;
+    return $li->id;
+}
+
+__PACKAGE__->register_method(
+       method => 'create_po_assets',
+       api_name        => 'open-ils.acq.purchase_order.assets.create',
+       signature => {
+        desc => q/Creates assets for each lineitem in the purchase order/,
+        params => [
+            {desc => 'Authentication token', type => 'string'},
+            {desc => 'The purchase order id', type => 'number'},
+            {desc => q/Options hash./}
+        ],
+        return => {desc => 'Streams a total versus completed counts object, event on error'}
+    }
+);
+
+sub create_po_assets {
+    my($self, $conn, $auth, $po_id, $options) = @_;
+    my $e = new_editor(authtoken=>$auth, xact=>1);
+    return $e->die_event unless $e->checkauth;
+
+    my $po = $e->retrieve_acq_purchase_order($po_id) or return $e->event;
+    return $e->die_event unless 
+        $e->allowed('CREATE_PURCHASE_ORDER', $po->ordering_agency);
+
+    my $li_ids = $e->search_acq_lineitem({purchase_order=>$po_id},{idlist=>1});
+    my $total = @$li_ids;
+    my $count = 0;
+
+    for my $li_id (@$li_ids) {
+        my $resp = create_lineitem_assets_impl($e, $auth, $li_id);
+        if($U->event_code($resp)) {
+            $e->rollback;
+            return $resp;
+        }
+        $conn->respond({total=>$count, progress=>++$count});
+    }
+
+    $po->edit_time('now');
+    $e->update_acq_purchase_order($po) or return $e->die_event;
+    $e->commit;
+
+    return {complete=>1};
+}
+
+__PACKAGE__->register_method(
+       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'},
+            {desc => 'The lineitem id', type => 'number'},
+            {desc => q/Options hash./}
+        ],
+        return => {desc => 'ID of newly created bib record, Event on error'}
+    }
+);
+
+sub create_lineitem_assets {
+    my($self, $conn, $auth, $li_id, $options) = @_;
+    my $e = new_editor(authtoken=>$auth, xact=>1);
+    return $e->die_event unless $e->checkauth;
+    my $resp = create_lineitem_assets_impl($e, $auth, $li_id, $options);
+    if($U->event_code($resp)) {
+        $e->rollback;
+        return $resp;
+    }
+    $e->commit;
+    return $resp;
+}
+
+sub create_lineitem_assets_impl {
+    my($e, $auth, $li_id, $options) = @_;
+    my $li = $e->retrieve_acq_lineitem([
+        $li_id,
+        {   flesh => 1,
+            flesh_fields => {jub => ['purchase_order']}
+        }
+    ]) or return $e->die_event;
+
+    return OpenILS::Event->new('BAD_PARAMS') # make this perm-based, not owner-based
+        unless $li->purchase_order->owner == $e->requestor->id;
+
+    # -----------------------------------------------------------------
+    # first, create the bib record if necessary
+    # -----------------------------------------------------------------
+    unless($li->eg_bib_id) {
+        my $record = $U->simplereq(
+            'open-ils.cat', 
+            'open-ils.cat.biblio.record.xml.import',
+            $auth, $li->marc, $li->source_label);
+
+        if($U->event_code($record)) {
+            $e->rollback;
+            return $record;
+        }
+
+        $li->eg_bib_id($record->id);
+        $e->update_acq_lineitem($li) or return $e->die_event;
+    }
+
+    my $li_details = $e->search_acq_lineitem_detail({lineitem => $li_id}, {idlist=>1});
+
+    # -----------------------------------------------------------------
+    # for each lineitem_detail, create the volume if necessary, create 
+    # a copy, and link them all together.
+    # -----------------------------------------------------------------
+    my %volcache;
+    for my $li_detail_id (@{$li_details}) {
+
+        my $li_detail = $e->retrieve_acq_lineitem_detail($li_detail_id)
+            or return $e->die_event;
+
+        my $volume = $volcache{$li_detail->cn_label};
+        unless($volume and $volume->owning_lib == $li_detail->owning_lib) {
+            my $vol_id = $U->simplereq(
+                'open-ils.cat',
+                'open-ils.cat.call_number.find_or_create',
+                $auth, $li_detail->cn_label, $li->eg_bib_id, $li_detail->owning_lib);
+            $volume = $e->retrieve_asset_call_number($vol_id) or return $e->die_event;
+            $volcache{$vol_id} = $volume;
+        }
+
+        if($U->event_code($volume)) {
+            $e->rollback;
+            return $volume;
+        }
+
+        my $copy = Fieldmapper::asset::copy->new;
+        $copy->isnew(1);
+        $copy->loan_duration(2);
+        $copy->fine_level(2);
+        $copy->status(OILS_COPY_STATUS_ON_ORDER);
+        $copy->barcode($li_detail->barcode);
+        $copy->location($li_detail->location);
+        $copy->call_number($volume->id);
+        $copy->circ_lib($volume->owning_lib);
+
+        my $stat = $U->simplereq(
+            'open-ils.cat',
+            'open-ils.cat.asset.copy.fleshed.batch.update', $auth, [$copy]);
+
+        if($U->event_code($stat)) {
+            $e->rollback;
+            return $stat;
+        }
+
+        my $new_copy = $e->search_asset_copy({deleted=>'f', barcode=>$copy->barcode})->[0]
+            or return $e->die_event;
+
+        $li_detail->eg_copy_id($new_copy->id);
+        $e->update_acq_lineitem_detail($li_detail) or return $e->die_event;
+    }
+
+    return 1;
+}
+
+
+
+__PACKAGE__->register_method(
+       method => 'retrieve_lineitem',
+       api_name        => 'open-ils.acq.lineitem.retrieve',
+       signature => {
+        desc => 'Retrieves a lineitem',
+        params => [
+            {desc => 'Authentication token', type => 'string'},
+            {desc => 'lineitem ID to retrieve', type => 'number'},
+            {options => q/Hash of options, including 
+                "flesh_attrs", which fleshes the attributes; 
+                "flesh_li_details", which fleshes the order details objects/, type => 'hash'},
+        ],
+        return => {desc => 'lineitem object on success, Event on error'}
+    }
+);
+
+
+sub retrieve_lineitem {
+    my($self, $conn, $auth, $li_id, $options) = @_;
+    my $e = new_editor(authtoken=>$auth);
+    return $e->die_event unless $e->checkauth;
+    $options ||= {};
+
+    # XXX finer grained perms...
+
+    my $li;
+
+    if($$options{flesh_attrs}) {
+        $li = $e->retrieve_acq_lineitem([
+            $li_id, {flesh => 1, flesh_fields => {jub => ['attributes']}}])
+            or return $e->event;
+    } else {
+        $li = $e->retrieve_acq_lineitem($li_id) or return $e->event;
+    }
+
+    if($$options{flesh_li_details}) {
+        my $ops = {
+            flesh => 1,
+            flesh_fields => {acqlid => []}
+        };
+        push(@{$ops->{flesh_fields}->{acqlid}}, 'fund') if $$options{flesh_fund};
+        push(@{$ops->{flesh_fields}->{acqlid}}, 'fund_debit') if $$options{flesh_fund_debit};
+        my $details = $e->search_acq_lineitem_detail([{lineitem => $li_id}, $ops]);
+        $li->lineitem_details($details);
+        $li->item_count(scalar(@$details));
+    } else {
+        my $details = $e->search_acq_lineitem_detail({lineitem => $li_id}, {idlist=>1});
+        $li->item_count(scalar(@$details));
+    }
+
+    if($li->picklist) {
+        my $picklist = $e->retrieve_acq_picklist($li->picklist)
+            or return $e->event;
+    
+        if($picklist->owner != $e->requestor->id) {
+            return $e->event unless 
+                $e->allowed('VIEW_PICKLIST', undef, $picklist);
+        }
+    }
+
+    $li->clear_marc if $$options{clear_marc};
+
+    return $li;
+}
+
+
+
+__PACKAGE__->register_method(
+       method => 'delete_lineitem',
+       api_name        => 'open-ils.acq.lineitem.delete',
+       signature => {
+        desc => 'Deletes a lineitem',
+        params => [
+            {desc => 'Authentication token', type => 'string'},
+            {desc => 'lineitem ID to delete', type => 'number'},
+        ],
+        return => {desc => '1 on success, Event on error'}
+    }
+);
+
+sub delete_lineitem {
+    my($self, $conn, $auth, $li_id) = @_;
+    my $e = new_editor(xact=>1, authtoken=>$auth);
+    return $e->die_event unless $e->checkauth;
+
+    my $li = $e->retrieve_acq_lineitem($li_id)
+        or return $e->die_event;
+
+    # XXX check state
+
+    if($li->picklist) {
+        my $picklist = $e->retrieve_acq_picklist($li->picklist)
+            or return $e->die_event;
+        return OpenILS::Event->new('BAD_PARAMS') 
+            if $picklist->owner != $e->requestor->id;
+    } else {
+        # check PO perms
+    }
+
+    # delete the attached lineitem_details
+    my $lid_ids = $e->search_acq_lineitem_detail(
+        {lineitem => $li_id}, {idlist=>1});
+
+    for my $lid_id (@$lid_ids) {
+        $e->delete_acq_lineitem_detail(
+            $e->retrieve_acq_lineitem_detail($lid_id))
+            or return $e->die_event;
+    }
+
+    $e->delete_acq_lineitem($li) or return $e->die_event;
+    $e->commit;
+    return 1;
+}
+
+
+__PACKAGE__->register_method(
+       method => 'update_lineitem',
+       api_name        => 'open-ils.acq.lineitem.update',
+       signature => {
+        desc => 'Update a lineitem',
+        params => [
+            {desc => 'Authentication token', type => 'string'},
+            {desc => 'lineitem object update', type => 'object'}
+        ],
+        return => {desc => '1 on success, Event on error'}
+    }
+);
+
+sub update_lineitem {
+    my($self, $conn, $auth, $li) = @_;
+    my $e = new_editor(xact=>1, authtoken=>$auth);
+    return $e->die_event unless $e->checkauth;
+
+    my $orig_li = $e->retrieve_acq_lineitem([
+        $li->id,
+        {   flesh => 1, # grab the lineitem with picklist attached
+            flesh_fields => {jub => ['picklist', 'purchase_order']}
+        }
+    ]) or return $e->die_event;
+
+    # the marc may have been cleared on retrieval...
+    $li->marc($e->retrieve_acq_lineitem($li->id)->marc)
+        unless $li->marc;
+
+    $e->update_acq_lineitem($li) or return $e->die_event;
+    $e->commit;
+    return 1;
+}
+
+__PACKAGE__->register_method(
+       method => 'lineitem_search',
+       api_name => 'open-ils.acq.lineitem.search',
+    stream => 1,
+       signature => {
+        desc => 'Searches lineitems',
+        params => [
+            {desc => 'Authentication token', type => 'string'},
+            {desc => 'Search definition', type => 'object'},
+            {desc => 'Optoins hash.  idlist=true', type => 'object'},
+            {desc => 'List of lineitems', type => 'object/number'},
+        ]
+    }
+);
+
+sub lineitem_search {
+    my($self, $conn, $auth, $search, $options) = @_;
+    my $e = new_editor(authtoken=>$auth, xact=>1);
+    return $e->event unless $e->checkauth;
+    return $e->event unless $e->allowed('CREATE_PICKLIST');
+    # XXX needs permissions consideration
+    my $lis = $e->search_acq_lineitem($search, {idlist=>1});
+    for my $li_id (@$lis) {
+        if($$options{idlist}) {
+            $conn->respond($li_id);
+        } else {
+            my $res = retrieve_lineitem($self, $conn, $auth, $li_id, $options);
+            $conn->respond($res) unless $U->event_code($res);
+        }
+    }
+    return undef;
+}
+
+
+__PACKAGE__->register_method(
+       method => 'lineitem_search_ident',
+       api_name => 'open-ils.acq.lineitem.search.ident',
+    stream => 1,
+       signature => {
+        desc => 'Performs a search against lineitem_attrs where ident is true',
+        params => [
+            {desc => 'Authentication token', type => 'string'},
+            {   desc => q/Search definition. Options are:
+                   attr_values : list of attribute values (required)
+                   li_states : list of lineitem states
+                   po_agencies : list of purchase order ordering agencies (org) ids
+                /,
+                type => 'object',
+            },
+            {   desc => q/
+                    Options hash.  Options are:
+                        idlist : if set, only return lineitem IDs
+                        clear_marc : if set, strip the MARC xml from the lineitem before delivery
+                        flesh_attrs : flesh lineitem attributes; 
+                /,
+                type => 'object',
+            }
+        ]
+    }
+);
+
+my $LI_ATTR_SEARCH = {
+    select => {acqlia => ['lineitem']},
+    from => {
+        acqlia => {
+            acqliad => {
+                field => 'id',
+                fkey => 'definition'
+            },
+            jub => {
+                field => 'id',
+                fkey => 'lineitem',
+                join => {
+                    acqpo => {
+                        field => 'id',
+                        fkey => 'purchase_order'
+                    }
+                }
+            }
+        }
+    }
+};
+
+sub lineitem_search_ident {
+    my($self, $conn, $auth, $search, $options) = @_;
+    my $e = new_editor(authtoken=>$auth, xact=>1);
+    return $e->event unless $e->checkauth;
+    # XXX needs permissions consideration
+
+    return [] unless $search;
+    my $attr_values = $search->{attr_values};
+    my $li_states = $search->{li_states};
+    my $po_agencies = $search->{po_agencies}; # XXX if none, base it on perms
+
+    my $where_clause = {
+        '-or' => [],
+        '+acqlia' => {
+            '+acqliad' => {ident => 't'},
+        }
+    };
+
+    push(@{$where_clause->{'-or'}}, {attr_value => {ilike => "%$_%"}}) for @$attr_values;
+
+    $where_clause->{'+jub'} = {state => {in => $li_states}}
+        if $li_states and @$li_states;
+
+    $where_clause->{'+acqpo'} = {ordering_agency => $po_agencies} 
+        if $po_agencies and @$po_agencies;
+
+    $LI_ATTR_SEARCH->{where} = $where_clause;
+
+    my $lis = $e->json_query($LI_ATTR_SEARCH);
+
+    for my $li_id_obj (@$lis) {
+        my $li_id = $li_id_obj->{lineitem};
+        if($$options{idlist}) {
+            $conn->respond($li_id);
+        } else {
+            my $li;
+            if($$options{flesh_attrs}) {
+                $li = $e->retrieve_acq_lineitem([
+                    $li_id, {flesh => 1, flesh_fields => {jub => ['attributes']}}])
+            } else {
+                $li = $e->retrieve_acq_lineitem($li_id);
+            }
+            $li->clear_marc if $$options{clear_marc};
+            $conn->respond($li);
+        }
+    }
+    return undef;
+}
+
+
+__PACKAGE__->register_method(
+       method => 'create_lineitem_detail',
+       api_name        => 'open-ils.acq.lineitem_detail.create',
+       signature => {
+        desc => q/Creates a new purchase order line item detail.  
+            Additionally creates the associated fund_debit/,
+        params => [
+            {desc => 'Authentication token', type => 'string'},
+            {desc => 'lineitem_detail to create', type => 'object'},
+        ],
+        return => {desc => 'The purchase order line item detail id, Event on failure'}
+    }
+);
+
+sub create_lineitem_detail {
+    my($self, $conn, $auth, $li_detail, $options) = @_;
+    my $e = new_editor(xact=>1, authtoken=>$auth);
+    return $e->die_event unless $e->checkauth;
+    $options ||= {};
+
+    my $li = $e->retrieve_acq_lineitem($li_detail->lineitem)
+        or return $e->die_event;
+
+    my $evt = update_li_edit_time($e, $li);
+    return $evt if $evt;
+
+    # XXX check lineitem provider perms
+
+    if($li_detail->fund) {
+        my $fund = $e->retrieve_acq_fund($li_detail->fund) or return $e->die_event;
+        return $e->die_event unless 
+            $e->allowed('MANAGE_FUND', $fund->org, $fund);
+    }
+
+    $e->create_acq_lineitem_detail($li_detail) or return $e->die_event;
+
+    unless($li_detail->barcode) {
+        my $pfx = $U->ou_ancestor_setting_value($li_detail->owning_lib, 'acq.tmp_barcode_prefix') || 'ACQ';
+        $li_detail->barcode($pfx.$li_detail->id);
+    }
+    unless($li_detail->cn_label) {
+        my $pfx = $U->ou_ancestor_setting_value($li_detail->owning_lib, 'acq.tmp_callnumber_prefix') || 'ACQ';
+        $li_detail->cn_label($pfx.$li_detail->id);
+    }
+
+    if(my $loc = $U->ou_ancestor_setting_value($li_detail->owning_lib, 'acq.default_copy_location')) {
+        $li_detail->location($loc);
+    }
+
+    $e->update_acq_lineitem_detail($li_detail) or return $e->die_event;
+
+    $e->commit;
+    return $li_detail if $$options{return_obj};
+    return $li_detail->id
+}
+
+__PACKAGE__->register_method(
+       method => 'update_lineitem_detail',
+       api_name        => 'open-ils.acq.lineitem_detail.update',
+       signature => {
+        desc => q/Updates a lineitem detail/,
+        params => [
+            {desc => 'Authentication token', type => 'string'},
+            {desc => 'lineitem_detail to update', type => 'object'},
+        ],
+        return => {desc => '1 on success, Event on failure'}
+    }
+);
+
+sub update_lineitem_detail {
+    my($self, $conn, $auth, $li_detail) = @_;
+    my $e = new_editor(xact=>1, authtoken=>$auth);
+    return $e->die_event unless $e->checkauth;
+
+    if($li_detail->fund) {
+        my $fund = $e->retrieve_acq_fund($li_detail->fund) or return $e->die_event;
+        return $e->die_event unless 
+            $e->allowed('MANAGE_FUND', $fund->org, $fund);
+    }
+
+    # XXX check lineitem perms
+
+    my $li = $e->retrieve_acq_lineitem($li_detail->lineitem)
+        or return $e->die_event;
+    my $evt = update_li_edit_time($e, $li);
+    return $evt if $evt;
+
+    $e->update_acq_lineitem_detail($li_detail) or return $e->die_event;
+    $e->commit;
+    return 1;
+}
+
+sub update_li_edit_time {
+    my ($e, $li) = @_;
+    # some lineitem edits are allowed after approval time...
+#    return OpenILS::Event->new('ACQ_LINEITEM_APPROVED', payload => $li->id)
+#        if $li->state eq 'approved';
+    $li->edit_time('now');
+    $e->update_acq_lineitem($li) or return $e->die_event;
+    return undef;
+}
+
+
+__PACKAGE__->register_method(
+       method => 'delete_lineitem_detail',
+       api_name        => 'open-ils.acq.lineitem_detail.delete',
+       signature => {
+        desc => q/Deletes a lineitem detail/,
+        params => [
+            {desc => 'Authentication token', type => 'string'},
+            {desc => 'lineitem_detail ID to delete', type => 'number'},
+        ],
+        return => {desc => '1 on success, Event on failure'}
+    }
+);
+
+sub delete_lineitem_detail {
+    my($self, $conn, $auth, $li_detail_id) = @_;
+    my $e = new_editor(xact=>1, authtoken=>$auth);
+    return $e->die_event unless $e->checkauth;
+    my $li_detail = $e->retrieve_acq_lineitem_detail([
+        $li_detail_id,
+        {   flesh => 1,
+            flesh_fields => {acqlid => ['lineitem']}
+        }
+    ]) or return $e->die_event;
+
+    my $li = $li_detail->lineitem;
+
+    my $evt = update_li_edit_time($e, $li);
+    return $evt if $evt;
+
+    return OpenILS::Event->new('BAD_PARAMS') unless 
+        $li->state =~ /new|approved/;
+
+    # XXX check lineitem perms
+
+    $e->delete_acq_lineitem_detail($li_detail) or return $e->die_event;
+    $e->commit;
+    return 1;
+}
+
+
+__PACKAGE__->register_method(
+       method => 'retrieve_lineitem_detail',
+       api_name        => 'open-ils.acq.lineitem_detail.retrieve',
+       signature => {
+        desc => q/Updates a lineitem detail/,
+        params => [
+            {desc => 'Authentication token', type => 'string'},
+            {desc => 'id of lineitem_detail to retrieve', type => 'number'},
+        ],
+        return => {desc => 'object on success, Event on failure'}
+    }
+);
+sub retrieve_lineitem_detail {
+    my($self, $conn, $auth, $li_detail_id) = @_;
+    my $e = new_editor(authtoken=>$auth);
+    return $e->event unless $e->checkauth;
+
+    my $li_detail = $e->retrieve_acq_lineitem_detail($li_detail_id)
+        or return $e->event;
+
+    if($li_detail->fund) {
+        my $fund = $e->retrieve_acq_fund($li_detail->fund) or return $e->event;
+        return $e->event unless 
+            $e->allowed('MANAGE_FUND', $fund->org, $fund);
+    }
+
+    # XXX check lineitem perms
+    return $li_detail;
+}
+
+
+
+__PACKAGE__->register_method(
+       method => 'approve_lineitem',
+       api_name        => 'open-ils.acq.lineitem.approve',
+       signature => {
+        desc => 'Mark a lineitem as approved',
+        params => [
+            {desc => 'Authentication token', type => 'string'},
+            {desc => 'lineitem ID', type => 'number'}
+        ],
+        return => {desc => '1 on success, Event on error'}
+    }
+);
+sub approve_lineitem {
+    my($self, $conn, $auth, $li_id) = @_;
+    my $e = new_editor(xact=>1, authtoken=>$auth);
+    return $e->die_event unless $e->checkauth;
+
+    # XXX perm checks for each lineitem detail
+
+    my $li = $e->retrieve_acq_lineitem($li_id)
+        or return $e->die_event;
+
+    return OpenILS::Event->new('ACQ_LINEITEM_APPROVED', payload => $li_id)
+        if $li->state eq 'approved';
+
+    my $details = $e->search_acq_lineitem_detail({lineitem => $li_id});
+    return OpenILS::Event->new('ACQ_LINEITEM_NO_COPIES', payload => $li_id)
+        unless scalar(@$details) > 0;
+
+    for my $detail (@$details) {
+        return OpenILS::Event->new('ACQ_LINEITEM_DETAIL_NO_FUND', payload => $detail->id)
+            unless $detail->fund;
+
+        return OpenILS::Event->new('ACQ_LINEITEM_DETAIL_NO_ORG', payload => $detail->id)
+            unless $detail->owning_lib;
+    }
+    
+    $li->state('approved');
+    $li->edit_time('now');
+    $e->update_acq_lineitem($li) or return $e->die_event;
+
+    $e->commit;
+    return 1;
+}
+
+
+__PACKAGE__->register_method(
+       method => 'receive_lineitem_detail',
+       api_name        => 'open-ils.acq.lineitem_detail.receive',
+       signature => {
+        desc => 'Mark a lineitem_detail as received',
+        params => [
+            {desc => 'Authentication token', type => 'string'},
+            {desc => 'lineitem detail ID', type => 'number'}
+        ],
+        return => {desc => '1 on success, Event on error'}
+    }
+);
+sub receive_lineitem_detail {
+    my($self, $conn, $auth, $lid_id) = @_;
+    my $e = new_editor(xact=>1, authtoken=>$auth);
+    return $e->die_event unless $e->checkauth;
+    my $resp = receive_lineitem_detail_impl($e, $lid_id);
+    if($resp) {$e->rollback; return $resp;}
+    $e->commit;
+    return 1;
+}
+
+sub receive_lineitem_detail_impl {
+    my($e, $lid_id) = @_;
+
+    my $lid = $e->retrieve_acq_lineitem_detail([
+        $lid_id,
+        {   flesh => 1,
+            flesh_fields => {
+                acqlid => ['fund_debit']
+            }
+        }
+    ]) or return $e->die_event;
+
+    return OpenILS::Event->new(
+        'ACQ_LINEITEM_DETAIL_RECEIVED') if $lid->recv_time;
+
+    $lid->recv_time('now');
+    $e->update_acq_lineitem_detail($lid) or return $e->die_event;
+
+    my $copy = $e->retrieve_asset_copy($lid->eg_copy_id)
+        or return $e->die_event;
+
+    $copy->status(OILS_COPY_STATUS_IN_PROCESS);
+    $copy->edit_date('now');
+    $copy->editor($e->requestor->id);
+    $e->update_asset_copy($copy) or return $e->die_event;
+
+    if($lid->fund_debit) {
+        $lid->fund_debit->encumbrance('f');
+        $e->update_acq_fund_debit($lid->fund_debit) or return $e->die_event;
+    }
+
+    # -------------------------------------------------------------
+    # if all of the lineitem details for this lineitem have 
+    # been received, mark the lineitem as received
+    # -------------------------------------------------------------
+    my $non_recv = $e->search_acq_lineitem_detail(
+        {recv_time => undef, lineitem => $lid->lineitem}, {idlist=>1});
+
+    return undef if @$non_recv;
+
+    my $li = $e->retrieve_acq_lineitem($lid->lineitem);
+    $li->state('received');
+    $li->edit_time('now');
+    $e->update_acq_lineitem($li) or return $e->die_event;
+
+    # -------------------------------------------------------------
+    # if all of the lineitems for this PO are received,
+    # mark the PO as received
+    # -------------------------------------------------------------
+    my $non_recv_li = $e->search_acq_lineitem(
+        {   purchase_order => $li->purchase_order, 
+            state => {'!=' => 'received'}
+        }, {idlist=>1});
+
+    return undef if @$non_recv_li;
+
+    my $po = $e->retrieve_acq_purchase_order($li->purchase_order);
+    $po->state('received');
+    $po->edit_time('now');
+    $e->update_acq_purchase_order($po) or return $e->die_event;
+
+    return undef;
+}
+
+
+__PACKAGE__->register_method(
+       method => 'set_lineitem_attr',
+       api_name        => 'open-ils.acq.lineitem_usr_attr.set',
+       signature => {
+        desc => 'Sets a lineitem_usr_attr value',
+        params => [
+            {desc => 'Authentication token', type => 'string'},
+            {desc => 'Lineitem ID', type => 'number'},
+            {desc => 'Attr name', type => 'string'},
+            {desc => 'Attr value', type => 'string'}
+        ],
+        return => {desc => '1 on success, Event on error'}
+    }
+);
+
+__PACKAGE__->register_method(
+       method => 'set_lineitem_attr',
+       api_name        => 'open-ils.acq.lineitem_local_attr.set',
+       signature => {
+        desc => 'Sets a lineitem_local_attr value',
+        params => [
+            {desc => 'Authentication token', type => 'string'},
+            {desc => 'Lineitem ID', type => 'number'},
+            {desc => 'Attr name', type => 'string'},
+            {desc => 'Attr value', type => 'string'}
+        ],
+        return => {desc => 'ID of the attr object on success, Event on error'}
+    }
+);
+
+
+sub set_lineitem_attr {
+    my($self, $conn, $auth, $li_id, $attr_name, $attr_value) = @_;
+    my $e = new_editor(xact=>1, authtoken=>$auth);
+    return $e->die_event unless $e->checkauth;
+
+    # XXX perm
+
+    my $attr_type = $self->api_name =~ /local_attr/ ?
+        'lineitem_local_attr_definition' : 'lineitem_usr_attr_definition';
+
+    my $attr = $e->search_acq_lineitem_attr({
+        lineitem => $li_id, 
+        attr_type => $attr_type,
+        attr_name => $attr_name})->[0];
+
+    my $find = "search_acq_$attr_type";
+
+    if($attr) {
+        $attr->attr_value($attr_value);
+        $e->update_acq_lineitem_attr($attr) or return $e->die_event;
+    } else {
+        $attr = Fieldmapper::acq::lineitem_attr->new;
+        $attr->lineitem($li_id);
+        $attr->attr_type($attr_type);
+        $attr->attr_name($attr_name);
+        $attr->attr_value($attr_value);
+
+        my $attr_def_id = $e->$find({code => $attr_name}, {idlist=>1})->[0] 
+            or return $e->die_event;
+        $attr->definition($attr_def_id);
+        $e->create_acq_lineitem_attr($attr) or return $e->die_event;
+    }
+
+    $e->commit;
+    return $attr->id;
+}
+
+__PACKAGE__->register_method(
+       method => 'get_lineitem_attr_defs',
+       api_name        => 'open-ils.acq.lineitem_attr_definition.retrieve.all',
+       signature => {
+        desc => 'Retrieve lineitem attr definitions',
+        params => [
+            {desc => 'Authentication token', type => 'string'},
+        ],
+        return => {desc => 'List of attr definitions'}
+    }
+);
+
+sub get_lineitem_attr_defs {
+    my($self, $conn, $auth) = @_;
+    my $e = new_editor(authtoken=>$auth);
+    return $e->event unless $e->checkauth;
+    my %results;
+    for my $type (qw/generated marc local usr provider/) {
+        my $call = "retrieve_all_acq_lineitem_${type}_attr_definition";
+        $results{$type} = $e->$call;
+    }
+    return \%results;
+}
+
+
+1;
diff --git a/Open-ILS/src/perlmods/OpenILS/Application/Acq/Picklist.pm b/Open-ILS/src/perlmods/OpenILS/Application/Acq/Picklist.pm
new file mode 100644 (file)
index 0000000..3176133
--- /dev/null
@@ -0,0 +1,461 @@
+package OpenILS::Application::Acq::Picklist;
+use base qw/OpenILS::Application/;
+use strict; use warnings;
+
+use OpenSRF::Utils::Logger qw(:logger);
+use OpenILS::Utils::Fieldmapper;
+use OpenILS::Utils::CStoreEditor q/:funcs/;
+use OpenILS::Const qw/:const/;
+use OpenSRF::Utils::SettingsClient;
+use OpenILS::Event;
+use OpenILS::Application::AppUtils;
+my $U = 'OpenILS::Application::AppUtils';
+
+
+__PACKAGE__->register_method(
+       method => 'create_picklist',
+       api_name        => 'open-ils.acq.picklist.create',
+       signature => {
+        desc => 'Creates a new picklist',
+        params => [
+            {desc => 'Authentication token', type => 'string'},
+            {desc => 'Picklist object to create', type => 'object'}
+        ],
+        return => {desc => 'The ID of the new picklist'}
+    }
+);
+
+sub create_picklist {
+    my($self, $conn, $auth, $picklist) = @_;
+    my $e = new_editor(xact=>1, authtoken=>$auth);
+    return $e->die_event unless $e->checkauth;
+    $picklist->org_unit($e->requestor->ws_ou) unless $picklist->org_unit;
+    return $e->die_event unless $e->allowed('CREATE_PICKLIST', $picklist->org_unit);
+    return OpenILS::Event->new('BAD_PARAMS')
+        unless $e->requestor->id == $picklist->owner;
+    $e->create_acq_picklist($picklist) or return $e->die_event;
+    $e->commit;
+    return $picklist->id;
+}
+
+
+__PACKAGE__->register_method(
+       method => 'update_picklist',
+       api_name        => 'open-ils.acq.picklist.update',
+       signature => {
+        desc => 'Updates a new picklist',
+        params => [
+            {desc => 'Authentication token', type => 'string'},
+            {desc => 'Picklist object to update', type => 'object'}
+        ],
+        return => {desc => '1 on success, Event on error'}
+    }
+);
+
+sub update_picklist {
+    my($self, $conn, $auth, $picklist) = @_;
+    my $e = new_editor(xact=>1, authtoken=>$auth);
+    return $e->die_event unless $e->checkauth;
+
+    # don't let them change the owner
+    my $o_picklist = $e->retrieve_acq_picklist($picklist->id)
+        or return $e->die_event;
+    if($o_picklist->owner != $e->requestor->id) {
+        return $e->die_event unless 
+            $e->allowed('UPDATE_PICKLIST', $o_picklist->org_unit);
+    }
+    return OpenILS::Event->new('BAD_PARAMS') unless $o_picklist->org_unit == $picklist->org_unit;
+
+    $e->update_acq_picklist($picklist) or return $e->die_event;
+    $e->commit;
+    return 1;
+}
+
+__PACKAGE__->register_method(
+       method => 'retrieve_picklist',
+       api_name        => 'open-ils.acq.picklist.retrieve',
+       signature => {
+        desc => 'Retrieves a picklist',
+        params => [
+            {desc => 'Authentication token', type => 'string'},
+            {desc => 'Picklist ID to retrieve', type => 'number'},
+            {desc => 'Options hash, including "flesh_lineitem_count" to get the count of attached entries', type => 'hash'},
+        ],
+        return => {desc => 'Picklist object on success, Event on error'}
+    }
+);
+
+sub retrieve_picklist {
+    my($self, $conn, $auth, $picklist_id, $options) = @_;
+    my $e = new_editor(authtoken=>$auth);
+    return $e->event unless $e->checkauth;
+
+    my $picklist = $e->retrieve_acq_picklist($picklist_id)
+        or return $e->event;
+
+    $picklist->entry_count(retrieve_lineitem_count($e, $picklist_id))
+        if $$options{flesh_lineitem_count};
+
+    if($e->requestor->id != $picklist->owner) {
+        return $e->event unless 
+            $e->allowed('VIEW_PICKLIST', $picklist->org_unit, $picklist);
+    }
+
+    $picklist->owner($e->retrieve_actor_user($picklist->owner)->usrname) 
+        if($$options{flesh_username});
+
+    return $picklist;
+}
+
+
+# Returns the number of entries associated with this picklist
+sub retrieve_lineitem_count {
+    my($e, $picklist_id) = @_;
+    my $count = $e->json_query({
+        select => { 
+            jub => [{transform => 'count', column => 'id', alias => 'count'}]
+        }, 
+        from => 'jub', 
+        where => {picklist => $picklist_id}}
+    );
+    return $count->[0]->{count};
+}
+
+
+
+__PACKAGE__->register_method(
+       method => 'retrieve_picklist_name',
+       api_name        => 'open-ils.acq.picklist.name.retrieve',
+       signature => {
+        desc => 'Retrieves a picklist by name.  Owner is implied by the caller',
+        params => [
+            {desc => 'Authentication token', type => 'string'},
+            {desc => 'Picklist name to retrieve', type => 'strin'},
+        ],
+        return => {desc => 'Picklist object on success, null on not found'}
+    }
+);
+
+sub retrieve_picklist_name {
+    my($self, $conn, $auth, $name) = @_;
+    my $e = new_editor(authtoken=>$auth);
+    return $e->event unless $e->checkauth;
+    my $picklist = $e->search_acq_picklist(
+        {name => $name, owner => $e->requestor->id})->[0];
+    if($e->requestor->id != $picklist->owner) {
+        return $e->event unless 
+            $e->allowed('VIEW_PICKLIST', $picklist->org_unit, $picklist);
+    }
+    return $picklist;
+}
+
+
+
+__PACKAGE__->register_method(
+       method => 'retrieve_user_picklist',
+       api_name        => 'open-ils.acq.picklist.user.retrieve',
+    stream => 1,
+       signature => {
+        desc => 'Retrieves a  user\'s picklists',
+        params => [
+            {desc => 'Authentication token', type => 'string'},
+            {desc => 'Options, including "idlist", whch forces the return
+                of a list of IDs instead of objects', type => 'hash'},
+        ],
+        return => {desc => 'Picklist object on success, Event on error'}
+    }
+);
+
+sub retrieve_user_picklist {
+    my($self, $conn, $auth, $options) = @_;
+    my $e = new_editor(authtoken=>$auth);
+    return $e->die_event unless $e->checkauth;
+
+    # don't grab the PL with name == "", because that is the designated temporary picklist
+    my $list = $e->search_acq_picklist(
+        {owner=>$e->requestor->id, name=>{'!='=>''}},
+        {idlist=>1}
+    );
+
+    for my $id (@$list) {
+        if($$options{idlist}) {
+            $conn->respond($id);
+        } else {
+            my $pl = $e->retrieve_acq_picklist($id);
+            $pl->entry_count(retrieve_lineitem_count($e, $id)) if $$options{flesh_lineitem_count};
+            $pl->owner($e->retrieve_actor_user($pl->owner)->usrname) if $$options{flesh_username};
+            $conn->respond($pl);
+        }
+    }
+
+    return undef;
+}
+
+
+__PACKAGE__->register_method(
+       method => 'retrieve_all_user_picklist',
+       api_name        => 'open-ils.acq.picklist.user.all.retrieve',
+    stream => 1,
+       signature => {
+        desc => 'Retrieves all of the picklists a user is allowed to see',
+        params => [
+            {desc => 'Authentication token', type => 'string'},
+            {desc => 'Options, including "idlist", whch forces the return
+                of a list of IDs instead of objects', type => 'hash'},
+        ],
+        return => {desc => 'Picklist objects on success, Event on error'}
+    }
+);
+
+sub retrieve_all_user_picklist {
+    my($self, $conn, $auth, $options) = @_;
+    my $e = new_editor(authtoken=>$auth);
+    return $e->event unless $e->checkauth;
+
+    my $my_list = $e->search_acq_picklist(
+        {owner=>$e->requestor->id, name=>{'!='=>''}}, {idlist=>1});
+
+    my $picklist_ids = $e->objects_allowed('VIEW_PICKLIST', 'acqpl');
+    my $p_orgs = $U->find_highest_work_orgs($e, 'VIEW_PICKLIST', {descendants =>1});
+    my $picklist_ids_2 = $e->search_acq_picklist(
+        {name=>{'!='=>''}, org_unit => $p_orgs}, {idlist=>1});
+
+    return undef unless @$my_list or @$picklist_ids or @$picklist_ids_2;
+
+    my @list = (@$my_list, @$picklist_ids, @$picklist_ids_2);
+    my %dedup;
+    $dedup{$_} = 1 for @list;
+    @list = keys %dedup;
+
+    return \@list if $$options{idlist};
+
+    for my $pl (@list) {
+        my $picklist = $e->retrieve_acq_picklist($pl) or return $e->event;
+        $picklist->entry_count(retrieve_lineitem_count($e, $picklist->id))
+            if($$options{flesh_lineitem_count});
+        $picklist->owner($e->retrieve_actor_user($picklist->owner)->usrname)
+            if $$options{flesh_username};
+        $conn->respond($picklist);
+    }
+
+    return undef;
+}
+
+
+__PACKAGE__->register_method(
+       method => 'delete_picklist',
+       api_name        => 'open-ils.acq.picklist.delete',
+       signature => {
+        desc => q/Deletes a picklist.  It also deletes any lineitems in the "new" state.  
+            Other attached lineitems are detached'/,
+        params => [
+            {desc => 'Authentication token', type => 'string'},
+            {desc => 'Picklist ID to delete', type => 'number'}
+        ],
+        return => {desc => '1 on success, Event on error'}
+    }
+);
+
+sub delete_picklist {
+    my($self, $conn, $auth, $picklist_id) = @_;
+    my $e = new_editor(xact=>1, authtoken=>$auth);
+    return $e->die_event unless $e->checkauth;
+
+    my $picklist = $e->retrieve_acq_picklist($picklist_id)
+        or return $e->die_event;
+    # don't let anyone delete someone else's picklist
+    if($picklist->owner != $e->requestor->id) {
+        return $e->die_event unless 
+            $e->allowed('DELETE_PICKLIST', $picklist->org_unit, $picklist);
+    }
+
+    # delete all 'new' lineitems
+    my $lis = $e->search_acq_lineitem({picklist => $picklist->id, state => 'new'});
+    for my $li (@$lis) {
+        $e->delete_acq_lineitem($li) or return $e->die_event;
+    }
+
+    # detach all non-'new' lineitems
+    $lis = $e->search_acq_lineitem({picklist => $picklist->id, state => {'!=' => 'new'}});
+    for my $li (@$lis) {
+        $li->clear_picklist;
+        $e->update_acq_lineitem($li) or return $e->die_event;
+    }
+
+    # remove any picklist-specific object perms
+    my $ops = $e->search_permission_usr_object_perm_map({object_type => 'acqpl', object_id => $picklist->id});
+    for my $op (@$ops) {
+        $e->delete_usr_object_perm_map($op) or return $e->die_event;
+    }
+
+
+    $e->delete_acq_picklist($picklist) or return $e->die_event;
+    $e->commit;
+    return 1;
+}
+
+__PACKAGE__->register_method(
+       method => 'retrieve_pl_lineitem',
+       api_name        => 'open-ils.acq.lineitem.picklist.retrieve',
+    stream => 1,
+       signature => {
+        desc => 'Retrieves lineitem objects according to picklist',
+        params => [
+            {desc => 'Authentication token', type => 'string'},
+            {desc => 'Picklist ID whose entries to retrieve', type => 'number'},
+            {desc => q/Options, including 
+                "sort_attr", which defines the attribute to sort on; 
+                "sort_attr_type", which defines the attribute type sort on; 
+                "sort_dir", which defines the sort order between "asc" and "desc";
+                "limit", retrieval limit;
+                "offset", retrieval offset;
+                "idlist", return a list of IDs instead of objects
+                "flesh_attrs", additionaly return the list of flattened attributes
+                "clear_marc", discards the raw MARC data to reduce data size
+                /, 
+                type => 'hash'}
+        ],
+        return => {desc => 'Array of lineitem objects or IDs,  on success, Event on error'}
+    }
+);
+
+
+my $PL_ENTRY_JSON_QUERY = {
+    select => {jub => ["id"], "acqlia" => ["attr_value"]},
+    "from" => {
+        "jub" => {
+            "acqlia" => {
+                "fkey" => "id", 
+                "field" => "lineitem", 
+                "type" => "left", 
+                "filter" => {
+                    "attr_type" => "lineitem_marc_attr_definition", 
+                    "attr_name" => "author" 
+                }
+            }
+        }
+    }, 
+    "order_by" => {"acqlia" => {"attr_value" => {"direction"=>"asc"}}}, 
+    "limit" => 10,
+    "where" => {"+jub" => {"picklist"=>2}},
+    "offset" => 0
+};
+
+sub retrieve_pl_lineitem {
+    my($self, $conn, $auth, $picklist_id, $options) = @_;
+    my $e = new_editor(authtoken=>$auth);
+    return $e->event unless $e->checkauth;
+
+    # collect the retrieval options
+    my $sort_attr = $$options{sort_attr} || 'title';
+    my $sort_attr_type = $$options{sort_attr_type} || 'lineitem_marc_attr_definition';
+    my $sort_dir = $$options{sort_dir} || 'asc';
+    my $limit = $$options{limit} || 10;
+    my $offset = $$options{offset} || 0;
+
+    $PL_ENTRY_JSON_QUERY->{where}->{'+jub'}->{picklist} = $picklist_id;
+    $PL_ENTRY_JSON_QUERY->{from}->{jub}->{acqlia}->{filter}->{attr_name} = $sort_attr;
+    $PL_ENTRY_JSON_QUERY->{from}->{jub}->{acqlia}->{filter}->{attr_type} = $sort_attr_type;
+    $PL_ENTRY_JSON_QUERY->{order_by}->{acqlia}->{attr_value}->{direction} = $sort_dir;
+    $PL_ENTRY_JSON_QUERY->{limit} = $limit;
+    $PL_ENTRY_JSON_QUERY->{offset} = $offset;
+
+    my $entries = $e->json_query($PL_ENTRY_JSON_QUERY);
+
+    my @ids;
+    push(@ids, $_->{id}) for @$entries;
+
+    for my $id (@ids) {
+        if($$options{idlist}) {
+            $conn->respond($id);
+            next;
+        } 
+
+        my $entry;
+        my $flesh = ($$options{flesh_attrs}) ? 
+            {flesh => 1, flesh_fields => {jub => ['attributes']}} : {};
+
+        $entry = $e->retrieve_acq_lineitem([$id, $flesh]);
+        my $details = $e->search_acq_lineitem_detail({lineitem => $id}, {idlist=>1});
+        $entry->item_count(scalar(@$details));
+        $entry->clear_marc if $$options{clear_marc};
+        $conn->respond($entry);
+    }
+
+    return undef;
+}
+
+=head comment
+request open-ils.cstore open-ils.cstore.json_query.atomic {"select":{"jub":[{"transform":"count", "attregate":1, "column":"id","alias":"count"}]}, "from":"jub","where":{"picklist":1}}
+=cut
+
+__PACKAGE__->register_method(
+       method => 'zsearch',
+       api_name => 'open-ils.acq.picklist.search.z3950',
+    stream => 1,
+       signature => {
+        desc => 'Performs a z3950 federated search and creates a picklist and associated lineitems',
+        params => [
+            {desc => 'Authentication token', type => 'string'},
+            {desc => 'Search definition', type => 'object'},
+            {desc => 'Picklist name, optional', type => 'string'},
+        ]
+    }
+);
+
+sub zsearch {
+    my($self, $conn, $auth, $search, $name) = @_;
+    my $e = new_editor(authtoken=>$auth, xact=>1);
+    return $e->die_event unless $e->checkauth;
+    return $e->die_event unless $e->allowed('CREATE_PICKLIST');
+
+    $search->{limit} ||= 10;
+
+    $name ||= '';
+    my $picklist = $e->search_acq_picklist({owner=>$e->requestor->id, name=>$name})->[0];
+    if($name eq '' and $picklist) {
+        my $evt = delete_picklist($self, $conn, $auth, $picklist->id);
+        return $evt unless $evt == 1;
+        $picklist = undef;
+    }
+
+    unless($picklist) {
+        $picklist = Fieldmapper::acq::picklist->new;
+        $picklist->owner($e->requestor->id);
+        $picklist->name($name);
+        $picklist->org_unit($e->requestor->ws_ou);
+        $e->create_acq_picklist($picklist) or return $e->die_event;
+    }
+
+    my $ses = OpenSRF::AppSession->create('open-ils.search');
+    my $req = $ses->request('open-ils.search.z3950.search_class', $auth, $search);
+
+    while(my $resp = $req->recv(timeout=>60)) {
+
+        my $result = $resp->content;
+        #use Data::Dumper;
+        #$logger->info("results = ".Dumper($resp));
+        my $count = $result->{count};
+        my $total = (($count < $search->{limit}) ? $count : $search->{limit})+1;
+        my $ctr = 0;
+        $conn->respond({total=>$total, progress=>++$ctr});
+
+        for my $rec (@{$result->{records}}) {
+            my $li = Fieldmapper::acq::lineitem->new;
+            $li->picklist($picklist->id);
+            $li->source_label($result->{service});
+            $li->selector($e->requestor->id);
+            $li->marc($rec->{marcxml});
+            $li->eg_bib_id($rec->{bibid}) if $rec->{bibid};
+            $e->create_acq_lineitem($li) or return $e->die_event;
+            $conn->respond({total=>$total, progress=>++$ctr});
+        }
+    }
+
+    $e->commit;
+    return {complete=>1, picklist_id=>$picklist->id};
+}
+
+
+
+1;
diff --git a/Open-ILS/src/perlmods/OpenILS/Application/Acq/Provider.pm b/Open-ILS/src/perlmods/OpenILS/Application/Acq/Provider.pm
new file mode 100644 (file)
index 0000000..6f22eb1
--- /dev/null
@@ -0,0 +1,183 @@
+package OpenILS::Application::Acq::Provider;
+use base qw/OpenILS::Application/;
+use strict; use warnings;
+
+use OpenILS::Event;
+use OpenILS::Const qw/:const/;
+use OpenSRF::Utils::Logger qw(:logger);
+use OpenILS::Utils::Fieldmapper;
+use OpenILS::Utils::CStoreEditor q/:funcs/;
+use OpenSRF::Utils::SettingsClient;
+use OpenILS::Application::AppUtils;
+
+my $U = 'OpenILS::Application::AppUtils';
+
+__PACKAGE__->register_method(
+       method => 'create_provider',
+       api_name        => 'open-ils.acq.provider.create',
+       signature => {
+        desc => 'Creates a new provider',
+        params => [
+            {desc => 'Authentication token', type => 'string'},
+            {desc => 'provider object to create', type => 'object'}
+        ],
+        return => {desc => 'The ID of the new provider'}
+    }
+);
+
+sub create_provider {
+    my($self, $conn, $auth, $provider) = @_;
+    my $e = new_editor(authtoken=>$auth, xact=>1);
+    return $e->die_event unless $e->checkauth;
+    return $e->die_event unless $e->allowed('ADMIN_PROVIDER', $provider->owner);
+    $e->create_acq_provider($provider) or return $e->die_event;
+    $e->commit;
+    return $provider->id;
+}
+
+
+
+__PACKAGE__->register_method(
+       method => 'retrieve_provider',
+       api_name        => 'open-ils.acq.provider.retrieve',
+       signature => {
+        desc => 'Retrieves a new provider',
+        params => [
+            {desc => 'Authentication token', type => 'string'},
+            {desc => 'provider ID', type => 'number'}
+        ],
+        return => {desc => 'The provider object on success, Event on failure'}
+    }
+);
+
+sub retrieve_provider {
+    my($self, $conn, $auth, $provider_id) = @_;
+    my $e = new_editor(authtoken=>$auth);
+    return $e->event unless $e->checkauth;
+    my $provider = $e->retrieve_acq_provider($provider_id) or return $e->event;
+    return $e->event unless $e->allowed(
+        ['ADMIN_PROVIDER', 'MANAGE_PROVIDER', 'VIEW_PROVIDER'], $provider->owner, $provider);
+    return $provider;
+}
+
+
+__PACKAGE__->register_method(
+       method => 'retrieve_org_providers',
+       api_name        => 'open-ils.acq.provider.org.retrieve',
+    stream => 1,
+       signature => {
+        desc => 'Retrieves all the providers associated with an org unit that the requestor has access to see',
+        params => [
+            {desc => 'Authentication token', type => 'string'},
+            {desc => 'List of org Unit IDs.  If no IDs are provided, this method returns the 
+                full set of funding sources this user has permission to view', type => 'number'},
+            {desc => q/Limiting permission.  this permission is used find the work-org tree from which  
+                the list of orgs is generated if no org ids are provided.  
+                The default is ADMIN_PROVIDER/, type => 'string'},
+        ],
+        return => {desc => 'The provider objects on success, empty array otherwise'}
+    }
+);
+
+sub retrieve_org_providers {
+    my($self, $conn, $auth, $org_id_list, $options) = @_;
+    my $e = new_editor(authtoken=>$auth);
+    return $e->event unless $e->checkauth;
+
+    my $limit_perm = ($$options{limit_perm}) ? $$options{limit_perm} : 'ADMIN_PROVIDER';
+
+    return OpenILS::Event->new('BAD_PARAMS')
+        unless $limit_perm =~ /(ADMIN|MANAGE|VIEW)_PROVIDER/;
+
+    my $org_ids = ($org_id_list and @$org_id_list) ? $org_id_list :
+        $U->find_highest_work_orgs($e, $limit_perm, {descendants =>1});
+
+    return [] unless @$org_ids;
+    $conn->respond($_) for @{$e->search_acq_provider({owner => $org_ids})};
+
+    return undef;
+}
+
+__PACKAGE__->register_method(
+       method => 'retrieve_provider_attr_def',
+       api_name        => 'open-ils.acq.lineitem_provider_attr_definition.provider.retrieve',
+    stream => 1,
+       signature => {
+        desc => 'Retrieves all of the lineitem_provider_attr_definition for a given provider',
+        params => [
+            {desc => 'Authentication token', type => 'string'},
+            {desc => 'Provider ID', type => 'number'}
+        ],
+        return => {desc => 'Streams a of lineitem_provider_attr_definition objects'}
+    }
+);
+
+sub retrieve_provider_attr_def {
+    my($self, $conn, $auth, $prov_id) = @_;
+    my $e = new_editor(authtoken=>$auth);
+    return $e->event unless $e->checkauth;
+    my $provider = $e->retrieve_acq_provider($prov_id)
+        or return $e->event;
+    return $e->event unless $e->allowed('ADMIN_PROVIDER', $provider->owner);
+    for my $id (@{$e->search_acq_lineitem_provider_attr_definition({provider=>$prov_id},{idlist=>1})}) {
+        $conn->respond($e->retrieve_acq_lineitem_provider_attr_definition($id));
+    }
+
+    return undef;
+}
+
+__PACKAGE__->register_method(
+       method => 'create_provider_attr_def',
+       api_name        => 'open-ils.acq.lineitem_provider_attr_definition.create',
+       signature => {
+        desc => 'Retrieves all of the lineitem_provider_attr_definition for a given provider',
+        params => [
+            {desc => 'Authentication token', type => 'string'},
+            {desc => 'Provider ID', type => 'number'}
+        ],
+        return => {desc => 'Streams a of lineitem_provider_attr_definition objects'}
+    }
+);
+
+sub create_provider_attr_def {
+    my($self, $conn, $auth, $attr_def) = @_;
+    my $e = new_editor(authtoken=>$auth, xact=>1);
+    return $e->die_event unless $e->checkauth;
+    my $provider = $e->retrieve_acq_provider($attr_def->provider)
+        or return $e->die_event;
+    return $e->die_event unless $e->allowed('ADMIN_PROVIDER', $provider->owner);
+    $e->create_acq_lineitem_provider_attr_definition($attr_def)
+        or return $e->die_event;
+    $e->commit;
+    return $attr_def->id;
+}
+
+__PACKAGE__->register_method(
+       method => 'delete_provider_attr_def',
+       api_name        => 'open-ils.acq.lineitem_provider_attr_definition.delete',
+       signature => {
+        desc => 'Deletes a lineitem_provider_attr_definition',
+        params => [
+            {desc => 'Authentication token', type => 'string'},
+            {desc => 'ID', type => 'number'}
+        ],
+        return => {desc => '1 on success, event on failure'}
+    }
+);
+
+sub delete_provider_attr_def {
+    my($self, $conn, $auth, $id) = @_;
+    my $e = new_editor(authtoken=>$auth, xact=>1);
+    return $e->die_event unless $e->checkauth;
+    my $attr_def = $e->retrieve_acq_lineitem_provider_attr_definition($id)
+        or return $e->die_event;
+    my $provider = $e->retrieve_acq_provider($attr_def->provider)
+        or return $e->die_event;
+    return $e->die_event unless $e->allowed('ADMIN_PROVIDER', $provider->owner);
+    $e->delete_acq_lineitem_provider_attr_definition($attr_def)
+        or return $e->die_event;
+    $e->commit;
+    return 1;
+}
+
+1;
index 145060a..687640b 100644 (file)
@@ -97,6 +97,9 @@ econst OILS_BILLING_TYPE_DEPOSIT => 'System: Deposit';
 econst OILS_BILLING_TYPE_RENTAL => 'System: Rental';
 econst OILS_BILLING_NOTE_SYSTEM => 'SYSTEM GENERATED';
 
+econst OILS_ACQ_DEBIT_TYPE_PURCHASE => 'purchase';
+econst OILS_ACQ_DEBIT_TYPE_TRANSFER => 'xfer';
+
 
 
 # ---------------------------------------------------------------------
diff --git a/Open-ILS/src/perlmods/OpenILS/Utils/MFHD.pm b/Open-ILS/src/perlmods/OpenILS/Utils/MFHD.pm
new file mode 100755 (executable)
index 0000000..98a1d1e
--- /dev/null
@@ -0,0 +1,58 @@
+package MFHD;
+use strict;
+use integer;
+use Carp;
+
+use MARC::Record;
+use MFHD::Caption;
+use MFHD::Holding;
+
+sub new {
+    my $proto = shift;
+    my $class = ref($proto) || $proto;
+    my $self = {};
+    my $rec = shift;
+
+    $self->{CAPTIONS} = {};
+    foreach my $caption ($rec->field('853')) {
+       my $cap_id;
+       $cap_id = $caption->subfield('8') || '0';
+       if (exists $self->{CAPTIONS}->{$cap_id}) {
+           carp "Multiple unlabelled MFHD captions";
+       }
+       $self->{CAPTIONS}->{$cap_id} = new MFHD::Caption($caption);
+    }
+
+    $self->{HOLDINGS} = {};
+    foreach my $holding ($rec->field('863')) {
+       my $linkage;
+       my ($link_id, $seqno);
+
+       $linkage = $holding->subfield('8');
+       ($link_id, $seqno) = split(/\./, $linkage);
+
+       if (!exists $self->{HOLDINGS}->{$link_id}) {
+           $self->{HOLDINGS}->{$link_id} = {};
+       }
+       $self->{HOLDINGS}->{$link_id}->{$seqno} =
+         new MFHD::Holding($seqno, $holding, $self->{CAPTIONS}->{$link_id});
+    }
+
+    bless ($self, $class);
+    return $self;
+}
+
+sub captions {
+    my $self = shift;
+
+    return sort keys %{$self->{CAPTIONS}}
+}
+
+sub holdings {
+    my $self = shift;
+    my $capid = shift;
+
+    return sort {$a->{SEQNO} cmp $b->{SEQNO}} values %{$self->{HOLDINGS}->{$capid}};
+}
+
+1;
diff --git a/Open-ILS/src/perlmods/OpenILS/Utils/MFHD/Caption.pm b/Open-ILS/src/perlmods/OpenILS/Utils/MFHD/Caption.pm
new file mode 100755 (executable)
index 0000000..b1abc20
--- /dev/null
@@ -0,0 +1,78 @@
+package MFHD::Caption;
+use strict;
+use integer;
+use Carp;
+
+use MARC::Record;
+
+sub new
+{
+    my $proto = shift;
+    my $class = ref($proto) || $proto;
+    my $caption = shift;
+    my $self = {};
+    my $last_enum = undef;
+
+    $self->{CAPTION} = $caption;
+    $self->{ENUMS} = {};
+    $self->{CHRONS} = {};
+    $self->{PATTERN} = {};
+    $self->{COPY} = undef;
+    $self->{UNIT} = undef;
+
+    foreach my $subfield ($caption->subfields) {
+       my ($key, $val) = @$subfield;
+       if ($key eq '8') {
+           $self->{LINK} = $val;
+       } elsif ($key =~ /[a-h]/) {
+           # Enumeration Captions
+           $self->{ENUMS}->{$key} = {CAPTION => $val,
+                                     COUNT => undef,
+                                     RESTART => undef};
+           if ($key =~ /[ag]/) {
+               $last_enum = undef;
+           } else {
+               $last_enum = $key;
+           }
+       } elsif ($key =~ /[i-m]/) {
+           # Chronology captions
+           $self->{CHRONS}->{$key} = $val;
+       } elsif ($key eq 'u') {
+           # Bib units per next higher enumeration level
+           carp('$u specified for top-level enumeration')
+             unless defined($last_enum);
+           $self->{ENUMS}->{$last_enum}->{COUNT} = $val;
+       } elsif ($key eq 'v') {
+           carp '$v specified for top-level enumeration'
+             unless defined($last_enum);
+           $self->{ENUMS}->{$last_enum}->{RESTART} = ($val eq 'r');
+       } elsif ($key =~ /[npw-z]/) {
+           # Publication Pattern ('o' == type of unit, 'q'..'t' undefined)
+           $self->{PATTERN}->{$key} = $val;
+       } elsif ($key eq 'o') {
+           # Type of unit
+           $self->{UNIT} = $val;
+       } elsif ($key eq 't') {
+           $self->{COPY} = $val;
+       } else {
+           carp "Unknown caption subfield '$key'";
+       }
+    }
+
+    bless ($self, $class);
+    return $self;
+}
+
+sub caption {
+    my $self = shift;
+    my $key;
+
+    if (@_) {
+       $key = shift;
+       return $self->{ENUMS}->{$key}->{CAPTION};
+    } else {
+       return $self->{CAPTION};
+    }
+}
+
+1;
diff --git a/Open-ILS/src/perlmods/OpenILS/Utils/MFHD/Holding.pm b/Open-ILS/src/perlmods/OpenILS/Utils/MFHD/Holding.pm
new file mode 100755 (executable)
index 0000000..0ce0fe0
--- /dev/null
@@ -0,0 +1,90 @@
+package MFHD::Holding;
+use strict;
+use integer;
+use Carp;
+
+use MARC::Record;
+
+sub new {
+    my $proto = shift;
+    my $class = ref($proto) || $proto;
+    my $seqno = shift;
+    my $holding = shift;
+    my $caption = shift;
+    my $self = {};
+    my $last_enum = undef;
+
+    $self->{SEQNO} = $seqno;
+    $self->{HOLDING} = $holding;
+    $self->{CAPTION} = $caption;
+    $self->{ENUMS} = {};
+    $self->{CHRON} = {};
+    $self->{DESCR} = {};
+    $self->{COPY} = undef;
+    $self->{BREAK} = undef;
+    $self->{NOTES} = {};
+    $self->{COPYRIGHT} = [];
+
+    foreach my $subfield ($holding->subfields) {
+       my ($key, $val) = @$subfield;
+       if ($key =~ /[a-h]/) {
+           # Enumeration details of holdings
+           $self->{ENUMS}->{$key} = {HOLDINGS => $val,
+                                    UNIT     => undef,};
+           $last_enum = $key;
+       } elsif ($key =~ /[i-m]/) {
+           $self->{CHRON}->{$key} = $val;
+           if (!exists $caption->{CHRONS}->{$key}) {
+               carp "Holding specified enumeration level '$key' not included in caption $caption->{LINK}";
+           }
+       } elsif ($key eq 'o') {
+           carp '$o specified prior to first enumeration'
+             unless defined($last_enum);
+           $self->{ENUMS}->{$last_enum}->{UNIT} = $val;
+           $last_enum = undef;
+       } elsif ($key =~ /[npq]/) {
+           $self->{DESCR}->{$key} = $val;
+       } elsif ($key eq 's') {
+           push @{$self->{COPYRIGHT}}, $val;
+       } elsif ($key eq 't') {
+           $self->{COPY} = $val;
+       } elsif ($key eq 'w') {
+           carp "Unrecognized break indicator '$val'"
+             unless $val =~ /^[gn]$/;
+           $self->{BREAK} = $val;
+       }
+    }
+
+    bless ($self, $class);
+    return $self;
+}
+
+sub format {
+    my $self = shift;
+    my $caption = $self->{CAPTION};
+    my $str = "";
+
+    foreach my $key ('a'..'f') {
+       last if !exists $caption->{ENUMS}->{$key};
+#      printf("fmt %s: '%s'\n", $key, $caption->caption($key));
+
+       $str .= ($key eq 'a' ? "" : ':') . $caption->caption($key) . $self->{ENUMS}->{$key}->{HOLDINGS};
+    }
+
+    if (exists $caption->{ENUMS}->{'g'}) {
+       # There's at least one level of alternative enumeration
+       $str .= ' ';
+       foreach my $key ('g', 'h') {
+           $str .= ($key eq 'g' ? '' : ':') . $caption->enum($key) . $self->{ENUMS}->{$key}->{HOLDINGS};
+       }
+    }
+
+    return $str;
+}
+
+sub next {
+    my $self = shift;
+    my $caption = $self->{CAPTION};
+}
+
+1;
diff --git a/Open-ILS/src/perlmods/OpenILS/Utils/MFHDParser.pm b/Open-ILS/src/perlmods/OpenILS/Utils/MFHDParser.pm
new file mode 100644 (file)
index 0000000..5995de9
--- /dev/null
@@ -0,0 +1,168 @@
+#!/usr/bin/perl -w
+use strict;
+use Date::Manip;
+
+# Parse MFHD patterns for http://www.loc.gov/marc/holdings/hd853855.html
+
+# Primary goal:
+# Expected input: a chunk of MFHD, a start date, and # of issuances to project
+# Expected output: a set of issuances projected forward from the start date,
+#   with issue/volume/dates/captions conforming to what the MFHD actually says
+
+# The thought had been to use Date::Manip to generate the actual dates for
+# each issuance, like:
+#
+#   # To find the 2nd Tuesday of every month
+#   @date = ParseRecur("0:1*2:2:0:0:0",$base,$start,$stop);
+
+# Secondary goal: generate automatic summary holdings
+#   (a la http://www.loc.gov/marc/holdings/hd863865.html)
+
+# Compressability comes from first indicator
+sub parse_compressability {
+       my $c = shift || return undef;
+
+       my %compressability = (
+               '0' => 'Cannot compress or expand',
+               '1' => 'Can compress but cannot expand',
+               '2' => 'Can compress or expand',
+               '3' => 'Unknown',
+               '#' => 'Undefined'
+       );
+
+       if (exists $compressability{$c}) {
+               return $compressability{$c};
+       }
+       # 'Unknown compressability indicator - expected one of (0,1,2,3,#)';
+       return undef;
+}
+
+# Caption evaluation comes from second indicator
+sub caption_evaluation {
+       my $ce = shift || return undef;
+
+       my %caption_evaluation = (
+               '0' => 'Captions verified; all levels present',
+               '1' => 'Captions verified; all levels may not be present',
+               '2' => 'Captions unverified; all levels present',
+               '3' => 'Captions unverified; all levels may not be present',
+               '#' => 'Undefined',
+       );
+
+       if (exists $caption_evaluation{$ce}) {
+               return $caption_evaluation{$ce};
+       }
+       # 'Unknown caption evaluation indicator - expected one of (0,1,2,3,#)';
+       return undef;
+}
+
+# Start with frequency ($w)
+# then overlay number of pieces of issuance ($p)
+# then regularity pattern ($y)
+my %frequency = (
+       'a' => 'annual',
+       'b' => 'bimonthly',
+       'c' => 'semiweekly',
+       'd' => 'daily',
+       'e' => 'biweekly',
+       'f' => 'semiannual',
+       'g' => 'biennial',
+       'h' => 'triennial',
+       'i' => 'three times a week',
+       'j' => 'three times a month',
+       'k' => 'continuously updated',
+       'm' => 'monthly',
+       'q' => 'quarterly',
+       's' => 'semimonthly',
+       't' => 'three times a year',
+       'w' => 'weekly',
+       'x' => 'completely irregular',
+);
+
+sub parse_frequency {
+       my $freq = shift || return undef;
+
+       if ($freq =~ m/^\d+$/) {
+               return "$freq times a year";
+       } elsif (exists $frequency{$freq}) {
+               return $frequency{$freq};
+       }
+       # unrecognized frequency specification
+       return undef;
+}
+
+# $x - Point at which the highest level increments or changes
+# Interpretation of two-digit numbers in the 01-12 range depends on the publishing frequency
+# More than one change can be passed in the subfield and are delimited by commas
+sub chronology_change {
+       my $chronology_change = shift || return undef;
+       my @c_changes = split /,/, $chronology_change;
+       foreach my $change (@c_changes) {
+               if ($change == 21) {
+                       
+               }
+       }
+       return undef;
+}
+
+# Publication code : first character in regularity pattern ($y)
+sub parse_publication_code {
+       my $c = shift || return undef;
+
+       my %publication_code = (
+               'c' => 'combined',
+               'o' => 'omitted',
+               'p' => 'published',
+               '#' => 'undefined',
+       );
+
+       if (exists $publication_code{$c}) {
+               return $publication_code{$c};
+       }
+       return undef;
+}
+
+# Chronology code : part of regularity pattern ($y)
+sub parse_chronology_code {
+       my $c = shift || return undef;
+
+       my %chronology_code = (
+               'd' => 'Day',
+               'm' => 'Month',
+               's' => 'Season',
+               'w' => 'Week',
+               'y' => 'Year',
+               'e' => 'Enumeration',
+       );
+
+       if (exists $chronology_code{$c}) {
+               return $chronology_code{$c};
+       }
+       return undef;
+}
+
+sub parse_regularity_pattern {
+       my $pattern = shift;
+       my ($pc, $cc, $cd) = $pattern =~ m{^(\w)(\w)(.+)$};
+       
+       my $pub_code = parse_publication_code($pc);
+       my $chron_code = parse_chronology_code($cc);
+       my $chron_def = parse_chronology_definition($cd);
+       
+       return ($pub_code, $chron_code, $chron_def);
+}
+
+sub parse_chronology_definition {
+       my $chron_def = shift || return undef;
+       # Well, this is where it starts to get hard, doesn't it?
+       return $chron_def;
+}
+
+print parse_regularity_pattern("cddd");
+print "\n";
+print parse_regularity_pattern("38dd");
+print "\n";
+
+1;
+
+# :vim:noet:ts=4:sw=4:
diff --git a/Open-ILS/src/perlmods/OpenILS/WWW/EGWeb.pm b/Open-ILS/src/perlmods/OpenILS/WWW/EGWeb.pm
new file mode 100644 (file)
index 0000000..14146b9
--- /dev/null
@@ -0,0 +1,150 @@
+package OpenILS::WWW::EGWeb;
+use strict; use warnings;
+use Template;
+use XML::Simple;
+use File::stat;
+use Apache2::Const -compile => qw(OK DECLINED HTTP_INTERNAL_SERVER_ERROR);
+use Apache2::Log;
+
+use constant OILS_HTTP_COOKIE_SKIN => 'oils:skin';
+use constant OILS_HTTP_COOKIE_THEME => 'oils:theme';
+use constant OILS_HTTP_COOKIE_LOCALE => 'oils:locale';
+
+my $web_config;
+my $web_config_file;
+my $web_config_edit_time;
+
+sub import {
+    my $self = shift;
+    $web_config_file = shift;
+    unless(-r $web_config_file) {
+        warn "Invalid web config $web_config_file";
+        return;
+    }
+    check_web_config();
+}
+
+
+sub handler {
+    my $r = shift;
+    check_web_config($r); # option to disable this
+    my $ctx = load_context($r);
+    my $base = $ctx->{base_uri};
+    my($template, $page_args) = find_template($r, $base);
+    return Apache2::Const::DECLINED unless $template;
+
+    $template = $ctx->{skin} . "/$template";
+    $ctx->{page_args} = $page_args;
+    $r->content_type('text/html; encoding=utf8');
+
+    my $tt = Template->new({
+        OUTPUT => $r,
+        INCLUDE_PATH => $ctx->{template_paths},
+    });
+
+    unless($tt->process($template, {ctx => $ctx})) {
+        $r->log->warn('Template error: ' . $tt->error);
+        return Apache2::Const::HTTP_INTERNAL_SERVER_ERROR;
+    }
+
+    return Apache2::Const::OK;
+}
+
+sub load_context {
+    my $r = shift;
+    my $cgi = CGI->new;
+    my $ctx = $web_config->{ctx};
+    $ctx->{skin} = $cgi->cookie(OILS_HTTP_COOKIE_SKIN) || 'default';
+    $ctx->{theme} = $cgi->cookie(OILS_HTTP_COOKIE_THEME) || 'default';
+    $ctx->{locale} = 
+        $r->headers_in->get('Accept-Language') || # this will need some trimming
+        $cgi->cookie(OILS_HTTP_COOKIE_LOCALE) || 'en-US';
+    $r->log->debug('skin = ' . $ctx->{skin} . ' : theme = ' . 
+        $ctx->{theme} . ' : locale = ' . $ctx->{locale});
+    return $ctx;
+}
+
+# Given a URI, finds the configured template and any extra page 
+# arguments (trailing path info).  Any extra data is returned
+# as page arguments, in the form of an array, one item per 
+# /-separated URI component
+sub find_template {
+    my $r = shift;
+    my $base = shift;
+    my $path = $r->uri;
+    $path =~ s/$base//og;
+    my @parts = split('/', $path);
+    my $template = '';
+    my $page_args = [];
+    my $handler = $web_config->{handlers};
+    while(@parts) {
+        my $part = shift @parts;
+        next unless $part;
+        my $t = $handler->{$part};
+        if(ref $t) {
+            $handler = $t;
+        } else {
+            $template = $t;
+            $page_args = [@parts];
+            last;
+        }
+    }
+
+    unless($template) {
+        $r->log->warn("No template configured for path $path");
+        return ();
+    }
+
+    $r->log->debug("template = $template : page args = @$page_args");
+    return ($template, $page_args);
+}
+
+# if the web configuration file has never been loaded or has
+# changed since the last load, reload it
+sub check_web_config {
+    my $r = shift;
+    my $epoch = stat($web_config_file)->mtime;
+    unless($web_config_edit_time and $web_config_edit_time == $epoch) {
+        $r->log->debug("Reloading web config after edit...") if $r;
+        $web_config_edit_time = $epoch;
+        $web_config = parse_config($web_config_file);
+    }
+}
+
+sub parse_config {
+    my $cfg_file = shift;
+    my $data = XML::Simple->new->XMLin($cfg_file);
+    my $ctx = {};
+    my $handlers = {};
+
+    $ctx->{media_prefix} = (ref $data->{media_prefix}) ? '' : $data->{media_prefix};
+    $ctx->{base_uri} = (ref $data->{base_uri}) ? '' : $data->{base_uri};
+    $ctx->{template_paths} = [];
+
+    my $tpaths = $data->{template_paths}->{path};
+    $tpaths = [$tpaths] unless ref $tpaths;
+    push(@{$ctx->{template_paths}}, $_) for @$tpaths;
+
+    for my $handler (@{$data->{handlers}->{handler}}) {
+        my @parts = split('/', $handler->{path});
+        my $h = $handlers;
+        my $pcount = scalar(@parts);
+        for(my $i = 0; $i < $pcount; $i++) {
+            my $p = $parts[$i];
+            unless(defined $h->{$p}) {
+                if($i == $pcount - 1) {
+                    $h->{$p} = $handler->{template};
+                    last;
+                } else {
+                    $h->{$p} = {};
+                }
+            }
+            $h = $h->{$p};
+        }
+    }
+
+    return {ctx => $ctx, handlers => $handlers};
+}
+
+
+1;
diff --git a/Open-ILS/src/sql/Pg/200.schema.acq.sql b/Open-ILS/src/sql/Pg/200.schema.acq.sql
new file mode 100644 (file)
index 0000000..5926f1e
--- /dev/null
@@ -0,0 +1,440 @@
+DROP SCHEMA acq CASCADE;
+
+BEGIN;
+
+CREATE SCHEMA acq;
+
+
+-- Tables
+
+
+CREATE TABLE acq.currency_type (
+       code    TEXT PRIMARY KEY,
+       label   TEXT
+);
+
+-- Use the ISO 4217 abbreviations for currency codes
+INSERT INTO acq.currency_type (code, label) VALUES ('USD','US Dollars');
+INSERT INTO acq.currency_type (code, label) VALUES ('CAN','Canadian Dollars');
+INSERT INTO acq.currency_type (code, label) VALUES ('EUR','Euros');
+
+CREATE TABLE acq.exchange_rate (
+    id              SERIAL  PRIMARY KEY,
+    from_currency   TEXT    NOT NULL REFERENCES acq.currency_type (code),
+    to_currency     TEXT    NOT NULL REFERENCES acq.currency_type (code),
+    ratio           NUMERIC NOT NULL,
+    CONSTRAINT exchange_rate_from_to_once UNIQUE (from_currency,to_currency)
+);
+
+INSERT INTO acq.exchange_rate (from_currency,to_currency,ratio) VALUES ('USD','CAN',1.2);
+INSERT INTO acq.exchange_rate (from_currency,to_currency,ratio) VALUES ('USD','EUR',0.5);
+
+CREATE TABLE acq.provider (
+       id              SERIAL  PRIMARY KEY,
+       name            TEXT    NOT NULL,
+       owner           INT     NOT NULL REFERENCES actor.org_unit (id),
+       currency_type   TEXT    NOT NULL REFERENCES acq.currency_type (code),
+       code            TEXT    UNIQUE,
+       CONSTRAINT provider_name_once_per_owner UNIQUE (name,owner)
+);
+
+CREATE TABLE acq.funding_source (
+       id              SERIAL  PRIMARY KEY,
+       name            TEXT    NOT NULL,
+       owner           INT     NOT NULL REFERENCES actor.org_unit (id),
+       currency_type   TEXT    NOT NULL REFERENCES acq.currency_type (code),
+       code            TEXT    UNIQUE,
+       CONSTRAINT funding_source_name_once_per_owner UNIQUE (name,owner)
+);
+
+CREATE TABLE acq.funding_source_credit (
+       id      SERIAL  PRIMARY KEY,
+       funding_source    INT     NOT NULL REFERENCES acq.funding_source (id),
+       amount  NUMERIC NOT NULL,
+       note    TEXT
+);
+
+CREATE TABLE acq.fund (
+    id              SERIAL  PRIMARY KEY,
+    org             INT     NOT NULL REFERENCES actor.org_unit (id) ON UPDATE CASCADE ON DELETE CASCADE,
+    name            TEXT    NOT NULL,
+    year            INT     NOT NULL DEFAULT EXTRACT( YEAR FROM NOW() ),
+    currency_type   TEXT    NOT NULL REFERENCES acq.currency_type (code),
+    code            TEXT    UNIQUE,
+    CONSTRAINT name_once_per_org_year UNIQUE (org,name,year)
+);
+
+CREATE TABLE acq.fund_debit (
+       id                      SERIAL  PRIMARY KEY,
+       fund                    INT     NOT NULL REFERENCES acq.fund (id),
+       origin_amount           NUMERIC NOT NULL,  -- pre-exchange-rate amount
+       origin_currency_type    TEXT    NOT NULL REFERENCES acq.currency_type (code),
+       amount                  NUMERIC NOT NULL,
+       encumbrance             BOOL    NOT NULL DEFAULT TRUE,
+       debit_type              TEXT    NOT NULL,
+       xfer_destination        INT     REFERENCES acq.fund (id)
+);
+
+CREATE TABLE acq.fund_allocation (
+    id          SERIAL  PRIMARY KEY,
+    funding_source        INT     NOT NULL REFERENCES acq.funding_source (id) ON UPDATE CASCADE ON DELETE CASCADE,
+    fund        INT     NOT NULL REFERENCES acq.fund (id) ON UPDATE CASCADE ON DELETE CASCADE,
+    amount      NUMERIC,
+    percent     NUMERIC CHECK (percent IS NULL OR percent BETWEEN 0.0 AND 100.0),
+    allocator   INT NOT NULL REFERENCES actor.usr (id),
+    note        TEXT,
+    CONSTRAINT allocation_amount_or_percent CHECK ((percent IS NULL AND amount IS NOT NULL) OR (percent IS NOT NULL AND amount IS NULL))
+);
+
+
+CREATE TABLE acq.picklist (
+       id              SERIAL                          PRIMARY KEY,
+       owner           INT                             NOT NULL REFERENCES actor.usr (id),
+       org_unit        INT                             NOT NULL REFERENCES actor.org_unit (id),
+       name            TEXT                            NOT NULL,
+       create_time     TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT NOW(),
+       edit_time       TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT NOW(),
+       CONSTRAINT name_once_per_owner UNIQUE (name,owner)
+);
+
+CREATE TABLE acq.purchase_order (
+       id              SERIAL                          PRIMARY KEY,
+       owner           INT                             NOT NULL REFERENCES actor.usr (id),
+       ordering_agency         INT                             NOT NULL REFERENCES actor.org_unit (id),
+       create_time     TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT NOW(),
+       edit_time       TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT NOW(),
+       provider        INT                             NOT NULL REFERENCES acq.provider (id),
+       state           TEXT                            NOT NULL DEFAULT 'new'
+);
+CREATE INDEX po_owner_idx ON acq.purchase_order (owner);
+CREATE INDEX po_provider_idx ON acq.purchase_order (provider);
+CREATE INDEX po_state_idx ON acq.purchase_order (state);
+
+CREATE TABLE acq.po_note (
+       id              SERIAL                          PRIMARY KEY,
+       purchase_order  INT                             NOT NULL REFERENCES acq.purchase_order (id),
+       creator         INT                             NOT NULL REFERENCES actor.usr (id),
+       editor          INT                             NOT NULL REFERENCES actor.usr (id),
+       create_time     TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT NOW(),
+       edit_time       TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT NOW(),
+       value           TEXT                            NOT NULL
+);
+CREATE INDEX po_note_po_idx ON acq.po_note (purchase_order);
+
+CREATE TABLE acq.lineitem (
+       id                  BIGSERIAL                   PRIMARY KEY,
+       selector            INT                         NOT NULL REFERENCES actor.org_unit (id),
+       provider            INT                         REFERENCES acq.provider (id),
+       purchase_order      INT                         REFERENCES acq.purchase_order (id),
+       picklist            INT                         REFERENCES acq.picklist (id),
+       expected_recv_time  TIMESTAMP WITH TIME ZONE,
+       create_time         TIMESTAMP WITH TIME ZONE    NOT NULL DEFAULT NOW(),
+       edit_time           TIMESTAMP WITH TIME ZONE    NOT NULL DEFAULT NOW(),
+       marc                TEXT                        NOT NULL,
+       eg_bib_id           INT                         REFERENCES biblio.record_entry (id),
+       source_label        TEXT,
+       item_count          INT                         NOT NULL DEFAULT 0,
+       state               TEXT                        NOT NULL DEFAULT 'new',
+    CONSTRAINT picklist_or_po CHECK (picklist IS NOT NULL OR purchase_order IS NOT NULL)
+);
+CREATE INDEX li_po_idx ON acq.lineitem (purchase_order);
+CREATE INDEX li_pl_idx ON acq.lineitem (picklist);
+
+CREATE TABLE acq.lineitem_note (
+       id              SERIAL                          PRIMARY KEY,
+       lineitem        INT                             NOT NULL REFERENCES acq.lineitem (id),
+       creator         INT                             NOT NULL REFERENCES actor.usr (id),
+       editor          INT                             NOT NULL REFERENCES actor.usr (id),
+       create_time     TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT NOW(),
+       edit_time       TIMESTAMP WITH TIME ZONE        NOT NULL DEFAULT NOW(),
+       value           TEXT                            NOT NULL
+);
+CREATE INDEX li_note_li_idx ON acq.lineitem_note (lineitem);
+
+CREATE TABLE acq.lineitem_detail (
+       id              BIGSERIAL                       PRIMARY KEY,
+       lineitem        INT                             NOT NULL REFERENCES acq.lineitem (id),
+       fund            INT                             REFERENCES acq.fund (id),
+       fund_debit      INT                             REFERENCES acq.fund_debit (id),
+       eg_copy_id      BIGINT                  REFERENCES asset.copy (id) ON DELETE SET NULL,
+       barcode         TEXT,
+       cn_label        TEXT,
+    owning_lib  INT             REFERENCES actor.org_unit (id) ON DELETE SET NULL,
+    location    INT             REFERENCES asset.copy_location (id) ON DELETE SET NULL,
+       recv_time       TIMESTAMP WITH TIME ZONE
+);
+
+CREATE INDEX li_detail_li_idx ON acq.lineitem_detail (lineitem);
+
+CREATE TABLE acq.lineitem_attr_definition (
+       id              BIGSERIAL       PRIMARY KEY,
+       code            TEXT            NOT NULL,
+       description     TEXT            NOT NULL,
+       remove          TEXT            NOT NULL DEFAULT '',
+       ident           BOOL            NOT NULL DEFAULT FALSE
+);
+
+CREATE TABLE acq.lineitem_marc_attr_definition (
+       id              BIGINT  PRIMARY KEY DEFAULT NEXTVAL('acq.lineitem_attr_definition_id_seq'),
+       xpath           TEXT            NOT NULL
+) INHERITS (acq.lineitem_attr_definition);
+
+CREATE TABLE acq.lineitem_provider_attr_definition (
+       id              BIGINT  PRIMARY KEY DEFAULT NEXTVAL('acq.lineitem_attr_definition_id_seq'),
+       xpath           TEXT            NOT NULL,
+       provider        INT     NOT NULL REFERENCES acq.provider (id)
+) INHERITS (acq.lineitem_attr_definition);
+
+CREATE TABLE acq.lineitem_generated_attr_definition (
+       id              BIGINT  PRIMARY KEY DEFAULT NEXTVAL('acq.lineitem_attr_definition_id_seq'),
+       xpath           TEXT            NOT NULL
+) INHERITS (acq.lineitem_attr_definition);
+
+CREATE TABLE acq.lineitem_usr_attr_definition (
+       id              BIGINT  PRIMARY KEY DEFAULT NEXTVAL('acq.lineitem_attr_definition_id_seq'),
+       usr             INT     NOT NULL REFERENCES actor.usr (id)
+) INHERITS (acq.lineitem_attr_definition);
+
+CREATE TABLE acq.lineitem_local_attr_definition (
+       id              BIGINT  PRIMARY KEY DEFAULT NEXTVAL('acq.lineitem_attr_definition_id_seq')
+) INHERITS (acq.lineitem_attr_definition);
+
+CREATE TABLE acq.lineitem_attr (
+       id              BIGSERIAL       PRIMARY KEY,
+       definition      BIGINT          NOT NULL,
+       lineitem        BIGINT          NOT NULL REFERENCES acq.lineitem (id),
+       attr_type       TEXT            NOT NULL,
+       attr_name       TEXT            NOT NULL,
+       attr_value      TEXT            NOT NULL
+);
+
+CREATE INDEX li_attr_li_idx ON acq.lineitem_attr (lineitem);
+CREATE INDEX li_attr_value_idx ON acq.lineitem_attr (attr_value);
+CREATE INDEX li_attr_definition_idx ON acq.lineitem_attr (definition);
+
+
+-- Seed data
+
+
+INSERT INTO acq.lineitem_marc_attr_definition ( code, description, xpath ) VALUES ('title','Title of work','//*[@tag="245"]/*[contains("abcmnopr",@code)]');
+INSERT INTO acq.lineitem_marc_attr_definition ( code, description, xpath ) VALUES ('author','Author of work','//*[@tag="100" or @tag="110" or @tag="113"]/*[contains("ad",@code)]');
+INSERT INTO acq.lineitem_marc_attr_definition ( code, description, xpath ) VALUES ('language','Lanuage of work','//*[@tag="240"]/*[@code="l"][1]');
+INSERT INTO acq.lineitem_marc_attr_definition ( code, description, xpath ) VALUES ('pagination','Pagination','//*[@tag="300"]/*[@code="a"][1]');
+INSERT INTO acq.lineitem_marc_attr_definition ( code, description, xpath, remove ) VALUES ('isbn','ISBN','//*[@tag="020"]/*[@code="a"]', $r$(?:-|\s.+$)$r$);
+INSERT INTO acq.lineitem_marc_attr_definition ( code, description, xpath, remove ) VALUES ('issn','ISSN','//*[@tag="022"]/*[@code="a"]', $r$(?:-|\s.+$)$r$);
+INSERT INTO acq.lineitem_marc_attr_definition ( code, description, xpath ) VALUES ('price','Price','//*[@tag="020" or @tag="022"]/*[@code="c"][1]');
+INSERT INTO acq.lineitem_marc_attr_definition ( code, description, xpath ) VALUES ('identifier','Identifier','//*[@tag="001"]');
+INSERT INTO acq.lineitem_marc_attr_definition ( code, description, xpath ) VALUES ('publisher','Publisher','//*[@tag="260"]/*[@code="b"][1]');
+INSERT INTO acq.lineitem_marc_attr_definition ( code, description, xpath ) VALUES ('pubdate','Publication Date','//*[@tag="260"]/*[@code="c"][1]');
+INSERT INTO acq.lineitem_marc_attr_definition ( code, description, xpath ) VALUES ('edition','Edition','//*[@tag="250"]/*[@code="a"][1]');
+
+
+-- Functions
+
+
+CREATE OR REPLACE FUNCTION public.extract_acq_marc_field ( BIGINT, TEXT, TEXT) RETURNS TEXT AS $$
+       SELECT public.extract_marc_field('acq.lineitem', $1, $2, $3);
+$$ LANGUAGE SQL;
+
+/*
+CREATE OR REPLACE FUNCTION public.extract_bib_marc_field ( BIGINT, TEXT ) RETURNS TEXT AS $$
+       SELECT public.extract_marc_field('biblio.record_entry', $1, $2);
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE FUNCTION public.extract_authority_marc_field ( BIGINT, TEXT ) RETURNS TEXT AS $$
+       SELECT public.extract_marc_field('authority.record_entry', $1, $2);
+$$ LANGUAGE SQL;
+*/
+-- For example:
+-- INSERT INTO acq.lineitem_provider_attr_definition ( provider, code, description, xpath ) VALUES (1,'price','Price','//*[@tag="020" or @tag="022"]/*[@code="a"][1]');
+
+/*
+Suggested vendor fields:
+       vendor_price
+       vendor_currency
+       vendor_avail
+       vendor_po
+       vendor_identifier
+*/
+
+CREATE OR REPLACE FUNCTION public.ingest_acq_marc ( ) RETURNS TRIGGER AS $$
+DECLARE
+       value           TEXT;
+       atype           TEXT;
+       prov            INT;
+       adef            RECORD;
+       xpath_string    TEXT;
+BEGIN
+       FOR adef IN SELECT *,tableoid FROM acq.lineitem_attr_definition LOOP
+
+               SELECT relname::TEXT INTO atype FROM pg_class WHERE oid = adef.tableoid;
+
+               IF (atype NOT IN ('lineitem_usr_attr_definition','lineitem_local_attr_definition')) THEN
+                       IF (atype = 'lineitem_provider_attr_definition') THEN
+                               SELECT provider INTO prov FROM acq.lineitem_provider_attr_definition WHERE id = adef.id;
+                               CONTINUE WHEN NEW.provider IS NULL OR prov <> NEW.provider;
+                       END IF;
+                       
+                       IF (atype = 'lineitem_provider_attr_definition') THEN
+                               SELECT xpath INTO xpath_string FROM acq.lineitem_provider_attr_definition WHERE id = adef.id;
+                       ELSIF (atype = 'lineitem_marc_attr_definition') THEN
+                               SELECT xpath INTO xpath_string FROM acq.lineitem_marc_attr_definition WHERE id = adef.id;
+                       ELSIF (atype = 'lineitem_generated_attr_definition') THEN
+                               SELECT xpath INTO xpath_string FROM acq.lineitem_generated_attr_definition WHERE id = adef.id;
+                       END IF;
+
+                       SELECT extract_acq_marc_field(id, xpath_string, adef.remove) INTO value FROM acq.lineitem WHERE id = NEW.id;
+
+                       IF (value IS NOT NULL AND value <> '') THEN
+                               INSERT INTO acq.lineitem_attr (lineitem, definition, attr_type, attr_name, attr_value)
+                                       VALUES (NEW.id, adef.id, atype, adef.code, value);
+                       END IF;
+
+               END IF;
+
+       END LOOP;
+
+       RETURN NULL;
+END;
+$$ LANGUAGE PLPGSQL;
+
+CREATE OR REPLACE FUNCTION public.cleanup_acq_marc ( ) RETURNS TRIGGER AS $$
+BEGIN
+       IF TG_OP = 'UPDATE' THEN
+               DELETE FROM acq.lineitem_attr
+                       WHERE lineitem = OLD.id AND attr_type IN ('lineitem_provider_attr_definition', 'lineitem_marc_attr_definition','lineitem_generated_attr_definition');
+               RETURN NEW;
+       ELSE
+               DELETE FROM acq.lineitem_attr WHERE lineitem = OLD.id;
+               RETURN OLD;
+       END IF;
+END;
+$$ LANGUAGE PLPGSQL;
+
+CREATE TRIGGER cleanup_lineitem_trigger
+       BEFORE UPDATE OR DELETE ON acq.lineitem
+       FOR EACH ROW EXECUTE PROCEDURE public.cleanup_acq_marc();
+
+CREATE TRIGGER ingest_lineitem_trigger
+       AFTER INSERT OR UPDATE ON acq.lineitem
+       FOR EACH ROW EXECUTE PROCEDURE public.ingest_acq_marc();
+
+CREATE OR REPLACE FUNCTION acq.exchange_ratio ( from_ex TEXT, to_ex TEXT ) RETURNS NUMERIC AS $$
+DECLARE
+    rat NUMERIC;
+BEGIN
+    IF from_ex = to_ex THEN
+        RETURN 1.0;
+    END IF;
+
+    SELECT ratio INTO rat FROM acq.exchange_rate WHERE from_currency = from_ex AND to_currency = to_ex;
+
+    IF FOUND THEN
+        RETURN rat;
+    ELSE
+        SELECT ratio INTO rat FROM acq.exchange_rate WHERE from_currency = to_ex AND to_currency = from_ex;
+        IF FOUND THEN
+            RETURN 1.0/rat;
+        END IF;
+    END IF;
+
+    RETURN NULL;
+
+END;
+$$ LANGUAGE PLPGSQL;
+
+CREATE OR REPLACE FUNCTION acq.exchange_ratio ( TEXT, TEXT, NUMERIC ) RETURNS NUMERIC AS $$
+    SELECT $3 * acq.exchange_ratio($1, $2);
+$$ LANGUAGE SQL;
+
+CREATE OR REPLACE VIEW acq.funding_source_credit_total AS
+    SELECT  funding_source,
+            SUM(amount) AS amount
+      FROM  acq.funding_source_credit
+      GROUP BY 1;
+
+CREATE OR REPLACE VIEW acq.funding_source_allocation_total AS
+    SELECT  funding_source,
+            SUM(amount)::NUMERIC(100,2) AS amount
+      FROM (
+            SELECT  funding_source,
+                    SUM(a.amount)::NUMERIC(100,2) AS amount
+              FROM  acq.fund_allocation a
+              WHERE a.percent IS NULL
+              GROUP BY 1
+                            UNION ALL
+            SELECT  funding_source,
+                    SUM( (SELECT SUM(amount) FROM acq.funding_source_credit c WHERE c.funding_source = a.funding_source) * (a.percent/100.0) )::NUMERIC(100,2) AS amount
+              FROM  acq.fund_allocation a
+              WHERE a.amount IS NULL
+              GROUP BY 1
+        ) x
+      GROUP BY 1;
+
+CREATE OR REPLACE VIEW acq.funding_source_balance AS
+    SELECT  COALESCE(c.funding_source, a.funding_source) AS funding_source,
+            SUM(COALESCE(c.amount,0.0) - COALESCE(a.amount,0.0))::NUMERIC(100,2) AS amount
+      FROM  acq.funding_source_credit_total c
+            FULL JOIN acq.funding_source_allocation_total a USING (funding_source)
+      GROUP BY 1;
+
+CREATE OR REPLACE VIEW acq.fund_allocation_total AS
+    SELECT  fund,
+            SUM(amount)::NUMERIC(100,2) AS amount
+      FROM (
+            SELECT  fund,
+                    SUM(a.amount * acq.exchange_ratio(s.currency_type, f.currency_type))::NUMERIC(100,2) AS amount
+              FROM  acq.fund_allocation a
+                    JOIN acq.fund f ON (a.fund = f.id)
+                    JOIN acq.funding_source s ON (a.funding_source = s.id)
+              WHERE a.percent IS NULL
+              GROUP BY 1
+                            UNION ALL
+            SELECT  fund,
+                    SUM( (SELECT SUM(amount) FROM acq.funding_source_credit c WHERE c.funding_source = a.funding_source) * acq.exchange_ratio(s.currency_type, f.currency_type) * (a.percent/100.0) )::NUMERIC(100,2) AS amount
+              FROM  acq.fund_allocation a
+                    JOIN acq.fund f ON (a.fund = f.id)
+                    JOIN acq.funding_source s ON (a.funding_source = s.id)
+              WHERE a.amount IS NULL
+              GROUP BY 1
+        ) x
+      GROUP BY 1;
+
+CREATE OR REPLACE VIEW acq.fund_debit_total AS
+    SELECT  id AS fund,
+            encumbrance,
+            SUM(amount) AS amount
+      FROM  acq.fund_debit 
+      GROUP BY 1,2;
+
+CREATE OR REPLACE VIEW acq.fund_encumbrance_total AS
+    SELECT  fund,
+            SUM(amount) AS amount
+      FROM  acq.fund_debit_total
+      WHERE encumbrance IS TRUE
+      GROUP BY 1;
+
+CREATE OR REPLACE VIEW acq.fund_spent_total AS
+    SELECT  fund,
+            SUM(amount) AS amount
+      FROM  acq.fund_debit_total
+      WHERE encumbrance IS FALSE
+      GROUP BY 1;
+
+CREATE OR REPLACE VIEW acq.fund_combined_balance AS
+    SELECT  c.fund,
+            c.amount - COALESCE(d.amount,0.0) AS amount
+      FROM  acq.fund_allocation_total c
+            LEFT JOIN acq.fund_debit_total d USING (fund);
+
+CREATE OR REPLACE VIEW acq.fund_spent_balance AS
+    SELECT  c.fund,
+            c.amount - COALESCE(d.amount,0.0) AS amount
+      FROM  acq.fund_allocation_total c
+            LEFT JOIN acq.fund_spent_total d USING (fund);
+
+COMMIT;
+
+
+
+
diff --git a/Open-ILS/src/sql/Pg/210.schema.serials.sql b/Open-ILS/src/sql/Pg/210.schema.serials.sql
new file mode 100644 (file)
index 0000000..56a4391
--- /dev/null
@@ -0,0 +1,64 @@
+
+
+DROP SCHEMA serial CASCADE;
+
+CREATE TABLE asset.uri (
+       id      SERIAL  PRIMARY KEY,
+       href    TEXT    NOT NULL,
+       label   TEXT,
+       use     TEXT,
+       active  BOOL    NOT NULL DEFAULT TRUE
+);
+
+ALTER TABLE asset.call_number ADD COLUMN uri INT REFERENCES asset.uri (id);
+
+BEGIN;
+
+CREATE SCHEMA serial;
+
+CREATE TABLE serial.subscription (
+       id              SERIAL  PRIMARY KEY,
+       callnumber      BIGINT  REFERENCES asset.call_number (id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED,
+       uri             INT     REFERENCES asset.uri (id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED,
+       start_date      DATE    NOT NULL,
+       end_date        DATE    NOT NULL
+);
+
+CREATE TABLE serial.binding_unit (
+       id              SERIAL  PRIMARY KEY,
+       subscription    INT     NOT NULL REFERENCES serial.subscription (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
+       label           TEXT    NOT NULL,
+       CONSTRAINT bu_label_once_per_sub UNIQUE (subscription, label)
+);
+
+CREATE TABLE serial.issuance (
+       id              SERIAL  PRIMARY KEY,
+       subscription    INT     NOT NULL REFERENCES serial.subscription (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
+       target_copy     BIGINT  REFERENCES asset.copy (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
+       binding_unit    INT     REFERENCES serial.binding_unit (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
+       label           TEXT
+);
+
+CREATE TABLE serial.bib_summary (
+       id                      SERIAL  PRIMARY KEY,
+       call_number             INT     UNIQUE NOT NULL REFERENCES serial.subscription (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
+       generated_coverage      TEXT    NOT NULL,
+       textual_holdings        TEXT
+);
+
+CREATE TABLE serial.sup_summary (
+       id                      SERIAL  PRIMARY KEY,
+       call_number             INT     UNIQUE NOT NULL REFERENCES serial.subscription (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
+       generated_coverage      TEXT    NOT NULL,
+       textual_holdings        TEXT
+);
+
+CREATE TABLE serial.index_summary (
+       id                      SERIAL  PRIMARY KEY,
+       call_number             INT     UNIQUE NOT NULL REFERENCES serial.subscription (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
+       generated_coverage      TEXT    NOT NULL,
+       textual_holdings        TEXT
+);
+
+COMMIT;
+
index f1a1304..ec55e73 100644 (file)
@@ -1123,9 +1123,36 @@ INSERT INTO permission.perm_list VALUES
 INSERT INTO permission.perm_list VALUES 
     (151, 'DELETE_CONTAINER_ITEM', oils_i18n_gettext(151, 'Allow a user to delete an item out of another user''s container', 'ppl', 'description'));
 INSERT INTO permission.perm_list VALUES 
+    (153, 'CREATE_FUNDING_SOURCE', oils_i18n_gettext(153, 'Allow a user to create a new funding source', 'ppl', 'description')),
+    (154, 'DELETE_FUNDING_SOURCE', oils_i18n_gettext(154, 'Allow a user to delete a funding source', 'ppl', 'description')),
+    (155, 'VIEW_FUNDING_SOURCE', oils_i18n_gettext(155, 'Allow a user to view a funding source', 'ppl', 'description')),
+    (156, 'UPDATE_FUNDING_SOURCE', oils_i18n_gettext(156, 'Allow a user to update a funding source', 'ppl', 'description')),
+    (157, 'CREATE_FUND', oils_i18n_gettext(157, 'Allow a user to create a new fund', 'ppl', 'description')),
+    (158, 'DELETE_FUND', oils_i18n_gettext(158, 'Allow a user to delete a fund', 'ppl', 'description')),
+    (159, 'VIEW_FUND', oils_i18n_gettext(159, 'Allow a user to view a fund', 'ppl', 'description')),
+    (160, 'UPDATE_FUND', oils_i18n_gettext(160, 'Allow a user to update a fund', 'ppl', 'description')),
+    (161, 'CREATE_FUND_ALLOCATION', oils_i18n_gettext(161, 'Allow a user to create a new fund allocation', 'ppl', 'description')),
+    (162, 'DELETE_FUND_ALLOCATION', oils_i18n_gettext(162, 'Allow a user to delete a fund allocation', 'ppl', 'description')),
+    (163, 'VIEW_FUND_ALLOCATION', oils_i18n_gettext(163, 'Allow a user to view a fund allocation', 'ppl', 'description')),
+    (164, 'UPDATE_FUND_ALLOCATION', oils_i18n_gettext(164, 'Allow a user to update a fund allocation', 'ppl', 'description')),
+    (165, 'GENERAL_ACQ', oils_i18n_gettext(165, 'Lowest level permission required to access the ACQ interface', 'ppl', 'description')),
+    (166, 'CREATE_PROVIDER', oils_i18n_gettext(166, 'Allow a user to create a new provider', 'ppl', 'description')),
+    (167, 'DELETE_PROVIDER', oils_i18n_gettext(167, 'Allow a user to delate a provider', 'ppl', 'description')),
+    (168, 'VIEW_PROVIDER', oils_i18n_gettext(168, 'Allow a user to view a provider', 'ppl', 'description')),
+    (169, 'UPDATE_PROVIDER', oils_i18n_gettext(169, 'Allow a user to update a provider', 'ppl', 'description')),
+    (170, 'ADMIN_FUNDING_SOURCE', oils_i18n_gettext(170, 'Allow a user to create/view/update/delete a funding source', 'ppl', 'description')),
+    (171, 'ADMIN_FUND', oils_i18n_gettext(171, 'Allow a user to create/view/update/delete a fund', 'ppl', 'description')),
+    (172, 'MANAGE_FUNDING_SOURCE', oils_i18n_gettext(172, 'Allow a user to view/credit/debit a funding source', 'ppl', 'description')),
+    (173, 'MANAGE_FUND', oils_i18n_gettext(173, 'Allow a user to view/credit/debit a fund', 'ppl', 'description')),
+    (174, 'CREATE_PICKLIST', oils_i18n_gettext(174, 'Allows a user to create a picklist', 'ppl', 'description')),
+    (175, 'ADMIN_PROVIDER', oils_i18n_gettext(175, 'Allow a user to create/view/update/delete a provider', 'ppl', 'description')),
+    (176, 'MANAGE_PROVIDER', oils_i18n_gettext(176, 'Allow a user to view and purchase from a provider', 'ppl', 'description')),
+    (177, 'VIEW_PICKLIST', oils_i18n_gettext(177, 'Allow a user to view another users picklist', 'ppl', 'description')),
     (152, 'ASSIGN_WORK_ORG_UNIT', oils_i18n_gettext(152, 'Allow a staff member to define where another staff member has their permissions', 'ppl', 'description'));
 INSERT INTO permission.perm_list VALUES 
-    (153, 'DELETE_RECORD', oils_i18n_gettext(153, 'Allow a staff member to directly remove a bibliographic record', 'ppl', 'description'));
+    (178, 'DELETE_RECORD', oils_i18n_gettext(178, 'Allow a staff member to directly remove a bibliographic record', 'ppl', 'description'));
+INSERT INTO permission.perm_list VALUES 
+    (179, 'ADMIN_CURRENCY_TYPE', oils_i18n_gettext(179, 'Allow a user to create/view/update/delete a currency_type', 'ppl', 'description'));
 
 SELECT SETVAL('permission.perm_list_id_seq'::TEXT, (SELECT MAX(id) FROM permission.perm_list));
 
index 9ad12ee..816ab74 100755 (executable)
@@ -102,6 +102,9 @@ ordered_file_list="
   100.circ_matrix.sql
   110.hold_matrix.sql
 
+  200.schema.acq.sql
+  210.schema.serials.sql
+
   300.schema.staged_search.sql
   
   500.view.cross-schema.sql
diff --git a/Open-ILS/src/support-scripts/test-scripts/acq_fund.py b/Open-ILS/src/support-scripts/test-scripts/acq_fund.py
new file mode 100644 (file)
index 0000000..17f16b5
--- /dev/null
@@ -0,0 +1,41 @@
+#!/usr/bin/python
+import sys
+import oils.system, oils.utils.utils
+import osrf.net_obj, osrf.ses
+
+# ---------------------------------------------------------------
+# Usage: python acq_fund_source.py <user> <password> <workstation> 
+# ---------------------------------------------------------------
+
+oils.system.System.connect(config_file='/openils/conf/opensrf_core.xml', config_context='config.opensrf')
+auth_info = oils.utils.utils.login(sys.argv[1], sys.argv[2], 'staff', sys.argv[3])
+authtoken = auth_info['payload']['authtoken']
+
+ses = osrf.ses.ClientSession('open-ils.acq')
+ses.connect() # not required, but faster for batches of request
+
+# XXX This loop assumes the existence of orgs with IDs 1-6 and a USD currency
+ids = []
+for i in range(0,5):
+    fund_source = osrf.net_obj.NetworkObject.acqfs()
+    fund_source.name("test-fund_source-%d" % i)
+    fund_source.owner(i+1)
+    fund_source.currency_type('USD')
+    req = ses.request('open-ils.acq.funding_source.create', authtoken, fund_source)
+    id = req.recv().content()
+    print 'created fund_source ' + str(id)
+    ids.append(id)
+
+req = ses.request('open-ils.acq.funding_source.org.retrieve', authtoken, 1, {"children":1})
+resp = req.recv().content()
+for fund_source in resp:
+    print 'fetched fund_source ' + str(fund_source.name())
+
+for i in ids:
+    req = ses.request('open-ils.acq.funding_source.delete', authtoken, i)
+    print 'delete returned ' + str(req.recv().content())
+
+
+ses.disconnect() # only required if a connect() call was made
+
+
diff --git a/Open-ILS/web/css/skin/default.css b/Open-ILS/web/css/skin/default.css
new file mode 100644 (file)
index 0000000..cda9b27
--- /dev/null
@@ -0,0 +1,62 @@
+/* import the default css for the install applications */
+@import "default/acq.css";
+@import "default/admin.css";
+/* import the dojo CSS */
+@import "/js/dojo/dojo/resources/dojo.css";
+@import "/js/dojo/dijit/themes/tundra/tundra.css";
+@import "/js/dojo/dojox/grid/_grid/Grid.css";
+
+
+html, body, #oils-base-body-block {
+    width:100%;
+    height:100%;
+    border:0;
+    margin:0;
+    padding:0;
+}
+table { border-collapse: collapse; }
+/* use this for divs whose contents should be entirely contained within the div */
+.container:after {content: ""; display: block; height: 0; clear: both; }
+
+.invisible { visibility: hidden; }
+.hidden { display: none; visibility: hidden; }
+.display { display: block; visibility: visible; }
+.oils-login-dialog td { padding: 5px; }
+
+/* main layout blocks */
+#oils-base-main-block { width: 100%; margin-top: 0px; padding-top: 0px;}
+#oils-base-navigate-block { width: 12%; vertical-align: top; float:left;}
+#oils-base-content-block { width: 87%; vertical-align: top; float:right; padding-top: 0px;}
+#oils-base-sidebar-block { width: 12%; vertical-align: top; float:left;}
+
+#oils-base-header-auto-login { padding-right: 20px; }
+#oils-base-header-block { width: 100%; text-align: right; margin-top: 0px; padding-bottom: 0px;}
+#oils-base-header-menu-block { float:left; text-align: left; width: 50%; }
+#oils-base-header-auto-login-block { float:right; text-align: right; width: 47%;}
+
+#oils-base-footer-block { width: 100%; text-align: center; vertical-align: bottom;}
+
+#oils-base-navigate-list { width: 100%; }
+.oils-base-navigate-sub-list { padding-left: 4px; }
+.oils-base-navigate-item {}
+
+/* general purpose form table */
+.oils-admin-table { width: 100%; }
+.oils-admin-table td { padding: 4px; }
+.oils-admin-table textarea { width: 400px; height: 40px; overflow:auto;}
+.oils-admin-label { width: auto; }
+
+.label { margin: 1px; }
+
+
+/* local dojo style enhancements ----------------------------------- */
+/*
+.dojoxGrid {border: 1px solid #333; height: 90%;}
+*/
+/*
+.dojoxGrid {height: 90%;}
+.dojoxGrid-cell {padding: 8px;}
+*/
+.dijitTooltipTable td {padding: 3px;} /* custom class for handling dialog tables */
+/* ----------------------------------------------------------------- */
+
diff --git a/Open-ILS/web/css/skin/default/acq.css b/Open-ILS/web/css/skin/default/acq.css
new file mode 100644 (file)
index 0000000..8b47cd1
--- /dev/null
@@ -0,0 +1,94 @@
+.spacer {clear: both}
+
+#oils-acq-index-block { font-weight:bold; }
+/*
+.oils-sub-navigate-block { width: 100%;  text-align: left; padding: 3px;}
+.oils-sub-navigate-block span { padding: 3px; }
+*/
+
+.oils-acq-detail-content-pane {height:600px;width:100%}
+.oils-acq-basic-form-table td {padding:4px;}
+
+/* bib search */
+#oils-acq-search-container { width:100%; }
+#oils-acq-search-sources-block { width:32%; vertical-align: top; float: left; margin-right: 10px;}
+#oils-acq-search-form-block { width:63%; vertical-align: top; float:right; }
+#oils-acq-search-sources-selector { padding: 2px; }
+#oils-acq-search-sources-selector option { margin-bottom: 2px; }
+.oils-acq-search-form-row { width: 100%; }
+.oils-acq-search-form-label {}
+.oils-acq-search-form-input {}
+#oils-acq-search-sources-list { padding: 1px; }
+#oils-acq-search-sources-list li { list-style-type: none; padding-left: 0px; }
+.oils-acq-search-sources-sublist { padding: 1px; list-style-type: none;}
+.oils-acq-search-sources-sublist li { margin-left: 10px; }
+.oils-acq-search-subsources-label { margin-top: 5px; }
+#oils-acq-search-sources-label { margin-bottom: 10px; }
+#oils-acq-search-fields-label { margin-bottom: 10px; }
+#oils-acq-search-fields-submit-block { margin: 5px; text-align: center;}
+#oils-acq-search-progress {width: 100%; text-align: center;}
+#oils-acq-search-source-select option {padding: 5px;}
+#oils-acq-search-fields-tbody td {padding: 3px;}
+
+/* list of picklists */
+#oils-acq-picklist-list-table {width: 100%;}
+#oils-acq-picklist-list-table td {padding: 3px;}
+
+/* a single picklist */
+#oils-acq-picklist-table { width: 100%; }
+#oils-acq-picklist-header { padding: 4px; margin-bottom: 20px; }
+.oils-acq-picklist-records-jacket-td { width: 46px; height: 54px; }
+.oils-acq-picklist-records-jacket { width: 42px; height: 54px; padding-left: 0px; }
+.oils-acq-picklist-records-title {vertical-align: top}
+.oils-acq-picklist-records-copies {text-align: right; width: 200px}
+#oils-acq-picklist-paging-block { width: 25%; float: left; position: relative; }
+#oils-acq-picklist-actions-block { width: 75%; text-align: right; float: right; position: relative; }
+#oils-acq-picklist-header-subtable { width: 100%; }
+#oils-acq-picklist-header-block { width: 100%; display: block }
+
+/* list of pos */
+#oils-acq-po-list-table {width: 100%;}
+#oils-acq-po-list-table td {padding: 3px;}
+
+/* a single po */
+#oils-acq-po-table { width: 100%; }
+#oils-acq-po-header { padding: 4px; margin-bottom: 20px; }
+.oils-acq-po-records-jacket-td { width: 46px; }
+.oils-acq-po-records-jacket { width: 42px; height: 54px; padding-left: 0px; }
+.oils-acq-po-records-title-row {}
+.oils-acq-po-records-author-row td { padding-left: 30px; }
+.oils-acq-po-records-phys_desc-row td { padding-left: 30px; }
+.oils-acq-po-records-phys_desc-row {}
+
+#oils-acq-po-paging-block { width: 50%; text-align: left;}
+#oils-acq-po-actions-block { width: 50%; text-align: right;}
+#oils-acq-po-header-subtable { width: 100%; }
+
+#oils-acq-list-header { margin: 10px; width: 98%;}
+#oils-acq-list-header-label { float: left; }
+#oils-acq-list-header-actions { float: right; }
+
+/* purchase order line item detail page */
+#oils-acq-po-li-header { padding: 4px; margin-bottom: 20px; }
+#oils-acq-po-li-summary {}
+#oils-acq-po-li-summary td {padding: 2px;}
+.oils-acq-po-li-attr {}
+.oils-acq-po-li-attr-type {}
+.oils-acq-po-li-attr-name {}
+.oils-acq-po-li-attr-value {}
+#oils-acq-po-li-marc-block { margin-top: 10px; padding: 6px; }
+#oils-acq-po-li-details-table { width: 100%; }
+.oils-acq-po-li-detail-row {}
+
+/* picklist entry page */
+#oils-acq-lineitem-header { padding: 4px; margin-bottom: 20px; }
+#oils-acq-lineitem-summary {}
+#oils-acq-lineitem-summary td {padding: 2px;}
+.oils-acq-lineitem-attr {}
+.oils-acq-lineitem-attr-type {}
+.oils-acq-lineitem-attr-name {}
+.oils-acq-lineitem-attr-value {}
+#oils-acq-lineitem-marc-block { margin-top: 10px; padding: 6px; }
+
+
+
diff --git a/Open-ILS/web/css/skin/default/admin.css b/Open-ILS/web/css/skin/default/admin.css
new file mode 100644 (file)
index 0000000..c10a384
--- /dev/null
@@ -0,0 +1,3 @@
+#oils-admin-object-actions { width: 100%; padding: 2px; margin: 2px; text-align: right;}
+#oils-admin-object-table { width: 100%; }
+#oils-admin-object-table td { padding: 3px; }
diff --git a/Open-ILS/web/css/theme/default.css b/Open-ILS/web/css/theme/default.css
new file mode 100644 (file)
index 0000000..83043ea
--- /dev/null
@@ -0,0 +1,58 @@
+/* import the default css for the install applications */
+@import "default/acq.css";
+@import "default/admin.css";
+
+body { font-size: 80%; }
+
+#oils-base-body-block {}
+/*
+#oils-base-navigate-block {border: 2px solid #85C777; background: #6BA160;}
+#oils-base-navigate-block {border: 2px solid #f8f7f1; background: #fffdf1;}
+*/
+#oils-base-navigate-block {background: #d9e8f9;}
+
+#oils-base-navigate-block a { color: #000000; }
+#oils-base-content-block {}
+#oils-base-sidebar-block {}
+#oils-base-footer-block {padding: 3px; margin-top: 20px; border-top: 1px solid #5E5E5E;}
+/*
+#oils-base-header-block {border-bottom: 1px solid #5E5E5E; border: 2px solid #85C777; background: #6BA160;}
+*/
+#oils-base-header-block {border-bottom: 1px solid #d9e8f9;}
+
+
+#oils-base-navigate-list { width: 100%; }
+.oils-base-navigate-sub-list { margin-left: 14px; }
+/*.oils-base-navigate-item {border: 2px solid #85C777; background: #6BA160;}*/
+.oils-base-navigate-item:hover { background: #85C777; }
+
+/*
+#oils-base-navigate-table td:hover { background: #85C777; }
+.oils-base-sub-navigate-block { border: 2px solid #6BA160; background: #85C777;}
+.oils-base-sub-navigate-block a { color: #000000; }
+*/
+
+#oils-base-header-user-info { font-weight: bold; margin-right: 8px;}
+
+
+.label { font-weight: bold; }
+
+/*
+.oils-admin-table td { border-bottom: 1px solid #5E5E5E; }
+*/
+.oils-admin-table td { border-bottom: 1px solid #d9e8f9; }
+.oils-admin-label { font-weight: bold; }
+
+.oils-acq-nav-link {
+    padding:0px 3px 3px 3px;
+    border-top:1px solid #F8F7F1;
+    border-bottom:1px solid #F8F7F1;
+    font-size: 105%;
+}
+.oils-acq-nav-link a { text-decoration:none; }
+.oils-acq-nav-link-active {
+    background:#FFFDF3;
+    border-top:1px solid #ACA899;
+    border-bottom:1px solid #ACA899;
+}
+
diff --git a/Open-ILS/web/css/theme/default/acq.css b/Open-ILS/web/css/theme/default/acq.css
new file mode 100644 (file)
index 0000000..488d738
--- /dev/null
@@ -0,0 +1,53 @@
+
+#oils-acq-index-div { font-weight:bold; }
+
+#oils-acq-search-container { width:100%; }
+#oils-acq-search-sources-div { width:20%; float:left; }
+#oils-acq-search-form-div { width:80%; float:right; }
+#oils-acq-search-z39-sources-table thead td { font-weight: bold; }
+#oils-acq-search-z39-sources-table tbody td { width: 33%; }
+#oils-acq-search-sources-label { font-weight: bold; border-bottom: 1px solid #6BA160;}
+#oils-acq-search-fields-label { font-weight: bold; border-bottom: 1px solid #6BA160;}
+#oils-acq-search-subsources-label { font-weight: bold; }
+#oils-acq-search-fields-submit-block { border: 2px solid #A1A1A1; }
+
+/* list of picklists */
+#oils-acq-picklist-list-table {width: 100%;}
+#oils-acq-picklist-list-table thead td {font-weight:bold;}
+
+/* picklist display */
+#oils-acq-picklist-table thead tr { border: 1px solid #A1A1A1; }
+#oils-acq-picklist-header {border: 1px solid #85C777;}
+#oils-acq-lineitem-header {border: 1px solid #85C777;}
+#oils-acq-picklist-name { font-weight: bold; font-style: italic; }
+.oils-acq-picklist-attributes { font-size: 90%; margin-left: 15px;}
+.oils-acq-lineitem-attributes { font-size: 90%; margin-left: 15px;}
+.oils-acq-picklist-records-phys_desc-row { border-bottom: 1px solid #6BA160; }
+.oils-acq-picklist-picklist-td { border-style: solid; border-color: #A1A1A1; border-width: 0px 1px 0px 1px; } 
+.oils-acq-picklist-records-service-td { font-size: 85%; }
+.oils-acq-lineitem-delete-link { font-size: 85%; }
+#oils-acq-picklist-header-subtable tr { border: none; }
+
+/* po display */
+#oils-acq-po-table thead tr { border: 1px solid #A1A1A1; }
+#oils-acq-po-header {border: 1px solid #85C777;}
+#oils-acq-po-li-header {border: 1px solid #85C777;}
+#oils-acq-po-name { font-weight: bold; font-style: italic; }
+.oils-acq-po-attributes { font-size: 90%; margin-left: 15px;}
+.oils-acq-po-li-attributes { font-size: 90%; margin-left: 15px;}
+.oils-acq-po-records-phys_desc-row { border-bottom: 1px solid #6BA160; }
+.oils-acq-po-po-td { border-style: solid; border-color: #A1A1A1; border-width: 0px 1px 0px 1px; } 
+.oils-acq-po-records-service-td { font-size: 85%; }
+.oils-acq-po-li-delete-link { font-size: 85%; }
+#oils-acq-po-header-subtable tr { border: none; }
+
+/*
+#oils-acq-list-header {border-bottom: 1px solid #6BA160;}
+*/
+#oils-acq-list-header {border-bottom: 2px solid #d9e8f9;}
+#oils-acq-list-header-label { font-weight: bold; font-size: 110%; }
+#oils-acq-list-header-actions { font-weight: bold; }
+
+
+/* entry display */
+#oils-acq-lineitem-marc-block { border: 1px solid #6BA160; }
diff --git a/Open-ILS/web/css/theme/default/admin.css b/Open-ILS/web/css/theme/default/admin.css
new file mode 100644 (file)
index 0000000..a79d202
--- /dev/null
@@ -0,0 +1 @@
+#oils-admin-object-table tr { border-bottom: 1px solid #6BA160; }
diff --git a/Open-ILS/web/images/eg_logo.jpg b/Open-ILS/web/images/eg_logo.jpg
new file mode 100644 (file)
index 0000000..633b7a7
Binary files /dev/null and b/Open-ILS/web/images/eg_logo.jpg differ
diff --git a/Open-ILS/web/images/eg_tiny_logo.jpg b/Open-ILS/web/images/eg_tiny_logo.jpg
new file mode 100644 (file)
index 0000000..a570a37
Binary files /dev/null and b/Open-ILS/web/images/eg_tiny_logo.jpg differ
diff --git a/Open-ILS/web/js/dojo/openils/Util.js b/Open-ILS/web/js/dojo/openils/Util.js
new file mode 100644 (file)
index 0000000..d5e0c44
--- /dev/null
@@ -0,0 +1,82 @@
+/* ---------------------------------------------------------------------------
+ * Copyright (C) 2008  Georgia Public Library Service
+ * Bill Erickson <erickson@esilibrary.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * ---------------------------------------------------------------------------
+ */
+
+
+/**
+ * General purpose, static utility functions
+ */
+
+if(!dojo._hasResource["openils.Util"]) {
+    dojo._hasResource["openils.Util"] = true;
+    dojo.provide("openils.Util");
+    dojo.require('openils.Event');
+    dojo.declare('openils.Util', null, {});
+
+
+    /**
+     * Wrapper for dojo.addOnLoad that verifies a valid login session is active
+     * before adding the function to the onload set
+     */
+    openils.Util.addOnLoad = function(func, noSes) {
+        if(func) {
+            if(!noSes) {
+                dojo.require('openils.User');
+                if(!openils.User.authtoken) 
+                    return;
+            }
+            console.log("adding onload " + func.name);
+            dojo.addOnLoad(func);
+        }
+    };
+
+    /**
+     * Returns true if the provided array contains the specified value
+     */
+    openils.Util.arrayContains = function(arr, val) {
+        for(var i = 0; arr && i < arr.length; i++) {
+            if(arr[i] == val)
+                return true;
+        }
+        return false;
+    };
+
+    /**
+     * Parses opensrf response objects to see if they contain 
+     * data and/or an ILS event.  This only calls request.recv()
+     * once, so in a streaming context, it's necessary to loop on
+     * this method. 
+     * @param r The OpenSRF Request object
+     * @param eventOK If true, any found events will be returned as responses.  
+     * If false, they will be treated as error conditions and their content will
+     * be alerted if openils.Util.alertEvent is set to true.  Also, if eventOk is
+     * false, the response content will be null when an event is encountered.
+     */
+    openils.Util.alertEvent = true;
+    openils.Util.extractResponse = function(r, eventOk) {
+        var msg = r.recv();
+        if(msg == null) return msg;
+        var val = msg.content();
+        if(e = openils.Event.parse(val)) {
+            if(eventOk) return e;
+            console.log(e.toString());
+            if(openils.Util.alertEvent)
+                alert(e);
+            return null;
+        }
+        return val;
+    };
+
+}
diff --git a/Open-ILS/web/js/dojo/openils/acq/CurrencyType.js b/Open-ILS/web/js/dojo/openils/acq/CurrencyType.js
new file mode 100644 (file)
index 0000000..62451fd
--- /dev/null
@@ -0,0 +1,55 @@
+/* ---------------------------------------------------------------------------
+ * Copyright (C) 2008  Georgia Public Library Service
+ * Bill Erickson <erickson@esilibrary.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * ---------------------------------------------------------------------------
+ */
+
+if(!dojo._hasResource["openils.acq.CurrencyType"]) {
+
+    dojo._hasResource["openils.acq.CurrencyType"] = true;
+    dojo.provide("openils.acq.CurrencyType");
+    dojo.require('openils.User');
+
+    dojo.declare('openils.acq.CurrencyType', null, {
+    });
+
+    openils.acq.CurrencyType.cache = {};
+
+    /**
+     * Retrieves all of the currency types
+     */
+    openils.acq.CurrencyType.fetchAll = function(onComplete) {
+        var req = new OpenSRF.ClientSession('open-ils.acq').request(
+            'open-ils.acq.currency_type.all.retrieve', openils.User.authtoken);
+
+        req.oncomplete = function(r) {
+            var msg = r.recv();
+            var types = msg.content();
+            for(var i in types) 
+                openils.acq.CurrencyType.cache[types[i].code()] = types[i];
+            onComplete(types);
+        }
+        req.send();
+    }
+
+    openils.acq.CurrencyType.loadSelectWidget = function(selector) {
+        openils.acq.CurrencyType.fetchAll(
+            function(ctypes) {
+                selector.store = new dojo.data.ItemFileReadStore(
+                    {data:acqct.toStoreData(ctypes, 'code', {identifier:'code'})});
+                selector.setValue(ctypes[0].code()); /* XXX get from setting */
+            }
+        );
+    }
+}
+
diff --git a/Open-ILS/web/js/dojo/openils/acq/Fund.js b/Open-ILS/web/js/dojo/openils/acq/Fund.js
new file mode 100644 (file)
index 0000000..9ed523b
--- /dev/null
@@ -0,0 +1,213 @@
+/* ---------------------------------------------------------------------------
+ * Copyright (C) 2008  Georgia Public Library Service
+ * Bill Erickson <erickson@esilibrary.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * ---------------------------------------------------------------------------
+ */
+
+if(!dojo._hasResource['openils.acq.Fund']) {
+dojo._hasResource['openils.acq.Fund'] = true;
+dojo.provide('openils.acq.Fund');
+dojo.require('fieldmapper.Fieldmapper');
+dojo.require('fieldmapper.dojoData');
+dojo.require('openils.Event');
+
+/** Declare the Fund class with dojo */
+dojo.declare('openils.acq.Fund', null, {
+    /* add instance methods here if necessary */
+});
+
+openils.acq.Fund.cache = {};
+openils.acq.Fund._cachecomplete = false;
+
+openils.acq.Fund.createStore = function(onComplete, limitPerm) {
+    /** Fetches the list of funds and builds a grid from them */
+
+    function mkStore(r) {
+        var msg;
+        var items = [];
+        while(msg = r.recv()) {
+            var src = msg.content();
+            if(e = openils.Event.parse(src))
+                return alert(e);
+            openils.acq.Fund.cache[src.id()] = src;
+            items.push(src);
+        }
+           openils.acq.Fund._cachecomplete = true;
+        onComplete(acqf.toStoreData(items));
+    }
+
+    fieldmapper.standardRequest(
+        ['open-ils.acq', 'open-ils.acq.fund.org.retrieve'],
+        {   async: true,
+            params: [openils.User.authtoken, null, {flesh_summary:1, limit_perm:limitPerm}],
+            oncomplete: mkStore
+        }
+    );
+};
+
+/**
+ * Create a new fund
+ * @param fields Key/value pairs used to create the new fund
+ */
+openils.acq.Fund.create = function(fields, onCreateComplete) {
+
+    var fund = new acqf()
+    for(var field in fields) 
+        fund[field](fields[field]);
+
+    fieldmapper.standardRequest(
+        ['open-ils.acq', 'open-ils.acq.fund.create'],
+        {   async: true,
+            params: [openils.User.authtoken, fund],
+            oncomplete: function(r) {
+                var msg = r.recv();
+                var id = msg.content();
+                if(onCreateComplete)
+                    onCreateComplete(id);
+            }
+        }
+    );
+};
+
+
+openils.acq.Fund.createAllocation = function(fields, onCreateComplete) {
+    var alloc = new acqfa()
+    for(var field in fields) 
+        alloc[field](fields[field]);
+    fieldmapper.standardRequest(
+        ['open-ils.acq', 'open-ils.acq.fund_allocation.create'],
+        {
+            async: true,
+            params: [openils.User.authtoken, alloc],
+            oncomplete: function(r) {
+                var msg = r.recv();
+                var id = msg.content();
+                if(onCreateComplete)
+                    onCreateComplete(id);
+            }
+        }
+    );
+};
+
+
+/**
+ * Synchronous fund retrievel method 
+ */
+openils.acq.Fund.retrieve = function(id) {
+    if(openils.acq.Fund.cache[id])
+        return openils.acq.Fund.cache[id];
+    openils.acq.Fund.cache[id] = fieldmapper.standardRequest(
+        ['open-ils.acq', 'open-ils.acq.fund.retrieve'],
+        [openils.User.authtoken, id]
+    );
+    return openils.acq.Fund.cache[id];
+};
+
+
+openils.acq.Fund.deleteFromGrid = function(grid, onComplete) {
+    var list = []
+    var selected = grid.selection.getSelected();
+    for(var rowIdx in selected) 
+        list.push(grid.model.getDatum(selected[rowIdx], 0));
+    openils.acq.Fund.deleteList(list, onComplete);
+};
+
+openils.acq.Fund.deleteList = function(list, onComplete) {
+    openils.acq.Fund._deleteList(list, 0, onComplete);
+}
+
+openils.acq.Fund._deleteList = function(list, idx, onComplete) {
+    if(idx >= list.length)    
+        return onComplete();
+
+    var fundId = list[idx];
+    delete openils.acq.Fund.cache[list[idx]];
+
+    fieldmapper.standardRequest(
+        ['open-ils.acq', 'open-ils.acq.fund.delete'],
+        {   async: true,
+            params: [openils.User.authtoken, fundId],
+            oncomplete: function(r) {
+                stat = r.recv().content();
+                /* XXX CHECH FOR EVENT */
+                openils.acq.Fund._deleteList(list, ++idx, onComplete);
+            }
+        }
+    );
+};
+
+openils.acq.Fund.nameMapping = function(oncomplete) {
+    var ids = [];
+    var names = [];
+    var buildMap = function() {
+       for (var i in openils.acq.Fund.cache) {
+           var item = openils.acq.Fund.cache[i];
+           ids.push(item.id());
+           names.push(item.name());
+           oncomplete(ids, names);
+       }
+    };
+
+    if (openils.acq.Fund._cachecomplete) {
+       buildMap(oncomplete);
+    } else {
+       openils.acq.Fund.createStore(buildMap);
+    }
+};
+
+/**
+  * Sets the store for an existing openils.widget.FundFilteringSelect 
+  * using the funds where the user has the requested permission.
+  * @param perm The permission to check
+  * @param selector The pre-created dijit.form.FilteringSelect object.  
+  */
+
+openils.acq.Fund.storeCache = [];
+
+openils.acq.Fund.buildPermFundSelector = function(perm, selector) {
+    dojo.require('dojo.data.ItemFileReadStore');
+
+    function hookupStore(store) {
+       selector.store = store;
+        selector.startup();
+    }
+
+    function buildPicker(r) {
+       var msg;
+       var fundList = [];
+       while (msg = r.recv()) {
+           var fund = msg.content();
+           fundList.push(fund);
+       }
+
+       var store = new dojo.data.ItemFileReadStore({data:acqf.toStoreData(fundList)});
+
+       hookupStore(store);
+       openils.acq.Fund.storeCache[perm] = store;
+    }
+
+    if (openils.acq.Fund.storeCache[perm]) {
+       hookupStore(openils.acq.Fund.storeCache[perm]);
+    } else {
+       fieldmapper.standardRequest(
+           ['open-ils.acq', 'open-ils.acq.fund.org.retrieve'],
+            {   params: [openils.User.authtoken, null,
+                        {flesh_summary:1, limit_perm:perm}],
+               oncomplete: buildPicker,
+               async: true
+            }
+       )
+    }
+}
+
+}
diff --git a/Open-ILS/web/js/dojo/openils/acq/FundingSource.js b/Open-ILS/web/js/dojo/openils/acq/FundingSource.js
new file mode 100644 (file)
index 0000000..d5cf3f6
--- /dev/null
@@ -0,0 +1,140 @@
+/* ---------------------------------------------------------------------------
+ * Copyright (C) 2008  Georgia Public Library Service
+ * Bill Erickson <erickson@esilibrary.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * ---------------------------------------------------------------------------
+ */
+
+if(!dojo._hasResource['openils.acq.FundingSource']) {
+dojo._hasResource['openils.acq.FundingSource'] = true;
+dojo.provide('openils.acq.FundingSource');
+dojo.require('fieldmapper.Fieldmapper');
+dojo.require('fieldmapper.dojoData');
+
+/** Declare the FundingSource class with dojo */
+dojo.declare('openils.acq.FundingSource', null, {
+    /* add instance methods here if necessary */
+});
+
+/** cached funding_source objects */
+openils.acq.FundingSource.cache = {};
+
+openils.acq.FundingSource.createStore = function(onComplete) {
+    /** Fetches the list of funding_sources and builds a grid from them */
+    var ses = new OpenSRF.ClientSession('open-ils.acq');
+    var req = ses.request('open-ils.acq.funding_source.org.retrieve', 
+        openils.User.authtoken, null, {flesh_summary:1});
+
+    req.oncomplete = function(r) {
+        var msg
+        var items = [];
+        var src = null;
+        while(msg = r.recv()) {
+            src = msg.content();
+            openils.acq.FundingSource.cache[src.id()] = src;
+            items.push(src);
+        }
+        onComplete(acqfs.toStoreData(items));
+    };
+
+    req.send();
+};
+
+
+
+/**
+ * Create a new funding source object
+ * @param fields Key/value pairs used to create the new funding source
+ */
+openils.acq.FundingSource.create = function(fields, onCreateComplete) {
+
+    var fs = new acqfs()
+    for(var field in fields) 
+        fs[field](fields[field]);
+
+    var ses = new OpenSRF.ClientSession('open-ils.acq');
+    var req = ses.request('open-ils.acq.funding_source.create', openils.User.authtoken, fs);
+
+    req.oncomplete = function(r) {
+        var msg = r.recv();
+        var id = msg.content();
+        if(onCreateComplete)
+            onCreateComplete(id);
+    };
+    req.send();
+};
+
+/**
+ * Synchronous funding_source retrievel method 
+ */
+openils.acq.FundingSource.retrieve = function(id) {
+    if(openils.acq.FundingSource.cache[id])
+        return openils.acq.FundingSource.cache[id];
+    openils.acq.FundingSource.cache[id] = fieldmapper.standardRequest(
+        ['open-ils.acq', 'open-ils.acq.funding_source.retrieve'],
+        [openils.User.authtoken, id]
+    );
+    return openils.acq.FundingSource.cache[id];
+};
+
+
+openils.acq.FundingSource.createCredit = function(fields, onCreateComplete) {
+
+    var fsc = new acqfscred()
+    for(var field in fields) 
+        fsc[field](fields[field]);
+
+    var ses = new OpenSRF.ClientSession('open-ils.acq');
+    var req = ses.request(
+        'open-ils.acq.funding_source_credit.create', openils.User.authtoken, fsc);
+
+    req.oncomplete = function(r) {
+        var msg = r.recv();
+        var id = msg.content();
+        if(onCreateComplete)
+            onCreateComplete(id);
+    };
+    req.send();
+};
+
+
+openils.acq.FundingSource.deleteFromGrid = function(grid, onComplete) {
+    var list = []
+    var selected = grid.selection.getSelected();
+    for(var rowIdx in selected) 
+        list.push(grid.model.getDatum(selected[rowIdx], 0));
+    openils.acq.FundingSource.deleteList(list, onComplete);
+};
+
+openils.acq.FundingSource.deleteList = function(list, onComplete) {
+    openils.acq.FundingSource._deleteList(list, 0, onComplete);
+}
+
+openils.acq.FundingSource._deleteList = function(list, idx, onComplete) {
+    if(idx >= list.length)    
+        return onComplete();
+
+    var ses = new OpenSRF.ClientSession('open-ils.acq');
+    var req = ses.request('open-ils.acq.funding_source.delete', openils.User.authtoken, list[idx]);
+    delete openils.acq.FundingSource.cache[list[idx]];
+
+    req.oncomplete = function(r) {
+        msg = r.recv()
+        stat = msg.content();
+        /* XXX CHECH FOR EVENT */
+        openils.acq.FundingSource._deleteList(list, ++idx, onComplete);
+    }
+    req.send();
+};
+
+
+} /* end dojo._hasResource[] */
diff --git a/Open-ILS/web/js/dojo/openils/acq/Lineitem.js b/Open-ILS/web/js/dojo/openils/acq/Lineitem.js
new file mode 100644 (file)
index 0000000..8325517
--- /dev/null
@@ -0,0 +1,239 @@
+/* ---------------------------------------------------------------------------
+ * Copyright (C) 2008  Georgia Public Library Service
+ * David J. Fiander <david@fiander.info>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * ---------------------------------------------------------------------------
+ */
+
+if(!dojo._hasResource['openils.acq.Lineitem']) {
+dojo._hasResource['openils.acq.Lineitem'] = true;
+dojo.provide('openils.acq.Lineitem');
+
+dojo.require('dojo.data.ItemFileWriteStore');
+dojo.require('dojox.grid.Grid');
+dojo.require('dojox.grid._data.model');
+dojo.require('fieldmapper.dojoData');
+dojo.require('openils.User');
+dojo.require('openils.Event');
+
+/** Declare the Lineitem class with dojo */
+dojo.declare('openils.acq.Lineitem', null, {
+    /* add instance methods here if necessary */
+
+    constructor: function(args) {
+        this.lineitem = args.lineitem;
+    },
+
+    findAttr: function(name, type) {
+        var attrs = this.lineitem.attributes();
+        if(!attrs) return null;
+        for(var i = 0; i < attrs.length; i++) {
+            var attr = attrs[i];
+            if (attr.attr_type() == type && attr.attr_name() == name) 
+                return attr.attr_value();
+        }
+    },
+
+    // returns the actual price if available, otherwise estimated price, otherwise null
+    // priority is given to local attrs, then provider attrs, then MARC attrs
+    getPrice: function() {
+        return this.getActualPrice() || this.getEstimatedPrice();
+    },
+
+    // returns the actual price, null if none
+    getActualPrice : function() {
+        return this._getPriceAttr('actual_price');
+    },
+
+    // returns the estimated price, null if none
+    getEstimatedPrice : function() {
+        return this._getPriceAttr('estimated_price');
+    },
+
+    _getPriceAttr : function(attr) {
+        var types = [
+            'lineitem_local_attr_definition', 
+            'lineitem_provider_attr_definition', 
+            'lineitem_marc_attr_definition'
+        ];
+
+        for(var t in types) {
+            if(price = this.findAttr(attr, types[t]))
+                return {price:price, source_type: attr, source_attr: types[t]};
+        }
+
+        return null;
+    },
+
+    update: function(oncomplete) {
+        fieldmapper.standardRequest(
+            ['open-ils.acq', 'open-ils.acq.lineitem.update'],
+            {   async: true,
+                params: [openils.User.authtoken, this.lineitem],
+                oncomplete: function(r) {
+                   oncomplete(openils.Event.parse(r.recv().content()));
+                }
+            }
+        );
+    },
+
+    approve: function(oncomplete) {
+       fieldmapper.standardRequest(
+           ['open-ils.acq', 'open-ils.acq.lineitem.approve'],
+           {  async: true,
+              params: [openils.User.authtoken, this.lineitem.id()],
+              oncomplete: function(r) {
+                  oncomplete(openils.Event.parse(r.recv().content()));
+              }
+           });
+    },
+
+    id: function() {
+       return this.lineitem.id();
+    },
+});
+
+openils.acq.Lineitem.ModelCache = {};
+openils.acq.Lineitem.acqlidCache = {};
+
+openils.acq.Lineitem.createLIDStore = function(li_id, onComplete) {
+    // Fetches the details of a lineitem and builds a grid
+
+    function mkStore(r) {
+       var msg;
+       var items = [];
+       while (msg = r.recv()) {
+           var data = msg.content();
+           for (i in data.lineitem_details()) {
+               var lid = data.lineitem_details()[i];
+               items.push(lid);
+               openils.acq.Lineitem.acqlidCache[lid.id()] = lid;
+           }
+       }
+
+       onComplete(acqlid.toStoreData(items));
+    }
+
+    fieldmapper.standardRequest(
+       ['open-ils.acq', 'open-ils.acq.lineitem.retrieve'],
+       { async: true,
+         params: [openils.User.authtoken, li_id,
+                  {flesh_attrs:1, flesh_li_details:1}],
+         oncomplete: mkStore
+       });
+};
+
+openils.acq.Lineitem.alertOnLIDSet = function(griditem, attr, oldVal, newVal) {
+    var item;
+    var updateDone = function(r) {
+       var stat = r.recv().content();
+       var evt = openils.Event.parse(stat);
+
+       if (evt) {
+           alert("Error: "+evt.desc);
+           console.dir(evt);
+           if (attr == "fund") {
+               item.fund(oldVal);
+               griditem.fund = oldVal;
+           } else if (attr ==  "owning_lib") {
+               item.owning_lib(oldVal);
+               griditem.owning_lib = oldVal;
+           }
+       }
+    };
+
+    if (oldVal == newVal) {
+       return;
+    }
+
+    item = openils.acq.Lineitem.acqlidCache[griditem.id];
+    
+    if (attr == "fund") {
+       item.fund(newVal);
+    } else if (attr ==  "owning_lib") {
+       item.owning_lib(newVal);
+    } else if (attr ==  "cn_label") {
+       item.cn_label(newVal);
+    } else if (attr ==  "barcode") {
+       item.barcode(newVal);
+    } else if (attr ==  "location") {
+       item.location(newVal);
+    } else {
+       alert("Unexpected attr in Lineitem.alertOnSet: '"+attr+"'");
+       return;
+    }
+
+    fieldmapper.standardRequest(
+       ["open-ils.acq", "open-ils.acq.lineitem_detail.update"],
+       { params: [openils.User.authtoken, item],
+         oncomplete: updateDone
+       });
+};
+
+openils.acq.Lineitem.deleteLID = function(id, onComplete) {
+    fieldmapper.standardRequest(
+        ['open-ils.acq', 'open-ils.acq.lineitem_detail.delete'],
+        {   async: true,
+            params: [openils.User.authtoken, id],
+            oncomplete: function(r) {
+                msg = r.recv()
+                stat = msg.content();
+               onComplete(openils.Event.parse(stat));
+            }
+    });
+};
+
+openils.acq.Lineitem.createLID = function(fields, onCreateComplete) {
+    var lid = new acqlid()
+    for (var field in fields) {
+       lid[field](fields[field]);
+    }
+
+    fieldmapper.standardRequest(
+       ['open-ils.acq', 'open-ils.acq.lineitem_detail.create'],
+       { async: true,
+         params: [openils.User.authtoken, lid, {return_obj:1}],
+         oncomplete: function(r) {
+             var msg = r.recv();
+          var obj = msg.content();
+          openils.Event.parse_and_raise(obj);
+             if (onCreateComplete) {
+                   onCreateComplete(obj);
+             }
+         }
+       });
+};
+
+openils.acq.Lineitem.loadLIDGrid = function(domNode, id, layout) {
+    if (!openils.acq.Lineitem.ModelCache[id]) {
+       openils.acq.Lineitem.createLIDStore(id,
+               function(storeData) {
+                   var store = new dojo.data.ItemFileWriteStore({data:storeData});
+                   var model = new dojox.grid.data.DojoData(null, store,
+                       {rowsPerPage: 20, clientSort:true, query:{id:'*'}});
+
+                   dojo.connect(store, "onSet",
+                                openils.acq.Lineitem.alertOnLIDSet);
+                   openils.acq.Lineitem.ModelCache[id] = model;
+
+                   domNode.setStructure(layout);
+                   domNode.setModel(model);
+                   domNode.update();
+               });
+    } else {
+       domNode.setModel(openils.acq.Lineitem.ModelCache[id]);
+       domNode.setStructure(layout);
+       domNode.update();
+       domNode.refresh();
+    }
+};
+}
diff --git a/Open-ILS/web/js/dojo/openils/acq/LineitemAttr.js b/Open-ILS/web/js/dojo/openils/acq/LineitemAttr.js
new file mode 100644 (file)
index 0000000..655914a
--- /dev/null
@@ -0,0 +1,55 @@
+/* ---------------------------------------------------------------------------
+ * Copyright (C) 2008  Georgia Public Library Service
+ * Bill Erickson <erickson@esilibrary.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * ---------------------------------------------------------------------------
+ */
+if(!dojo._hasResource['openils.acq.LineitemAttr']) {
+dojo._hasResource['openils.acq.LineitemAttr'] = true;
+dojo.provide('openils.acq.LineitemAttr');
+dojo.require('fieldmapper.dojoData');
+dojo.require('openils.User');
+dojo.require('openils.Event');
+
+/** Declare the LineitemAttr class with dojo */
+dojo.declare('openils.acq.LineitemAttr', null, {});
+
+/** Pile of static methods for handling the different types of 
+ * lineitem attributes and definitions
+ */
+
+
+/**
+ * Creates a set of attr definition stores, one per definition type.
+ */
+openils.acq.LineitemAttr.createAttrDefStores = function(onload) {
+    function process(r) {
+        var res = r.recv().content();
+        openils.Event.parse_and_raise(res);
+        var stores = {};
+        stores.marc = acqlimad.toStoreData(res.marc);
+        stores.usr = acqliuad.toStoreData(res.usr);
+        stores.local = acqlilad.toStoreData(res.local);
+        stores.generated = acqligad.toStoreData(res.generated);
+        stores.provider = acqlipad.toStoreData(res.provider);
+        onload(stores);
+    }
+
+    fieldmapper.standardRequest(
+        ['open-ils.acq', 'open-ils.acq.lineitem_attr_definition.retrieve.all'],
+        {   async: true,
+            params: [openils.User.authtoken],
+            oncomplete: process
+        }
+    );
+}
+}
diff --git a/Open-ILS/web/js/dojo/openils/acq/PO.js b/Open-ILS/web/js/dojo/openils/acq/PO.js
new file mode 100644 (file)
index 0000000..26c859f
--- /dev/null
@@ -0,0 +1,76 @@
+/* ---------------------------------------------------------------------------
+ * Copyright (C) 2008  Georgia Public Library Service
+ * Bill Erickson <erickson@esilibrary.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * ---------------------------------------------------------------------------
+ */
+
+if(!dojo._hasResource['openils.acq.PO']) {
+
+    dojo._hasResource['openils.acq.PO'] = true;
+    dojo.provide('openils.acq.PO');
+    dojo.require('fieldmapper.Fieldmapper');
+    dojo.require('fieldmapper.dojoData');
+    dojo.require('openils.Util');
+
+    /** Declare the PO class with dojo */
+    dojo.declare('openils.acq.PO', null, {
+        /* add instance methods here if necessary */
+    });
+
+    openils.acq.PO.cache = {};
+
+    openils.acq.PO.retrieve = function(id, oncomplete, args) {
+
+        var req = ['open-ils.acq', 'open-ils.acq.purchase_order.retrieve'];
+        var par = [openils.User.authtoken, id, args];
+
+        if(oncomplete) {
+            fieldmapper.standardRequest(
+                req, 
+                {   params:par, 
+                    async: true,
+                    oncomplete:function(r) {
+                        var po = openils.Util.extractResponse(r)
+                        if(po) {
+                            openils.acq.PO.cache[po.id()] = po;
+                            oncomplete(po);
+                        }
+                    }
+                }
+            );
+        } else {
+            return openils.acq.PO.cache[po.id()] = 
+                fieldmapper.standardRequest(req, par);
+        }
+    }
+
+    openils.acq.PO.create = function(po, oncomplete) {
+        var req = ['open-ils.acq', 'open-ils.acq.purchase_order.create'];
+        var par = [openils.User.authtoken, po];
+
+        fieldmapper.standardRequest(
+            req,
+            {   params: par,
+                async: true, 
+                oncomplete: function(r) {
+                    var po_id = r.recv().content();
+                    po.id(po_id);
+                    openils.acq.PO.cache[po_id] = po;
+                    oncomplete(po_id);
+                }
+            }
+        );
+    }
+};
+        
+
diff --git a/Open-ILS/web/js/dojo/openils/acq/Picklist.js b/Open-ILS/web/js/dojo/openils/acq/Picklist.js
new file mode 100644 (file)
index 0000000..ea84465
--- /dev/null
@@ -0,0 +1,166 @@
+/* ---------------------------------------------------------------------------
+ * Copyright (C) 2008  Georgia Public Library Service
+ * David J. Fiander <david@fiander.info>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * ---------------------------------------------------------------------------
+ */
+
+if(!dojo._hasResource['openils.acq.Picklist']) {
+dojo._hasResource['openils.acq.Picklist'] = true;
+dojo.provide('openils.acq.Picklist');
+
+dojo.require('dojo.data.ItemFileWriteStore');
+dojo.require('dojox.grid.Grid');
+dojo.require('dojox.grid._data.model');
+dojo.require('fieldmapper.Fieldmapper');
+dojo.require('fieldmapper.dojoData');
+dojo.require('openils.Event');
+
+/** Declare the Picklist class with dojo */
+dojo.declare('openils.acq.Picklist', null, {
+
+    constructor: function (pl_id, onComplete, args) {
+
+        var pl_this = this;            // 'this' doesn't exist inside callbacks
+        var liArgs = (args && args.liArgs) ? args.liArgs : {flesh_attrs:1, clear_marc:1};
+        var mkStore = function (r) {
+            var storeData;
+            var msg;
+            pl_this._items = [];
+
+            while (msg = r.recv()) {
+                var data = msg.content();
+                pl_this._data[data.id()] = data;
+                pl_this._items.push(data);
+            }
+
+            storeData = jub.toStoreData(pl_this._items, null, {virtualFields:['estimated_price', 'actual_price']});
+            pl_this._store = new dojo.data.ItemFileWriteStore({data:storeData});
+            pl_this._model = new dojox.grid.data.DojoData(null, pl_this._store,
+                                   {rowsPerPage:20, clientSort:true,
+                                query:{id:'*'}});
+            onComplete(pl_this._model);
+        };
+
+        this._id = pl_id;
+        this._data = {};
+        this._plist = null;
+        //
+        // Fetch the picklist information
+        fieldmapper.standardRequest(
+            ['open-ils.acq', 'open-ils.acq.picklist.retrieve'],
+            {   async: false,
+                params: [openils.User.authtoken, pl_id, {flesh_lineitem_count:1}],
+                oncomplete: function(r) {
+                    var pl = r.recv().content(); 
+                    if(e = openils.Event.parse(pl))
+                        return alert(pl);
+                    pl_this._plist = pl;
+                }
+            });
+
+        // Fetch the title list for the picklist, asynchronously
+        fieldmapper.standardRequest(
+            ['open-ils.acq', 'open-ils.acq.lineitem.picklist.retrieve'],
+            { async: true,
+              params: [openils.User.authtoken, pl_id, liArgs],
+              oncomplete: mkStore
+        });
+    },
+
+    id: function () {
+       return this._id;
+    },
+    name: function() {
+       return this._plist.name();
+    },
+    owner: function() {
+       return this._plist.owner();
+    },
+    create_time: function() {
+       return this._plist.create_time();
+    },
+    edit_time: function() {
+       return this._plist.edit_time();
+    },
+
+    find_attr: function(id, at_name, at_type) {
+        attr_list = this._data[id].attributes();
+        for (var i in attr_list) {
+            var attr = attr_list[i];
+            if (attr.attr_type() == at_type && attr.attr_name() == at_name) {
+            return attr.attr_value();
+            }
+        }
+        return '';
+    },
+});
+
+/** Creates a new picklist. fields.name is required */ 
+openils.acq.Picklist.create = function(fields, oncomplete) {
+    var picklist = new acqpl();
+    picklist.owner(fields.owner || new openils.User().user.id());
+    picklist.name(fields.name);
+
+    fieldmapper.standardRequest(
+        ['open-ils.acq', 'open-ils.acq.picklist.create'],
+        {   async: true,
+            params: [openils.User.authtoken, picklist],
+            oncomplete: function(r) { 
+                // XXX event/error handling
+                oncomplete(r.recv().content());
+            }
+        }
+    );
+}
+
+/** Creates a new picklist. fields.name is required */ 
+openils.acq.Picklist.update = function(picklist, oncomplete) {
+    fieldmapper.standardRequest(
+        ['open-ils.acq', 'open-ils.acq.picklist.update'],
+        {   async: true,
+            params: [openils.User.authtoken, picklist],
+            oncomplete: function(r) { 
+                // XXX event/error handling
+                oncomplete(r.recv().content());
+            }
+        }
+    );
+}
+
+/** Deletes a list of picklists
+ * @param list Array of picklist IDs
+ */
+openils.acq.Picklist.deleteList = function(list, onComplete) {
+    openils.acq.Picklist._deleteList(list, 0, onComplete);
+}
+
+/* iterate through the list of IDs deleting asynchronously as we go... */
+openils.acq.Picklist._deleteList = function(list, idx, onComplete) {
+    if(idx >= list.length)
+        return onComplete();
+    fieldmapper.standardRequest(
+        ['open-ils.acq', 'open-ils.acq.picklist.delete'],
+        {   async: true,
+            params: [openils.User.authtoken, list[idx]],
+            oncomplete: function(r) {
+                msg = r.recv()
+                stat = msg.content();
+                /* XXX CHECH FOR EVENT */
+                openils.acq.Picklist._deleteList(list, ++idx, onComplete);
+            }
+        }
+    );
+}
+
+}
+
diff --git a/Open-ILS/web/js/dojo/openils/acq/Provider.js b/Open-ILS/web/js/dojo/openils/acq/Provider.js
new file mode 100644 (file)
index 0000000..9803bd0
--- /dev/null
@@ -0,0 +1,174 @@
+/* ---------------------------------------------------------------------------
+ * Copyright (C) 2008  Georgia Public Library Service
+ * Bill Erickson <erickson@esilibrary.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * ---------------------------------------------------------------------------
+ */
+
+if(!dojo._hasResource['openils.acq.Provider']) {
+dojo._hasResource['openils.acq.Provider'] = true;
+dojo.provide('openils.acq.Provider');
+dojo.require('fieldmapper.Fieldmapper');
+dojo.require('fieldmapper.dojoData');
+
+/** Declare the Provider class with dojo */
+dojo.declare('openils.acq.Provider', null, {
+    /* add instance methods here if necessary */
+});
+
+openils.acq.Provider.cache = {};
+
+/* define some static provider methods ------- */
+
+openils.acq.Provider.createStore = function(onComplete, limitPerm) {
+    /** Fetches the list of funding_sources and builds a grid from them */
+
+    function mkStore(r) {
+        var msg;
+        var items = [];
+        while(msg = r.recv()) {
+            var provider = msg.content();
+            openils.acq.Provider.cache[provider.id()] = provider;
+            items.push(provider);
+        }
+        onComplete(acqpro.toStoreData(items));
+    }
+
+    fieldmapper.standardRequest(
+        ['open-ils.acq', 'open-ils.acq.provider.org.retrieve'],
+        {   async: true,
+            params: [openils.User.authtoken],
+            oncomplete: mkStore
+        }
+    );
+};
+
+
+/**
+ * Synchronous provider retrievel method 
+ */
+openils.acq.Provider.retrieve = function(id) {
+    if(openils.acq.Provider.cache[id])
+        return openils.acq.Provider.cache[id];
+
+    openils.acq.Provider.cache[id] = 
+        fieldmapper.standardRequest(
+            ['open-ils.acq', 'open-ils.acq.provider.retrieve'],
+            [openils.User.authtoken, id]
+        );
+    return openils.acq.Provider.cache[id];
+};
+
+openils.acq.Provider.create = function(fields, oncomplete) {
+    var provider = new acqpro()
+    for(var field in fields) 
+        provider[field](fields[field]);
+
+    fieldmapper.standardRequest(
+        ['open-ils.acq', 'open-ils.acq.provider.create'],
+        {   async: true,
+            params: [openils.User.authtoken, provider],
+            oncomplete: function(r) {
+                var msg = r.recv();
+                var id = msg.content();
+                if(oncomplete)
+                    oncomplete(id);
+            }
+        }
+    );
+};
+
+
+openils.acq.Provider.retrieveLineitemProviderAttrDefs = function(providerId, oncomplete) {
+    fieldmapper.standardRequest(
+        ['open-ils.acq', 'open-ils.acq.lineitem_provider_attr_definition.provider.retrieve.atomic'],
+        {   async: true,
+            params: [openils.User.authtoken, providerId],
+            oncomplete: function(r) {oncomplete(r.recv().content());}
+        }
+    );
+}
+
+openils.acq.Provider.createLineitemProviderAttrDef = function(fields, oncomplete) {
+    var attr = new acqlipad();
+    for(var field in fields) 
+        attr[field](fields[field]);
+
+    fieldmapper.standardRequest(
+        ['open-ils.acq', 'open-ils.acq.lineitem_provider_attr_definition.create'],
+        {   async: true,
+            params: [openils.User.authtoken, attr],
+            oncomplete: function(r) {oncomplete(r.recv().content());}
+        }
+    );
+}
+
+
+openils.acq.Provider.lineitemProviderAttrDefDeleteList = function(list, oncomplete) {
+    openils.acq.Provider._lineitemProviderAttrDefDeleteList(list, 0, oncomplete);
+}
+
+openils.acq.Provider._lineitemProviderAttrDefDeleteList = function(list, idx, oncomplete) {
+    if(idx >= list.length)
+        return oncomplete();
+    fieldmapper.standardRequest(
+        ['open-ils.acq', 'open-ils.acq.lineitem_provider_attr_definition.delete'],
+        {   async: true,
+            params: [openils.User.authtoken, list[idx]],
+            oncomplete: function(r) {
+                msg = r.recv()
+                stat = msg.content();
+                /* XXX CHECH FOR EVENT */
+                openils.acq.Provider._lineitemProviderAttrDefDeleteList(list, ++idx, oncomplete);
+            }
+        }
+    );
+}
+
+openils.acq.Provider.storeCache = [];
+
+openils.acq.Provider.buildPermProviderSelector = function(perm, selector) {
+    dojo.require('dojo.data.ItemFileReadStore');
+
+    function hookupStore(store) {
+       selector.store = store;
+       selector.startup();
+    }
+
+    function buildPicker(r) {
+       var msg;
+       var providerList = [];
+       while (msg = r.recv()) {
+           var provider = msg.content();
+           providerList.push(provider);
+       }
+
+       var store = new dojo.data.ItemFileReadStore({data:acqpro.toStoreData(providerList)});
+
+       hookupStore(store);
+       openils.acq.Provider.storeCache[perm] = store;
+    }
+
+    if (openils.acq.Provider.storeCache[perm]) {
+       hookupStore(openils.acq.Provider.storeCache[perm]);
+    } else {
+       fieldmapper.standardRequest(
+           ['open-ils.acq', 'open-ils.acq.provider.org.retrieve'],
+            {   params: [openils.User.authtoken, null,
+                        {flesh_summary:1, limit_perm:perm}],
+               oncomplete: buildPicker,
+               async: true
+            }
+       )
+    }
+}
+}
diff --git a/Open-ILS/web/js/dojo/openils/editors.js b/Open-ILS/web/js/dojo/openils/editors.js
new file mode 100644 (file)
index 0000000..2b83304
--- /dev/null
@@ -0,0 +1,83 @@
+if(!dojo._hasResource["openils.editors"]){
+dojo._hasResource["openils.editors"] = true;
+dojo.provide("openils.editors");
+
+dojo.require("dojox.grid._data.dijitEditors");
+dojo.require("dojox.grid._data.editors");
+dojo.require("dijit.form.NumberSpinner");
+dojo.require('dijit.form.FilteringSelect');
+
+dojo.require("openils.widget.FundSelector");
+dojo.require("openils.widget.ProviderSelector");
+dojo.require("openils.widget.OrgUnitFilteringSelect");
+
+dojo.require("openils.acq.Fund");
+
+dojo.declare("openils.editors.NumberSpinner", dojox.grid.editors.Dijit, {
+    editorClass: "dijit.form.NumberSpinner",
+
+    getvalue: function() {
+       var e = this.editor;
+       // make sure to apply the displayed value
+       e.setDisplayedValue(e.getDisplayedValue());
+       return e.getValue();
+    },
+
+    getEditorProps: function(inDatum){
+       return dojo.mixin({}, this.cell.editorProps||{}, {
+           constraints: dojo.mixin({}, this.cell.constraints) || {},
+           value: inDatum
+       });
+    },
+});
+
+dojo.declare('openils.editors.FundSelectEditor', dojox.grid.editors.Dijit, {
+    editorClass: "openils.widget.FundSelector",
+    createEditor: function(inNode, inDatum, inRowIndex) {
+       var editor = new this.editorClass(this.getEditorProps(inDatum), inNode);
+       openils.acq.Fund.buildPermFundSelector(this.cell.perm || this.perm,
+                                        editor);
+       return editor;
+    },
+});
+
+dojo.declare('openils.editors.ProviderSelectEditor', dojox.grid.editors.Dijit, {
+    editorClass: "openils.widget.ProviderSelector",
+    createEditor: function(inNode, inDatum, inRowIndex) {
+       console.log("openils.widget.ProviderSelectEditor");
+       var editor = new this.editorClass(this.getEditorProps(inDatum), inNode);
+       openils.acq.Provider.buildPermProviderSelector(this.cell.perm || this.perm,
+                                                      editor);
+       return editor;
+    },
+});
+
+dojo.declare('openils.editors.OrgUnitSelectEditor', dojox.grid.editors.Dijit, {
+    editorClass: "openils.widget.OrgUnitFilteringSelect",
+    createEditor: function(inNode, inDatum, inRowIndex) {
+       var editor = new this.editorClass(this.getEditorProps(inDatum), inNode);
+       new openils.User().buildPermOrgSelector(this.cell.perm || this.perm, editor);
+       editor.setValue(inDatum);
+       return editor;
+    },
+});
+
+dojo.declare('openils.editors.CopyLocationSelectEditor', dojox.grid.editors.Dijit, {
+    editorClass: "dijit.form.FilteringSelect",
+    createEditor: function(inNode, inDatum, inRowIndex) {
+        dojo.require('openils.CopyLocation');
+           var editor = new this.editorClass(this.getEditorProps(inDatum), inNode);
+        openils.CopyLocation.createStore(1,  /* XXX how do we propagate arguments to the editor?? */
+            function(store) {
+                editor.store = new dojo.data.ItemFileReadStore({data:store});
+                editor.startup();
+                if(inDatum)
+                    editor.setValue(inDatum);
+            }
+        );
+           return editor;
+    },
+});
+
+}
+
diff --git a/Open-ILS/web/js/dojo/openils/widget/FundSelector.js b/Open-ILS/web/js/dojo/openils/widget/FundSelector.js
new file mode 100644 (file)
index 0000000..38442cd
--- /dev/null
@@ -0,0 +1,18 @@
+if(!dojo._hasResource["openils.widget.FundSelector"]){
+    dojo._hasResource["openils.widget.FundSelector"] = true;
+    dojo.provide("openils.widget.FundSelector");
+
+    dojo.require('openils.acq.Fund');
+    dojo.require('fieldmapper.Fieldmapper');
+    dojo.require('fieldmapper.dojoData');
+
+    /**
+     * This widget provides a specific selector for selecting
+     * a fund.
+     */
+
+    dojo.declare(
+       "openils.widget.FundSelector", [dijit.form.FilteringSelect],
+       {
+       });
+}
diff --git a/Open-ILS/web/js/dojo/openils/widget/ProviderSelector.js b/Open-ILS/web/js/dojo/openils/widget/ProviderSelector.js
new file mode 100644 (file)
index 0000000..460bf2b
--- /dev/null
@@ -0,0 +1,20 @@
+if(!dojo._hasResource["openils.widget.ProviderSelector"]){
+    dojo._hasResource["openils.widget.ProviderSelector"] = true;
+    dojo.provide("openils.widget.ProviderSelector");
+
+    dojo.require('openils.acq.Provider');
+    dojo.require('fieldmapper.Fieldmapper');
+    dojo.require('fieldmapper.dojoData');
+
+    /**
+     * This widget provides a specific selector for selecting
+     * a provider.
+     */
+
+    dojo.declare(
+       "openils.widget.ProviderSelector", [dijit.form.FilteringSelect],
+       {
+        labelAttr: 'code',
+        searchAttr: 'code'
+       });
+}
diff --git a/Open-ILS/web/js/ui/base.js b/Open-ILS/web/js/ui/base.js
new file mode 100644 (file)
index 0000000..1c3c249
--- /dev/null
@@ -0,0 +1,34 @@
+dojo.require('dijit.Dialog');
+dojo.require('fieldmapper.dojoData');
+dojo.require('openils.User');
+dojo.require('dojo.cookie');
+dojo.require('openils.CGI');
+dojo.require('openils.Event');
+
+function oilsSetupUser() {
+    var authtoken = new openils.CGI().param('ses') || dojo.cookie('ses');
+    var user;
+    if(authtoken) user = new openils.User({authtoken:authtoken});
+    if(!authtoken || openils.Event.parse(user.user)) {
+        dojo.cookie('ses', openils.User.authtoken, {expires:-1, path:'/'});
+        openils.User.authtoken = null;
+        dojo.addOnLoad(function(){oilsLoginDialog.show();});
+        return;
+    }
+    dojo.cookie('ses', authtoken, {path : oilsCookieBase});
+    openils.User.authtoken = authtoken;
+}
+
+function oilsDoLogin() {
+    var user = new openils.User();
+    user.login({
+        username: dojo.byId('oils-login-username').value,
+        passwd: dojo.byId('oils-login-password').value,
+        type: 'staff' // hardcode for now
+    });
+    dojo.cookie('ses', user.authtoken, {path : oilsCookieBase});
+    return true;
+}
+
+oilsSetupUser();
+
diff --git a/Open-ILS/web/js/ui/default/acq/common/jubgrid.js b/Open-ILS/web/js/ui/default/acq/common/jubgrid.js
new file mode 100644 (file)
index 0000000..6a0c451
--- /dev/null
@@ -0,0 +1,382 @@
+dojo.require('dojo.data.ItemFileReadStore');
+dojo.require('dijit.layout.SplitContainer');
+dojo.require('dijit.Dialog');
+dojo.require('dijit.form.FilteringSelect');
+dojo.require('dijit.form.Button');
+dojo.require('dojox.grid.Grid');
+dojo.require('dojo.date.locale');
+dojo.require('dojo.date.stamp');
+
+
+dojo.require("openils.User");
+dojo.require("openils.acq.Fund");
+dojo.require("openils.acq.Lineitem");
+dojo.require('openils.acq.Provider');
+dojo.require("openils.widget.FundSelector");
+dojo.require('openils.editors');
+dojo.require('openils.Event');
+dojo.require("openils.widget.OrgUnitFilteringSelect");
+dojo.require("fieldmapper.OrgUtils");
+
+/* put all the accessors, etc. into a local object for namespacing */
+var JUBGrid = {
+    jubGrid : null,
+    lineitems : [], // full list of lineitem objects to display 
+    getLi : function(id) { 
+        // given an ID, returns the lineitem object from the list
+        for(var i in JUBGrid.lineitems) {
+            var li = JUBGrid.lineitems[i];
+            if(li.id() == id)
+                return li;
+        }
+    },
+
+    _getMARCAttr : function(rowIndex, attr) {
+        var data = JUBGrid.jubGrid.model.getRow(rowIndex);
+        if (!data) return '';
+        return new openils.acq.Lineitem(
+            {lineitem:JUBGrid.getLi(data.id)}).findAttr(attr, 'lineitem_marc_attr_definition')
+    },
+    getJUBTitle : function(rowIndex) {
+        return JUBGrid._getMARCAttr(rowIndex, 'title');
+    },
+    getJUBAuthor : function(rowIndex) {
+        return JUBGrid._getMARCAttr(rowIndex, 'author');
+    },
+    getJUBIsbn : function(rowIndex) {
+        return JUBGrid._getMARCAttr(rowIndex, 'isbn');
+    },
+    getJUBActualPrice : function(rowIndex) {
+        var data = JUBGrid.jubGrid.model.getRow(rowIndex);
+        if (!data) return '';
+        var price = new openils.acq.Lineitem(
+            {lineitem:JUBGrid.getLi(data.id)}).getActualPrice();
+        if(price) return price.price;
+        return ''
+    },
+    getJUBEstimatedPrice : function(rowIndex) {
+        var data = JUBGrid.jubGrid.model.getRow(rowIndex);
+        if (!data) return '';
+           var price = new openils.acq.Lineitem(
+            {lineitem:JUBGrid.getLi(data.id)}).getEstimatedPrice();
+        if(price) return price.price;
+        return ''
+    },
+    getJUBPubdate : function(rowIndex) {
+        return JUBGrid._getMARCAttr(rowIndex, 'pubdate');
+    },
+    getProvider : function(rowIndex) {
+        data = JUBGrid.jubGrid.model.getRow(rowIndex);
+        if(!data || !data.provider) return;
+        return openils.acq.Provider.retrieve(data.provider).code();
+    },
+    getRecvTime : function(rowIndex) {
+        var data = JUBGrid.jubDetailGrid.model.getRow(rowIndex);
+        if (!(data && data.recv_time)) return '';
+        var date = dojo.date.stamp.fromISOString(data.recv_time);
+        return dojo.date.locale.format(date, {formatLength:'medium'});
+    },
+    getCopyLocation : function(rowIndex) {
+        var data = JUBGrid.jubDetailGrid.model.getRow(rowIndex);
+        if(!data || !data.location) return '';
+        return openils.CopyLocation.retrieve(data.location).name();
+    },
+    getLIDFundName : function(rowIndex) {
+        var data = JUBGrid.jubDetailGrid.model.getRow(rowIndex);
+        if (!data || !data.fund) return;
+        try {
+            return openils.acq.Fund.retrieve(data.fund).name();
+        } catch (evt) {
+            return data.fund;
+        }
+    },
+    getLIDFundCode : function(rowIndex) {
+        var data = JUBGrid.jubDetailGrid.model.getRow(rowIndex);
+        if (!data || !data.fund) return;
+        try {
+            return openils.acq.Fund.retrieve(data.fund).code();
+        } catch (evt) {
+            return data.fund;
+        }
+    },
+    getLIDLibName : function(rowIndex) {
+        var data = JUBGrid.jubDetailGrid.model.getRow(rowIndex);
+        if (!data || !data.owning_lib) return;
+        return fieldmapper.aou.findOrgUnit(data.owning_lib).shortname();
+    },
+
+    gridDataChanged : function(newVal, rowIdx, cellIdx) {
+        // cellIdx == -1 if you are editing a column that
+        // is not represented in the data model. Khaaaaaaan!!! 
+    },
+
+    populate : function(gridWidget, model, lineitems) {
+        for (var i in lineitems) {
+            JUBGrid.lineitems[lineitems[i].id()] = lineitems[i];
+        }
+        JUBGrid.jubGrid = gridWidget;
+        JUBGrid.jubGrid.setModel(model);
+        if(JUBGrid.showDetails) {
+            dojo.connect(gridWidget, "onRowClick", 
+                function(evt) {
+                    var jub = model.getRow(evt.rowIndex);
+                    var grid;
+
+                    JUBGrid.jubDetailGrid.lineitemID = jub.id;
+
+                    //if (jub.state == "approved") {
+                    if (false) { // need finer grained control here
+                        grid = JUBGrid.jubDetailGridLayoutReadOnly;
+                    } else {
+                        grid = JUBGrid.jubDetailGridLayout;
+                    }
+                    openils.acq.Lineitem.loadLIDGrid(
+                        JUBGrid.jubDetailGrid, 
+                        JUBGrid.jubGrid.model.getRow(evt.rowIndex).id, grid);
+                }
+            );
+        }
+        // capture changes to lineitems
+        dojo.connect(model.store, "onSet", JUBGrid.onJUBSet);
+        gridWidget.update();
+    },
+
+    approveJUB: function(evt) {
+        var list = [];
+        var selected = JUBGrid.jubGrid.selection.getSelected();
+        for (var idx = 0; idx < selected.length; idx++) {
+            var rowIdx = selected[idx];
+            JUBGrid.approveSingleJUB(JUBGrid.jubGrid.model.getRow(rowIdx));
+        }
+    },
+
+    approveSingleJUB: function(jub) {
+        var li = new openils.acq.Lineitem({lineitem:JUBGrid.getLi(jub.id)});
+        var approveStore = function(evt) {
+            if (evt) {
+                // something bad happened
+                console.log("jubgrid.js: approveJUB: error:");
+                console.dir(evt);
+                alert("Error: "+evt.desc);
+            } else {
+                var approveACQLI = function(jub, rq) {
+                    JUBGrid.jubGrid.model.store.setValue(jub, "state", "approved");
+                    JUBGrid.jubGrid.model.refresh();
+                    JUBGrid.jubGrid.update();
+                    // Reload lineitem details, read-only
+                    //openils.acq.Lineitem.loadLIDGrid(JUBGrid.jubDetailGrid, li.id(), JUBGrid.jubDetailGridLayout);
+                        //JUBGrid.jubDetailGridLayoutReadOnly);
+                };
+                JUBGrid.jubGrid.model.store.fetch({query:{id:jub.id}, onItem: approveACQLI});
+            }
+        };
+
+        li.approve(approveStore);
+    },
+
+
+    removeSelectedJUBs: function(evt) {
+
+        function deleteList(list, idx, oncomplete) {
+            if(idx >= list.length) 
+                return oncomplete();
+            fieldmapper.standardRequest([
+                'open-ils.acq',
+                'open-ils.acq.lineitem.delete'], 
+                {   async: true,
+                    params: [openils.User.authtoken, list[idx].id()],
+                    oncomplete: function(r) {
+                        var res = r.recv().content();
+                        if(openils.Event.parse(res))
+                            alert(openils.Event.parse(res));
+                        deleteList(list, ++idx, oncomplete);
+                    }
+                }
+            );
+        }
+
+        var lineitems = JUBGrid.lineitems;
+        var deleteMe = [];
+        var keepMe = [];
+        var selected = JUBGrid.jubGrid.selection.getSelected();
+
+        for(var id in lineitems) {
+            var deleted = false;
+            for(var i = 0; i < selected.length; i++) {
+                var rowIdx = selected[i];
+                   var jubid = JUBGrid.jubGrid.model.getRow(rowIdx).id;
+                if(jubid == id) {
+                   if (lineitems[id].state() == 'new') {
+                       deleteMe.push(lineitems[id]);
+                       deleted = true;
+                   } else {
+                       alert("Can not delete line item "+id+
+                             ": item is "+lineitems[id].state());
+                   }
+                }
+            }
+            if(!deleted) 
+                keepMe[id] = lineitems[id];
+        }
+
+        JUBGrid.lineitems = keepMe;
+        deleteList(deleteMe, 0, function(){
+            JUBGrid.jubGrid.model.store = 
+                new dojo.data.ItemFileReadStore({data:jub.toStoreData(keepMe)});
+            JUBGrid.jubGrid.model.refresh();
+            JUBGrid.jubGrid.update();
+        });
+    },
+
+    deleteLID: function(evt) {
+       var list =[];
+       var selected = JUBGrid.jubDetailGrid.selection.getSelected();
+       for (var idx = 0; idx < selected.length; idx++) {
+           var rowIdx = selected[idx];
+           var lid = JUBGrid.jubDetailGrid.model.getRow(rowIdx);
+           var deleteFromStore = function (evt) {
+
+               if (evt) {
+                   // something bad happened
+                   alert("Error: "+evt.desc);
+               } else {
+                   var deleteItem = function(item, rq) {
+                       JUBGrid.jubDetailGrid.model.store.deleteItem(item);
+                   };
+                   var updateCount = function(item) {
+                       var newval = JUBGrid.jubGrid.model.store.getValue(item, "item_count");
+                       JUBGrid.jubGrid.model.store.setValue(item, "item_count", newval-1);
+                       JUBGrid.jubGrid.update();
+                   };
+
+                   JUBGrid.jubDetailGrid.model.store.fetch({query:{id:lid.id},
+                                                            onItem: deleteItem});
+                   JUBGrid.jubGrid.model.store.fetch({query:{id:JUBGrid.jubDetailGrid.lineitemID},
+                                                      onItem: updateCount});
+               }
+               JUBGrid.jubDetailGrid.update(); 
+           };
+
+           openils.acq.Lineitem.deleteLID(lid.id, deleteFromStore);
+       }
+    },
+
+    createLID: function(fields) {
+        fields['lineitem'] = JUBGrid.jubDetailGrid.lineitemID;
+        var addToStore = function (lid) {
+            JUBGrid.jubDetailGrid.model.store.newItem(acqlid.toStoreData([lid]).items[0]);
+            JUBGrid.jubDetailGrid.refresh();
+            JUBGrid.jubGrid.update();
+            JUBGrid.jubGrid.refresh();
+        }
+        openils.acq.Lineitem.createLID(fields, addToStore);
+    },
+
+    receiveLID: function(evt) {
+           var list =[];
+           var selected = JUBGrid.jubDetailGrid.selection.getSelected();
+       for (var idx = 0; idx < selected.length; idx++) {
+           var rowIdx = selected[idx];
+           var lid = JUBGrid.jubDetailGrid.model.getRow(rowIdx);
+            list.push(lid.id);
+        }
+        if(lid != null) { // is at least one selected?
+            JUBGrid._receiveLIDList(list, 0, 
+                function() {
+                    delete openils.acq.Lineitem.ModelCache[lid.lineitem];
+                    openils.acq.Lineitem.loadLIDGrid(
+                        JUBGrid.jubDetailGrid, lid.lineitem, JUBGrid.jubDetailGridLayout);
+                }
+            );
+        }
+    },
+
+    // loop through the list of LIDs and mark them as received
+    _receiveLIDList: function(list, idx, callback) {
+        if(idx >= list.length)
+            return callback();
+        fieldmapper.standardRequest(
+            ['open-ils.acq', 'open-ils.acq.lineitem_detail.receive'],
+            {   asnync: true,
+                params: [openils.User.authtoken, list[idx++]],
+                oncomplete: function(r) {
+                    var res = r.recv().content();
+                    if(e = openils.Event.parse(res))
+                        return alert(e);
+                    JUBGrid._receiveLIDList(list, idx, callback);
+                }
+            }
+        );
+    },
+
+
+    // called when a lineitem is edited
+    onJUBSet: function (griditem, attr, oldVal,newVal) {
+        var item;
+
+        var updateDone = function(r) {
+            var stat = r.recv().content();
+            if(e = openils.Event.parse(stat)) 
+                console.dir(e);
+        };
+
+        // after an attribute has been updated
+        var attrUpdateDone = function(r, attr) {
+            var res = r.recv().content();
+            if(e = openils.Event.parse(res))
+                return alert(e);
+
+            var oldVal = new openils.acq.Lineitem(
+                {lineitem:item}).findAttr(attr, 'lineitem_local_attr_definition');
+
+            if(oldVal) {
+                // if this attr already exists on the object, just update the value
+                for(var i = 0; i < item.attributes().length; i++) {
+                    var attrobj = item.attributes()[i];
+                    if(attrobj.attr_type() == 'lineitem_local_attr_definition' && attrobj.attr_name() == attr) {
+                        attrobj.attr_value(newVal);
+                    }
+                }
+            } else {
+                // if this is a new attribute, create a new object to match reality
+                liad = new acqlia();
+                liad.attr_type('lineitem_local_attr_definition');
+                liad.attr_value(newVal);
+                liad.attr_name(attr);
+                liad.id(res);
+                item.attributes().push(liad);
+            }
+        }
+
+        if (oldVal == newVal) {
+            return;
+        }
+
+        item = JUBGrid.lineitems[griditem.id];
+        if (attr == "provider") {
+            if(newVal == '') 
+                newVal = null;
+            item.provider(newVal);
+
+        } else if(attr == 'estimated_price' || attr == 'actual_price') {
+            fieldmapper.standardRequest(
+                ['open-ils.acq', 'open-ils.acq.lineitem_local_attr.set'],
+                {   async: true,
+                    params: [openils.User.authtoken, item.id(), attr, newVal],
+                    oncomplete: function(r) {attrUpdateDone(r, attr); }
+                }
+            );
+        } else {
+            //alert("Unexpected attr in Picklist.onSet: '"+attr+"'");
+            return;
+        }
+
+        fieldmapper.standardRequest(
+            ["open-ils.acq", "open-ils.acq.lineitem.update"],
+            {params: [openils.User.authtoken, item],
+             oncomplete: updateDone
+            }
+        );
+    },
+};
+
diff --git a/Open-ILS/web/js/ui/default/acq/financial/list_currency_types.js b/Open-ILS/web/js/ui/default/acq/financial/list_currency_types.js
new file mode 100644 (file)
index 0000000..f548788
--- /dev/null
@@ -0,0 +1,25 @@
+dojo.require("dijit.Dialog");
+dojo.require('dijit.form.Button');
+dojo.require('dojox.grid.Grid');
+dojo.require('openils.acq.CurrencyType');
+dojo.require('openils.Event');
+dojo.require('openils.Util');
+dojo.require('fieldmapper.dojoData');
+
+var currencyTypes = [];
+
+function loadCTypesGrid() {
+    openils.acq.CurrencyType.fetchAll(
+        function(types) {
+            var store = new dojo.data.ItemFileReadStore(
+                {data:acqct.toStoreData(types, 'code', {identifier:'code'})});
+            var model = new dojox.grid.data.DojoData(null, store, 
+                {rowsPerPage: 20, clientSort: true, query:{code:'*'}});
+            currencyTypeListGrid.setModel(model);
+            currencyTypeListGrid.update();
+        }
+    );
+}
+
+
+openils.Util.addOnLoad(loadCTypesGrid);
diff --git a/Open-ILS/web/js/ui/default/acq/financial/list_funding_sources.js b/Open-ILS/web/js/ui/default/acq/financial/list_funding_sources.js
new file mode 100644 (file)
index 0000000..d773f0b
--- /dev/null
@@ -0,0 +1,35 @@
+dojo.require("dijit.Dialog");
+dojo.require("dijit.form.FilteringSelect");
+dojo.require('openils.acq.FundingSource');
+dojo.require('openils.acq.CurrencyType');
+dojo.require('openils.widget.OrgUnitFilteringSelect');
+dojo.require('dijit.form.Button');
+dojo.require('dojox.grid.Grid');
+dojo.require('openils.Event');
+dojo.require('openils.Util');
+
+function getOrgInfo(rowIndex) {
+    data = fundingSourceListGrid.model.getRow(rowIndex);
+    if(!data) return;
+    return fieldmapper.aou.findOrgUnit(data.owner).shortname();
+}
+
+function getBalanceInfo(rowIndex) {
+    data = fundingSourceListGrid.model.getRow(rowIndex);
+    if(!data) return;
+    return new String(openils.acq.FundingSource.cache[data.id].summary().balance);
+}
+
+function loadFSGrid() {
+    openils.acq.FundingSource.createStore(
+        function(storeData) {
+            var store = new dojo.data.ItemFileReadStore({data:storeData});
+            var model = new dojox.grid.data.DojoData(null, store, 
+                {rowsPerPage: 20, clientSort: true, query:{id:'*'}});
+            fundingSourceListGrid.setModel(model);
+            fundingSourceListGrid.update();
+        }
+    );
+}
+
+openils.Util.addOnLoad(loadFSGrid);
diff --git a/Open-ILS/web/js/ui/default/acq/financial/list_funds.js b/Open-ILS/web/js/ui/default/acq/financial/list_funds.js
new file mode 100644 (file)
index 0000000..43acbb5
--- /dev/null
@@ -0,0 +1,63 @@
+dojo.require("dijit.Dialog");
+dojo.require("dijit.form.FilteringSelect");
+dojo.require('dijit.form.Button');
+dojo.require('dojox.grid.Grid');
+
+dojo.require('openils.widget.OrgUnitFilteringSelect');
+dojo.require('openils.acq.CurrencyType');
+dojo.require('openils.Event');
+dojo.require('openils.Util');
+dojo.require('openils.acq.Fund');
+
+function getOrgInfo(rowIndex) {
+    data = fundListGrid.model.getRow(rowIndex);
+    if(!data) return;
+    return fieldmapper.aou.findOrgUnit(data.org).shortname();
+}
+
+function getBalanceInfo(rowIndex) {
+    data = fundListGrid.model.getRow(rowIndex);
+    if(!data) return;
+    return new String(openils.acq.Fund.cache[data.id].summary().combined_balance);
+}
+
+
+function loadFundGrid() {
+    openils.acq.Fund.createStore(
+        function(storeData) {
+            var store = new dojo.data.ItemFileReadStore({data:storeData});
+            var model = new dojox.grid.data.DojoData(null, store, 
+                {rowsPerPage: 20, clientSort: true, query:{id:'*'}});
+            fundListGrid.setModel(model);
+            fundListGrid.update();
+
+            var yearStore = {identifier:'year', name:'year', items:[]};
+
+            var added = [];
+            for(var i = 0; i < storeData.items.length; i++) {
+                var year = storeData.items[i].year;
+                if(added.indexOf(year) == -1) {
+                    yearStore.items.push({year:year});
+                    added.push(year);
+                }
+            }
+            yearStore.items = yearStore.items.sort().reverse();
+            fundFilterYearSelect.store = new dojo.data.ItemFileReadStore({data:yearStore});
+            var today = new Date().getFullYear().toString();
+            fundFilterYearSelect.setValue((added.indexOf(today != -1)) ? today : added[0]);
+        }
+    );
+}
+
+function filterGrid() {
+    var year = fundFilterYearSelect.getValue();
+    if(year) 
+        fundListGrid.model.query = {year:year};
+    else
+        fundListGrid.model.query = {id:'*'};
+    fundListGrid.model.refresh();
+    fundListGrid.update();
+}
+
+openils.Util.addOnLoad(loadFundGrid);
+
diff --git a/Open-ILS/web/js/ui/default/acq/financial/list_providers.js b/Open-ILS/web/js/ui/default/acq/financial/list_providers.js
new file mode 100644 (file)
index 0000000..cf41b39
--- /dev/null
@@ -0,0 +1,35 @@
+dojo.require("dijit.Dialog");
+dojo.require("dijit.form.FilteringSelect");
+dojo.require('dijit.form.Button');
+dojo.require('dojox.grid.Grid');
+
+dojo.require('openils.acq.CurrencyType');
+dojo.require('openils.Event');
+dojo.require('openils.Util');
+dojo.require('openils.acq.Provider');
+dojo.require("fieldmapper.OrgUtils");
+dojo.require('openils.widget.OrgUnitFilteringSelect');
+
+function getOrgInfo(rowIndex) {
+    data = providerListGrid.model.getRow(rowIndex);
+    if(!data) return;
+    return fieldmapper.aou.findOrgUnit(data.owner).shortname();
+}
+
+function loadProviderGrid() {
+    openils.acq.Provider.createStore(
+        function(storeData) {
+            var store = new dojo.data.ItemFileReadStore({data:storeData});
+            var model = new dojox.grid.data.DojoData(null, store, 
+                {rowsPerPage: 20, clientSort: true, query:{id:'*'}});
+            providerListGrid.setModel(model);
+            providerListGrid.update();
+        }
+    );
+}
+function createProvider(fields) {
+    openils.acq.Provider.create(fields, function(){loadProviderGrid()});
+}
+
+
+openils.Util.addOnLoad(loadProviderGrid);
diff --git a/Open-ILS/web/js/ui/default/acq/financial/view_fund.js b/Open-ILS/web/js/ui/default/acq/financial/view_fund.js
new file mode 100644 (file)
index 0000000..1f73b44
--- /dev/null
@@ -0,0 +1,86 @@
+dojo.require("dijit.Dialog");
+dojo.require('dijit.form.FilteringSelect');
+dojo.require('dijit.layout.TabContainer');
+dojo.require('dijit.layout.ContentPane');
+dojo.require('dojox.grid.Grid');
+
+dojo.require("fieldmapper.OrgUtils");
+dojo.require('openils.acq.Fund');
+dojo.require('openils.acq.FundingSource');
+dojo.require('openils.Event');
+dojo.require('openils.User');
+dojo.require('openils.Util');
+
+var fund = null;
+
+function getSummaryInfo(rowIndex) {
+    return new String(fund.summary()[this.field]);
+}
+
+function createAllocation(fields) {
+    fields.fund = fundID;
+    if(isNaN(fields.percent)) fields.percent = null;
+    if(isNaN(fields.amount)) fields.amount = null;
+    //openils.acq.Fund.createAllocation(fields, resetPage);
+    openils.acq.Fund.createAllocation(fields, 
+        function(r){location.href = location.href;});
+}
+
+function getOrgInfo(rowIndex) {
+    data = fundGrid.model.getRow(rowIndex);
+    if(!data) return;
+    return fieldmapper.aou.findOrgUnit(data.org).shortname();
+}
+
+function getXferDest(rowIndex) {
+    data = this.grid.model.getRow(rowIndex);
+    if(!(data && data.xfer_destination)) return '';
+    return data.xfer_destination;
+}
+
+function loadFundGrid() {
+    var store = new dojo.data.ItemFileReadStore({data:acqf.toStoreData([fund])});
+    var model = new dojox.grid.data.DojoData(
+        null, store, {rowsPerPage: 20, clientSort: true, query:{id:'*'}});
+    fundGrid.setModel(model);
+    fundGrid.update();
+}
+
+function loadAllocationGrid() {
+    if(fundAllocationGrid.isLoaded) return;
+    var store = new dojo.data.ItemFileReadStore({data:acqfa.toStoreData(fund.allocations())});
+    var model = new dojox.grid.data.DojoData(
+        null, store, {rowsPerPage: 20, clientSort: true, query:{id:'*'}});
+    fundAllocationGrid.setModel(model);
+    fundAllocationGrid.update();
+    fundAllocationGrid.isLoaded = true;
+}
+
+function loadDebitGrid() {
+    if(fundDebitGrid.isLoaded) return;
+    var store = new dojo.data.ItemFileReadStore({data:acqfa.toStoreData(fund.debits())});
+    var model = new dojox.grid.data.DojoData(
+        null, store, {rowsPerPage: 20, clientSort: true, query:{id:'*'}});
+    fundDebitGrid.setModel(model);
+    fundDebitGrid.update();
+    fundDebitGrid.isLoaded = true;
+}
+
+function fetchFund() {
+    fieldmapper.standardRequest(
+        ['open-ils.acq', 'open-ils.acq.fund.retrieve'],
+        {   async: true,
+            params: [
+                openils.User.authtoken, fundID, 
+                {flesh_summary:1, flesh_allocations:1, flesh_debits:1} 
+                /* TODO grab allocations and debits only on as-needed basis */
+            ],
+            oncomplete: function(r) {
+                fund = r.recv().content();
+                loadFundGrid(fund);
+            }
+        }
+    );
+}
+
+openils.Util.addOnLoad(fetchFund);
diff --git a/Open-ILS/web/js/ui/default/acq/financial/view_funding_source.js b/Open-ILS/web/js/ui/default/acq/financial/view_funding_source.js
new file mode 100644 (file)
index 0000000..8f6a31e
--- /dev/null
@@ -0,0 +1,100 @@
+dojo.require("dijit.Dialog");
+dojo.require('dijit.layout.TabContainer');
+dojo.require('dijit.layout.ContentPane');
+dojo.require("dijit.form.FilteringSelect");
+dojo.require("dijit.form.Textarea");
+dojo.require("dijit.form.CurrencyTextBox");
+dojo.require('dojox.grid.Grid');
+
+dojo.require("fieldmapper.OrgUtils");
+dojo.require('openils.acq.FundingSource');
+dojo.require('openils.acq.Fund');
+dojo.require('openils.Event');
+dojo.require('openils.Util');
+    
+var ses = new OpenSRF.ClientSession('open-ils.acq');
+var fundingSource = null;
+
+function resetPage() {
+    fundingSource = null;
+    fsCreditGrid.isLoaded = false;
+    fsAllocationGrid.isLoaded = false;
+    loadFS();
+}
+
+/** creates a new funding_source_credit from the dialog ----- */
+function applyFSCredit(fields) {
+    fields.funding_source = fundingSourceID;
+    openils.acq.FundingSource.createCredit(fields, resetPage);
+}
+
+function applyFSAllocation(fields) {
+    fields.funding_source = fundingSourceID;
+    if(isNaN(fields.percent)) fields.percent = null;
+    if(isNaN(fields.amount)) fields.amount = null;
+    openils.acq.Fund.createAllocation(fields, resetPage);
+}
+
+/** fetch the fleshed funding source ----- */
+function loadFS() {
+    var req = ses.request(
+        'open-ils.acq.funding_source.retrieve', 
+        openils.User.authtoken, fundingSourceID, 
+        {flesh_summary:1, flesh_credits:1,flesh_allocations:1}
+    );
+
+    req.oncomplete = function(r) {
+        var msg = req.recv();
+        fundingSource = msg.content();
+        var evt = openils.Event.parse(fundingSource);
+        if(evt) {
+            alert(evt);
+            return;
+        }
+        loadFSGrid();
+    }
+    req.send();
+}
+
+/** Some grid rendering accessor functions ----- */
+function getOrgInfo(rowIndex) {
+    data = fundingSourceGrid.model.getRow(rowIndex);
+    if(!data) return;
+    return fieldmapper.aou.findOrgUnit(data.owner).shortname();
+}
+
+function getSummaryInfo(rowIndex) {
+    return new String(fundingSource.summary()[this.field]);
+}
+
+/** builds the credits grid ----- */
+function loadFSGrid() {
+    if(!fundingSource) return;
+    var store = new dojo.data.ItemFileReadStore({data:acqfs.toStoreData([fundingSource])});
+    var model = new dojox.grid.data.DojoData(null, store, {rowsPerPage: 20, clientSort: true, query:{id:'*'}});
+    fundingSourceGrid.setModel(model);
+    fundingSourceGrid.update();
+}
+
+
+/** builds the credits grid ----- */
+function loadCreditGrid() {
+    if(fsCreditGrid.isLoaded) return;
+    var store = new dojo.data.ItemFileReadStore({data:acqfa.toStoreData(fundingSource.credits())});
+    var model = new dojox.grid.data.DojoData(null, store, {rowsPerPage: 20, clientSort: true, query:{id:'*'}});
+    fsCreditGrid.setModel(model);
+    fsCreditGrid.update();
+    fsCreditGrid.isLoaded = true;
+}
+
+/** builds the allocations grid ----- */
+function loadAllocationGrid() {
+    if(fsAllocationGrid.isLoaded) return;
+    var store = new dojo.data.ItemFileReadStore({data:acqfa.toStoreData(fundingSource.allocations())});
+    var model = new dojox.grid.data.DojoData(null, store, {rowsPerPage: 20, clientSort: true, query:{id:'*'}});
+    fsAllocationGrid.setModel(model);
+    fsAllocationGrid.update();
+    fsAllocationGrid.isLoaded = true;
+}
+
+openils.Util.addOnLoad(loadFS);
diff --git a/Open-ILS/web/js/ui/default/acq/financial/view_provider.js b/Open-ILS/web/js/ui/default/acq/financial/view_provider.js
new file mode 100644 (file)
index 0000000..181aff8
--- /dev/null
@@ -0,0 +1,99 @@
+dojo.require("dijit.Dialog");
+dojo.require('dijit.layout.TabContainer');
+dojo.require('dijit.layout.ContentPane');
+dojo.require('dijit.form.FilteringSelect');
+dojo.require('dojox.grid.Grid');
+dojo.require("fieldmapper.OrgUtils");
+dojo.require('openils.acq.Provider');
+dojo.require('openils.Event');
+dojo.require('openils.User');
+dojo.require('openils.Util');
+
+var provider = null;
+var marcRegex = /^\/\/\*\[@tag="(\d+)"]\/\*\[@code="(\w)"]$/;
+
+function getOrgInfo(rowIndex) {
+    data = providerGrid.model.getRow(rowIndex);
+    if(!data) return;
+    return fieldmapper.aou.findOrgUnit(data.owner).shortname();
+}
+
+function getTag(rowIdx) {
+    data = padGrid.model.getRow(rowIdx);
+    if(!data) return;
+    return data.xpath.replace(marcRegex, '$1');
+}
+
+function getSubfield(rowIdx) {
+    data = padGrid.model.getRow(rowIdx);
+    if(!data) return;
+    return data.xpath.replace(marcRegex, '$2');
+}
+
+
+function loadProviderGrid() {
+    var store = new dojo.data.ItemFileReadStore({data:acqpro.toStoreData([provider])});
+    var model = new dojox.grid.data.DojoData(
+        null, store, {rowsPerPage: 20, clientSort: true, query:{id:'*'}});
+    providerGrid.setModel(model);
+    providerGrid.update();
+}
+
+function loadPADGrid() {
+    openils.acq.Provider.retrieveLineitemProviderAttrDefs(providerId, 
+        function(attrs) {
+            var store = new dojo.data.ItemFileReadStore({data:acqlipad.toStoreData(attrs)});
+            var model = new dojox.grid.data.DojoData(
+                null, store, {rowsPerPage: 20, clientSort: true, query:{id:'*'}});
+            padGrid.setModel(model);
+            padGrid.update();
+        }
+    );
+}
+
+
+function fetchProvider() {
+    fieldmapper.standardRequest(
+        ['open-ils.acq', 'open-ils.acq.provider.retrieve'],
+        {   async: true,
+            params: [ openils.User.authtoken, providerId ],
+            oncomplete: function(r) {
+                provider = r.recv().content();
+                loadProviderGrid(provider);
+            }
+        }
+    );
+}
+
+function createOrderRecordField(fields) {
+    fields.provider = providerId;
+    if(!fields.xpath) 
+        fields.xpath = '//*[@tag="'+fields.tag+'"]/*[@code="'+fields.subfield+'"]';
+    delete fields.tag;
+    delete fields.subfield;
+    openils.acq.Provider.createLineitemProviderAttrDef(fields, 
+        function(id) {
+            loadPADGrid();
+        }
+    );
+}
+
+function setORDesc() {
+    var code = dijit.byId('oils-acq-provider-or-code');
+    var desc = dijit.byId('oils-acq-provider-or-desc');
+    desc.setValue(code.getDisplayedValue());
+}
+
+function deleteORDataFields() {
+    var list = []
+    var selected = padGrid.selection.getSelected();
+    for(var idx = 0; idx < selected.length; idx++) 
+        list.push(padGrid.model.getRow(selected[idx]).id);
+    openils.acq.Provider.lineitemProviderAttrDefDeleteList(
+        list, function(){loadPADGrid();});
+}
+
+
+openils.Util.addOnLoad(fetchProvider);
+
+
diff --git a/Open-ILS/web/js/ui/default/acq/picklist/bib_search.js b/Open-ILS/web/js/ui/default/acq/picklist/bib_search.js
new file mode 100644 (file)
index 0000000..51e6bcc
--- /dev/null
@@ -0,0 +1,205 @@
+dojo.require('dojox.form.CheckedMultiSelect');
+dojo.require('fieldmapper.Fieldmapper');
+dojo.require('dijit.ProgressBar');
+dojo.require('dijit.form.Form');
+dojo.require('dijit.form.TextBox');
+dojo.require('dijit.form.NumberSpinner');
+dojo.require('openils.Event');
+dojo.require('openils.acq.Picklist');
+dojo.require('openils.acq.Lineitem');
+dojo.require('openils.User');
+dojo.require('openils.Util');
+
+var searchFields = [];
+var resultPicklist;
+var resultLIs;
+var selectedLIs;
+var recvCount = 0;
+var sourceCount = 0; // how many sources are we searching
+var user = new openils.User();
+var searchLimit = 10;
+
+function drawForm() {
+
+    var sources = fieldmapper.standardRequest(
+        ['open-ils.search', 'open-ils.search.z3950.retrieve_services'], 
+        [user.authtoken]
+    );
+
+    openils.Event.parse_and_raise(sources);
+
+    for(var name in sources) {
+        source = sources[name];
+        bibSourceSelect.addOption(name, name+':'+source.host);
+        for(var attr in source.attrs) 
+            if(!attr.match(/^#/)) // xml comment nodes
+                searchFields.push(source.attrs[attr]);
+    }
+
+    searchFields = searchFields.sort(
+        function(a,b) {
+            if(a.label < b.label) 
+                return -1;
+            if(a.label > b.label) 
+                return 1;
+            return 0;
+        }
+    );
+
+    var tbody = dojo.byId('oils-acq-search-fields-tbody');
+    var tmpl = tbody.removeChild(dojo.byId('oils-acq-search-fields-template'));
+
+    for(var f in searchFields) {
+        var field = searchFields[f];
+        if(dijit.byId('text_input_'+field.name)) continue;
+        var row = tmpl.cloneNode(true);
+        tbody.insertBefore(row, dojo.byId('oils-acq-seach-fields-count-row'));
+        var labelCell = dojo.query('[name=label]', row)[0];
+        var inputCell = dojo.query('[name=input]', row)[0];
+        labelCell.appendChild(document.createTextNode(field.label));
+        input = new dijit.form.TextBox({name:field.name, label:field.label, id:'text_input_'+field.name});
+        inputCell.appendChild(input.domNode);
+    }
+}
+
+function doSearch(values) {
+    dojo.style('searchProgress', 'visibility', 'visible');
+    searchProgress.update({progress: 0});
+
+    search = {
+        service : [],
+        username : [],
+        password : [],
+        search : {},
+        limit : values.limit,
+        offset : searchOffset
+    };
+    searchLimit = values.limit;
+    delete values.limit;
+
+    var selected = bibSourceSelect.getValue();
+    for(var i = 0; i < selected.length; i++) {
+        search.service.push(selected[i]);
+        search.username.push('');
+        search.password.push('');
+        sourceCount++;
+    }
+
+    for(var v in values) {
+        if(values[v]) {
+            var input = dijit.byId('text_input_'+v);
+            search.search[v] = values[v];
+        }
+    }
+
+    fieldmapper.standardRequest(
+        ['open-ils.acq', 'open-ils.acq.picklist.search.z3950'],
+        {   async: true,
+            params: [user.authtoken, search],
+            onresponse: handleResult,
+        }
+    );
+}
+
+function handleResult(r) {
+    var result = r.recv().content();
+    if(openils.Event.parse(result)) {
+        alert(openils.Event.parse(result));
+        dojo.style('searchProgress', 'visibility', 'hidden');
+        return;
+    }
+    if(result.complete)
+        return viewResults(result.picklist_id);
+    searchProgress.update({maximum: result.total, progress: result.progress});
+}
+
+function viewResults(plId) {
+    var plist = new openils.acq.Picklist(plId,
+        function(model) {
+            resultLIs = plist._items;
+            dojo.style('oils-acq-pl-search-results', 'visibility', 'visible');
+            JUBGrid.populate(plResultGrid, model, plist._items);
+        },
+        {flesh_attrs:1, clear_marc:1, limit: searchLimit}
+    );
+    resultPicklist = plist._plist;
+}
+
+function loadPLSelect() {
+    var plList = [];
+    function handleResponse(r) {
+        plList.push(r.recv().content());
+    }
+    var method = 'open-ils.acq.picklist.user.retrieve';
+    fieldmapper.standardRequest(
+        ['open-ils.acq', method],
+        {   async: true,
+            params: [openils.User.authtoken],
+            onresponse: handleResponse,
+            oncomplete: function() {
+                plAddExistingSelect.store = 
+                    new dojo.data.ItemFileReadStore({data:acqpl.toStoreData(plList)});
+                plAddExistingSelect.setValue();
+            }
+        }
+    );
+}
+
+
+function saveResults(values) {
+    selectedLIs = resultLIs;
+
+    if(values.which == 'selected') {
+        selectedLIs = [];
+        var selected = plResultGrid.selection.getSelected();
+        for(var idx = 0; idx < selected.length; idx++) {
+            var rowIdx = selected[idx];
+            var id = plResultGrid.model.getRow(rowIdx).id;
+            for(var i = 0; i < resultLIs.length; i++) {
+                var pl = resultLIs[i];
+                if(pl.id() == id) {
+                    selectedLIs.push(pl);
+                }
+            }
+        }
+    }
+        
+    if(values.new_name && values.new_name != '') {
+        // save selected lineitems to a new picklist
+        if(values.which = 'selected') {
+            openils.acq.Picklist.create(
+                {name: values.new_name}, 
+                function(id) {
+                    updateLiList(id, selectedLIs, 0, 
+                        function(){location.href = 'view/' + id});
+                }
+            );
+        }  else {
+            // save all == change the name of the results picklist
+            resultPicklist.name(values.new_name); 
+            openils.acq.Picklist.update(resultPicklist,
+                function(stat) {
+                    location.href = 'view/' + resultPicklist.id(); 
+                }
+            );
+        }
+    } else if(values.existing_pl) {
+        // update lineitems to use an existing picklist
+        updateLiList(values.existing_pl, selectedLIs, 0, 
+            function(){location.href = 'view/' + values.existing_pl});
+    }
+}
+
+function updateLiList(pl, list, idx, oncomplete) {
+    if(idx >= list.length)
+        return oncomplete();
+    var li = selectedLIs[idx];
+    li.picklist(pl);
+    new openils.acq.Lineitem({lineitem:li}).update(
+        function(r) {
+            updateLiList(pl, list, ++idx, oncomplete);
+        }
+    );
+}
+
+openils.Util.addOnLoad(drawForm);
diff --git a/Open-ILS/web/js/ui/default/acq/picklist/view_list.js b/Open-ILS/web/js/ui/default/acq/picklist/view_list.js
new file mode 100644 (file)
index 0000000..ca25741
--- /dev/null
@@ -0,0 +1,81 @@
+dojo.require('dojox.grid.Grid');
+dojo.require('dijit.Dialog');
+dojo.require('dijit.form.Button');
+dojo.require('dijit.form.TextBox');
+dojo.require('dijit.form.Button');
+dojo.require('openils.acq.Picklist');
+dojo.require('openils.Util');
+
+var plList = [];
+var listAll = false;
+
+function makeGridFromList() {
+    var store = new dojo.data.ItemFileReadStore({data:acqpl.toStoreData(plList)});
+    var model = new dojox.grid.data.DojoData(null, store, 
+        {rowsPerPage: 20, clientSort: true, query:{id:'*'}});
+    plListGrid.setModel(model);
+    plListGrid.update();
+}
+
+
+function loadGrid() {
+    var method = 'open-ils.acq.picklist.user.retrieve.atomic';
+    if(listAll)
+        method = method.replace(/user/, 'user.all');
+
+    fieldmapper.standardRequest(
+        ['open-ils.acq', method],
+        {   async: true,
+            params: [openils.User.authtoken, 
+                {flesh_lineitem_count:1, flesh_username:1}],
+            oncomplete: function(r) {
+                var resp = r.recv().content();
+                if(e = openils.Event.parse(resp))
+                    return alert(e);
+                plList = resp;
+                makeGridFromList();
+            }
+        }
+    );
+}
+
+function createPL(fields) {
+    if(fields.name == '') return;
+    openils.acq.Picklist.create(fields,
+        function(plId) {
+            fieldmapper.standardRequest(
+                ['open-ils.acq', 'open-ils.acq.picklist.retrieve'],
+                {   async: true,
+                    params: [openils.User.authtoken, plId,
+                        {flesh_lineitem_count:1, flesh_username:1}],
+                    oncomplete: function(r) {
+                        var pl = r.recv().content();
+                        plList.push(pl);
+                        makeGridFromList();
+                    }
+                }
+            );
+        }
+    );
+}
+
+function deleteFromGrid() {
+    var list = []
+    var selected = plListGrid.selection.getSelected();
+    for(var idx = 0; idx < selected.length; idx++) {
+        var rowIdx = selected[idx];
+        var id = plListGrid.model.getRow(rowIdx).id;
+        for(var i = 0; i < plList.length; i++) {
+            var pl = plList[i];
+            if(pl.id() == id && pl.owner() == new openils.User().user.usrname()) {
+                list.push(id);
+                plList = (plList.slice(0, i) || []).concat(plList.slice(i+1, plList.length) || []);
+            }
+        }
+    }
+    openils.acq.Picklist.deleteList(list, function() { makeGridFromList(); });
+}
+
+openils.Util.addOnLoad(loadGrid);
+
+
diff --git a/Open-ILS/web/js/ui/default/acq/po/li_search.js b/Open-ILS/web/js/ui/default/acq/po/li_search.js
new file mode 100644 (file)
index 0000000..d099ed0
--- /dev/null
@@ -0,0 +1,189 @@
+dojo.require('fieldmapper.Fieldmapper');
+dojo.require('dijit.ProgressBar');
+dojo.require('dijit.form.Form');
+dojo.require('dijit.form.TextBox');
+dojo.require('dijit.form.CheckBox');
+dojo.require('dijit.form.FilteringSelect');
+dojo.require('dijit.form.Button');
+dojo.require("dijit.Dialog");
+dojo.require('openils.Event');
+dojo.require('openils.Util');
+dojo.require('openils.acq.Lineitem');
+dojo.require('openils.acq.Provider');
+dojo.require('openils.acq.PO');
+dojo.require('openils.widget.OrgUnitFilteringSelect');
+
+var recvCount = 0;
+var createAssetsSelected = false;
+var createDebitsSelected = false;
+
+var lineitems = [];
+
+function drawForm() {
+    buildProviderSelect(providerSelector);
+}
+
+function buildProviderSelect(sel, oncomplete) {
+    openils.acq.Provider.createStore(
+        function(store) {
+            sel.store = new dojo.data.ItemFileReadStore({data:store});
+            if(oncomplete)
+                oncomplete();
+        },
+        'MANAGE_PROVIDER'
+    );
+}
+
+var liReceived;
+function doSearch(values) {
+    var search = {};
+    for(var v in values) {
+        var val = values[v];
+        if(val != null && val != '')
+            search[v] = val;
+    }
+
+    if(values.state == 'approved')
+        dojo.style('oils-acq-li-search-po-create', 'visibility', 'visible');
+    else
+        dojo.style('oils-acq-li-search-po-create', 'visibility', 'hidden');
+
+    //search = [search, {limit:searchLimit, offset:searchOffset}];
+    search = [search, {}];
+    options = {clear_marc:1, flesh_attrs:1};
+
+    liReceived = 0;
+    lineitems = [];
+    dojo.style('searchProgress', 'visibility', 'visible');
+    fieldmapper.standardRequest(
+        ['open-ils.acq', 'open-ils.acq.lineitem.search'],
+        {   async: true,
+            params: [openils.User.authtoken, search, options],
+            onresponse: handleResult,
+            oncomplete: viewList
+        }
+    );
+}
+
+function handleResult(r) {
+    var result = r.recv().content();
+    searchProgress.update({maximum: searchLimit, progress: ++liReceived});
+    lineitems.push(result);
+}
+
+function viewList() {
+    dojo.style('searchProgress', 'visibility', 'hidden');
+    dojo.style('oils-acq-li-search-result-grid', 'visibility', 'visible');
+    var store = new dojo.data.ItemFileWriteStore(
+        {data:jub.toStoreData(lineitems, null, 
+            {virtualFields:['estimated_price', 'actual_price']})});
+    var model = new dojox.grid.data.DojoData(
+        null, store, {rowsPerPage: 20, clientSort: true, query:{id:'*'}});
+    JUBGrid.populate(liGrid, model, lineitems);
+}
+
+function createPOFromLineitems(fields) {
+    var po = new acqpo();
+    po.provider(newPOProviderSelector.getValue());
+    createAssetsSelected = fields.create_assets;
+    createDebitsSelected = fields.create_debits;
+
+    if(fields.which == 'selected') {
+        // find the selected lineitems
+        var selected = liGrid.selection.getSelected();
+        var selList = [];
+        for(var idx = 0; idx < selected.length; idx++) {
+            var rowIdx = selected[idx];
+            var id = liGrid.model.getRow(rowIdx).id;
+            for(var i = 0; i < lineitems.length; i++) {
+                var li = lineitems[i];
+                if(li.id() == id && !li.purchase_order() && li.state() == 'approved')
+                    selList.push(lineitems[i]);
+            }
+        }
+    } else {
+        selList = lineitems;
+    }
+
+    if(selList.length == 0) return;
+
+    openils.acq.PO.create(po, 
+        function(poId) {
+            if(e = openils.Event.parse(poId)) 
+                return alert(e);
+            updateLiList(poId, selList);
+        }
+    );
+}
+
+function updateLiList(poId, selList) {
+    _updateLiList(poId, selList, 0);
+}
+
+function checkCreateDebits(poId) {
+    if(!createDebitsSelected)
+        return viewPO(poId);
+    fieldmapper.standardRequest(
+        ['open-ils.acq', 'open-ils.acq.purchase_order.debits.create'],
+        {   async: true,
+            params: [openils.User.authtoken, poId, {encumbrance:1}],
+            oncomplete : function(r) {
+                var total = r.recv().content();
+                if(e = openils.Event.parse(total))
+                    return alert(e);
+                viewPO(poId);
+            }
+        }
+    );
+}
+
+function viewPO(poId) {
+    location.href = 'view/' + poId;
+}
+
+function _updateLiList(poId, selList, idx) {
+    if(idx >= selList.length) {
+        if(createAssetsSelected)
+            return createAssets(poId);
+        else
+            return checkCreateDebits(poId);
+    }
+    var li = selList[idx];
+    li.purchase_order(poId);
+    li.state('in-process');
+    new openils.acq.Lineitem({lineitem:li}).update(
+        function(stat) {
+            _updateLiList(poId, selList, ++idx);
+        }
+    );
+}
+
+function createAssets(poId) {
+    searchProgress.update({progress: 0});
+    dojo.style('searchProgress', 'visibility', 'visible');
+
+    function onresponse(r) {
+        var stat = r.recv().content();
+        if(e = openils.Event.parse(stat))
+            return alert(e);
+        searchProgress.update({maximum: stat.total, progress: stat.progress});
+    }
+
+    function oncomplete(r) {
+        dojo.style('searchProgress', 'visibility', 'hidden');
+        checkCreateDebits(poId);
+    }
+
+    fieldmapper.standardRequest(
+        ['open-ils.acq','open-ils.acq.purchase_order.assets.create'],
+        {   async: true,
+            params: [openils.User.authtoken, poId],
+            onresponse : onresponse,
+            oncomplete : oncomplete
+        }
+    );
+}
+    
+
+openils.Util.addOnLoad(drawForm);
+
diff --git a/Open-ILS/web/js/ui/default/acq/po/search.js b/Open-ILS/web/js/ui/default/acq/po/search.js
new file mode 100644 (file)
index 0000000..acec485
--- /dev/null
@@ -0,0 +1,81 @@
+dojo.require('dijit.form.Form');
+dojo.require('dijit.form.Button');
+dojo.require('dijit.form.FilteringSelect');
+dojo.require('dijit.form.NumberTextBox');
+dojo.require('dojox.grid.Grid');
+dojo.require('openils.acq.Provider');
+dojo.require('fieldmapper.OrgUtils');
+dojo.require('dojo.date.locale');
+dojo.require('dojo.date.stamp');
+dojo.require('openils.User');
+dojo.require('openils.Util');
+dojo.require('openils.widget.OrgUnitFilteringSelect');
+
+function getOrgInfo(rowIndex) {
+    data = poGrid.model.getRow(rowIndex);
+    if(!data) return;
+    return fieldmapper.aou.findOrgUnit(data.owner).shortname();
+}
+
+function getProvider(rowIndex) {
+    data = poGrid.model.getRow(rowIndex);
+    if(!data) return;
+    return openils.acq.Provider.retrieve(data.provider).name();
+}
+
+function getPOOwner(rowIndex) {
+    data = poGrid.model.getRow(rowIndex);
+    if(!data) return;
+    return new openils.User({id:data.owner}).user.usrname();
+}
+
+function getDateTimeField(rowIndex) {
+    data = poGrid.model.getRow(rowIndex);
+    if(!data) return;
+    var date = dojo.date.stamp.fromISOString(data[this.field]);
+    return dojo.date.locale.format(date, {formatLength:'medium'});
+}
+
+function doSearch(fields) {
+    var itemList = [];
+    if(!isNaN(fields.id)) 
+        fields = {id:fields.id};
+    else
+        delete fields.id;
+
+    fieldmapper.standardRequest(
+        ['open-ils.acq', 'open-ils.acq.purchase_order.search'],
+        {
+            async:1,
+            params: [openils.User.authtoken, fields],
+            onresponse : function(r) {
+                var msg = r.recv();
+                if(msg) itemList.push(msg.content());
+            },
+            oncomplete : function(r) {
+                dojo.style('po-grid', 'visibility', 'visible');
+                var store = new dojo.data.ItemFileReadStore({data:acqpo.toStoreData(itemList)});
+                var model = new dojox.grid.data.DojoData(null, store,
+                    {rowsPerPage: 20, clientSort: true, query:{id:'*'}});
+                poGrid.setModel(model);
+                poGrid.update();
+            },
+        }
+    );
+}
+
+function loadForm() {
+
+    /* load the providers */
+    openils.acq.Provider.createStore(
+        function(store) {
+            providerSelector.store = 
+                new dojo.data.ItemFileReadStore({data:store});
+        },
+        'MANAGE_PROVIDER'
+    );
+
+    new openils.User().buildPermOrgSelector('VIEW_PURCHASE_ORDER', poSearchOrderingAgencySelect);
+}
+
+openils.Util.addOnLoad(loadForm);
diff --git a/Open-ILS/web/js/ui/default/acq/po/view_po.js b/Open-ILS/web/js/ui/default/acq/po/view_po.js
new file mode 100644 (file)
index 0000000..542a132
--- /dev/null
@@ -0,0 +1,82 @@
+dojo.require("dijit.Dialog");
+dojo.require('dijit.form.FilteringSelect');
+dojo.require('dijit.layout.TabContainer');
+dojo.require('dijit.layout.ContentPane');
+dojo.require('dojox.grid.Grid');
+dojo.require('openils.acq.PO');
+dojo.require('openils.Event');
+dojo.require('openils.User');
+dojo.require('openils.Util');
+dojo.require('fieldmapper.OrgUtils');
+dojo.require('openils.acq.Provider');
+dojo.require('openils.acq.Lineitem');
+dojo.require('dojo.date.locale');
+dojo.require('dojo.date.stamp');
+
+var PO = null;
+var lineitems = [];
+
+function getOrgInfo(rowIndex) {
+    data = poGrid.model.getRow(rowIndex);
+    if(!data) return;
+    return fieldmapper.aou.findOrgUnit(data.owner).shortname();
+}
+
+function getProvider(rowIndex) {
+    data = poGrid.model.getRow(rowIndex);
+    if(!data) return;
+    return openils.acq.Provider.retrieve(data.provider).code();
+}
+
+function getPOOwner(rowIndex) {
+    data = poGrid.model.getRow(rowIndex);
+    if(!data) return;
+    return new openils.User({id:data.owner}).user.usrname();
+}
+
+function getDateTimeField(rowIndex) {
+    data = poGrid.model.getRow(rowIndex);
+    if(!data) return;
+    var date = dojo.date.stamp.fromISOString(data[this.field]);
+    return dojo.date.locale.format(date, {formatLength:'medium'});
+}
+
+function loadPOGrid() {
+    if(!PO) return;
+    var store = new dojo.data.ItemFileReadStore({data:acqpo.toStoreData([PO])});
+    var model = new dojox.grid.data.DojoData(
+        null, store, {rowsPerPage: 20, clientSort: true, query:{id:'*'}});
+    poGrid.setModel(model);
+    poGrid.update();
+}
+
+function loadLIGrid() {
+    if(liGrid.isLoaded) return;
+
+    function load(po) {
+        lineitems = po.lineitems();
+        var store = new dojo.data.ItemFileReadStore({data:jub.toStoreData(lineitems)});
+        var model = new dojox.grid.data.DojoData(
+            null, store, {rowsPerPage: 20, clientSort: true, query:{id:'*'}});
+        JUBGrid.populate(liGrid, model, lineitems)
+    }
+
+    openils.acq.PO.retrieve(poId, load, {flesh_lineitems:1, clear_marc:1});
+    liGrid.isLoaded = true;
+}
+
+function loadPage() {
+    fetchPO();
+}
+
+function fetchPO() {
+    openils.acq.PO.retrieve(poId, 
+        function(po) {
+            PO = po;
+            loadPOGrid();
+        },
+        {flesh_lineitem_count:1}
+    );
+}
+
+openils.Util.addOnLoad(loadPage);
diff --git a/Open-ILS/web/js/ui/default/acq/receiving/process.js b/Open-ILS/web/js/ui/default/acq/receiving/process.js
new file mode 100644 (file)
index 0000000..8c7a8b8
--- /dev/null
@@ -0,0 +1,62 @@
+dojo.require('fieldmapper.Fieldmapper');
+dojo.require('dijit.ProgressBar');
+dojo.require('dijit.form.Form');
+dojo.require('dijit.form.TextBox');
+dojo.require('dijit.form.CheckBox');
+dojo.require('dijit.form.FilteringSelect');
+dojo.require('dijit.form.Button');
+dojo.require("dijit.Dialog");
+dojo.require('openils.Event');
+dojo.require('openils.Util');
+dojo.require('openils.acq.Lineitem');
+dojo.require('openils.widget.OrgUnitFilteringSelect');
+
+var lineitems = [];
+
+function drawForm() {
+    new openils.User().buildPermOrgSelector('VIEW_PURCHASE_ORDER', orderingAgencySelect);
+}
+
+var liReceived;
+function doSearch(values) {
+
+    var search = {
+        attr_values : [values.identifier],
+        po_agencies : (values.ordering_agency) ? [values.ordering_agency] : null,
+        li_states : ['in-process']
+    };
+
+    options = {clear_marc:1, flesh_attrs:1};
+    liReceived = 0;
+    dojo.style('searchProgress', 'visibility', 'visible');
+
+    fieldmapper.standardRequest(
+        ['open-ils.acq', 'open-ils.acq.lineitem.search.ident'],
+        {   async: true,
+            params: [openils.User.authtoken, search, options],
+            onresponse: handleResult,
+            oncomplete: viewList
+        }
+    );
+}
+
+var searchLimit = 10; // ?
+function handleResult(r) {
+    var result = r.recv().content();
+    searchProgress.update({maximum: searchLimit, progress: ++liReceived});
+    lineitems.push(result);
+}
+
+function viewList() {
+    dojo.style('searchProgress', 'visibility', 'hidden');
+    dojo.style('oils-acq-recv-grid', 'visibility', 'visible');
+    var store = new dojo.data.ItemFileWriteStore(
+        {data:jub.toStoreData(lineitems, null, 
+            {virtualFields:['estimated_price', 'actual_price']})});
+    var model = new dojox.grid.data.DojoData(
+        null, store, {rowsPerPage: 20, clientSort: true, query:{id:'*'}});
+    JUBGrid.populate(liGrid, model, lineitems);
+}
+
+openils.Util.addOnLoad(drawForm);
+
diff --git a/Open-ILS/web/js/ui/default/acq/settings/li_attr.js b/Open-ILS/web/js/ui/default/acq/settings/li_attr.js
new file mode 100644 (file)
index 0000000..28b8f0b
--- /dev/null
@@ -0,0 +1,105 @@
+dojo.require("dijit.Dialog");
+dojo.require('dijit.layout.TabContainer');
+dojo.require('dijit.layout.ContentPane');
+dojo.require('dijit.form.FilteringSelect');
+dojo.require('dijit.form.TextBox');
+dojo.require('dojox.grid.Grid');
+dojo.require("fieldmapper.OrgUtils");
+dojo.require('openils.Event');
+dojo.require('openils.User');
+dojo.require('openils.acq.LineitemAttr');
+
+var provider = null;
+var marcRegex = /^\/\/\*\[@tag="(\d+)"]\/\*\[@code="(\w)"]$/;
+var attrDefStores;
+
+
+function getOrgInfo(rowIndex) {
+    data = providerGrid.model.getRow(rowIndex);
+    if(!data) return;
+    return fieldmapper.aou.findOrgUnit(data.owner).shortname();
+}
+
+function getTag(rowIdx) {
+    data = this.grid.model.getRow(rowIdx);
+    if(!data) return;
+    return data.xpath.replace(marcRegex, '$1');
+}
+
+function getSubfield(rowIdx) {
+    data = this.grid.model.getRow(rowIdx);
+    if(!data) return;
+    return data.xpath.replace(marcRegex, '$2');
+}
+
+
+function loadStores(onload) {
+    if(attrDefStores) 
+        return onload();
+    openils.acq.LineitemAttr.createAttrDefStores(
+        function(stores) {
+            attrDefStores = stores;
+            onload();
+        }
+    )
+}
+
+
+function loadMarcAttrGrid() {
+    loadStores(
+        function() {
+            var store = new dojo.data.ItemFileReadStore({data:attrDefStores.marc});
+            var model = new dojox.grid.data.DojoData(
+                null, store, {rowsPerPage: 20, clientSort: true, query:{id:'*'}});
+            liMarcAttrGrid.setModel(model);
+            liMarcAttrGrid.update();
+        }
+    );
+}
+
+function loadGeneratedAttrGrid() {
+    loadStores(
+        function() {
+            var store = new dojo.data.ItemFileReadStore({data:attrDefStores.generated});
+            var model = new dojox.grid.data.DojoData(
+                null, store, {rowsPerPage: 20, clientSort: true, query:{id:'*'}});
+            liGeneratedAttrGrid.setModel(model);
+            liGeneratedAttrGrid.update();
+        }
+    );
+}
+
+/*
+function createOrderRecordField(fields) {
+    fields.provider = providerId;
+    if(!fields.xpath) 
+        fields.xpath = '//*[@tag="'+fields.tag+'"]/*[@code="'+fields.subfield+'"]';
+    delete fields.tag;
+    delete fields.subfield;
+    openils.acq.Provider.createLineitemProviderAttrDef(fields, 
+        function(id) {
+            loadPADGrid();
+        }
+    );
+}
+
+function setORDesc() {
+    var code = dijit.byId('oils-acq-provider-or-code');
+    var desc = dijit.byId('oils-acq-provider-or-desc');
+    desc.setValue(code.getDisplayedValue());
+}
+
+function deleteORDataFields() {
+    var list = []
+    var selected = padGrid.selection.getSelected();
+    for(var idx = 0; idx < selected.length; idx++) 
+        list.push(padGrid.model.getRow(selected[idx]).id);
+    openils.acq.Provider.lineitemProviderAttrDefDeleteList(
+        list, function(){loadPADGrid();});
+}
+*/
+
+
+//dojo.addOnLoad(loadLIAttrGrid);
+
+
diff --git a/Open-ILS/web/templates/base.tt2 b/Open-ILS/web/templates/base.tt2
new file mode 100644 (file)
index 0000000..84549c1
--- /dev/null
@@ -0,0 +1,41 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns='http://www.w3.org/1999/xhtml' lang='${locale}' xml:lang='${locale}'>
+    <head>
+        <link rel='stylesheet' type='text/css' 
+            href='[% ctx.media_prefix %]/css/skin/[% ctx.skin %].css'></link>
+        <link rel='stylesheet' type='text/css' 
+            href='[% ctx.media_prefix %]/css/theme/[% ctx.skin %].css'></link>
+        <script type="text/javascript" src="[% ctx.media_prefix %]/js/dojo/dojo/dojo.js"
+            djConfig="parseOnLoad: true, isDebug:true"></script>
+        <script type="text/javascript" src="[% ctx.media_prefix %]/js/dojo/opensrf/md5.js"></script>
+        <script>
+            var oilsCookieBase = '[% ctx.base_uri %]';
+        </script>
+        <script type="text/javascript" src="[% ctx.media_prefix %]/js/ui/base.js"></script>
+    </head>
+    <body class='tundra'>
+
+        <!-- general purpose login dialog -->
+        <div style='display:none;' dojoType="dijit.Dialog" jsId='oilsLoginDialog' class='oils-login-dialog'>
+            <b>Please Login</b>
+            <form onsubmit='oilsDoLogin();'>
+            <table>
+                <tr>
+                    <td>Username</td>
+                    <td><input type='dijit.form.TextBox' id='oils-login-username'/></td>
+                </tr>
+                <tr>
+                    <td>Password</td>
+                    <td><input type='dijit.form.TextBox' id='oils-login-password' type='password'/></td>
+                </tr>
+                <tr>
+                    <td colspan='2'>
+                        <button type='submit' dojoType='dijit.form.Button'>Login</button>
+                    </td>
+                </tr>
+            </table>
+            </form>
+        </div>
+        [% content %]
+    </body>
+</html>
diff --git a/Open-ILS/web/templates/default/acq/common/jubgrid.tt2 b/Open-ILS/web/templates/default/acq/common/jubgrid.tt2
new file mode 100644 (file)
index 0000000..94bc374
--- /dev/null
@@ -0,0 +1,209 @@
+[%#-
+This template creates a split screen Dojo layout. The top frame
+of the screen holds a list of of JUBs, or titles. Clicking on a
+title in the top frame will load the purchase details for all the
+copies on order for that title into the bottom frame.
+
+To create a display for a set of JUBs, create a Dojo store and
+model for the set of JUBs, then place the following lines in your
+HTML where you want the display to appear:
+
+    <%namespace file='/oils/default/common/jubgrid.html' name='jubgrid'/>
+    ${jubgrid.jubgrid('dom_prefix', 'grid_jsid')}
+
+where 'dom_prefix' is a string that will be used as the prefix
+for the DOM notes that are created by this template, and
+'grid_jsid' is a valid JavaScript identifier that will name the
+DOM node to which the list of JUBs will be attached.  For example
+
+    ${jubgrid.jubgrid('oils-acq-picklist', 'pickListGrid', hideDetails)}
+
+will create a Dojo grid with the DOM id of
+
+    'oils-acq-picklist-JUB-grid'
+
+and a jsid of
+
+    pickListGrid
+
+To fill the grid with data, call the javascript function
+
+    JUBGrid.populate(grid_jsid, model)
+
+'grid_jsid' is the same javascript id that was used to
+instantiate the template, and model is a javascript variable
+pointing to the JUB model (and store) that you have created.
+-#%]
+
+[% UNLESS hide_details %]
+<div dojoType='dijit.layout.ContentPane' style='height:100%;'>
+[% END %]
+
+    <style type='text/css'>
+        .grid_container {width: 100%; height: 100%;}
+    </style>
+
+    <script src='[% ctx.media_prefix %]/js/ui/default/acq/common/jubgrid.js'> </script>
+    <script src='[% ctx.media_prefix %]/js/dojo/openils/CopyLocation.js'> </script>
+    <script type="text/javascript">
+    JUBGrid.getPO = function(rowIndex) {
+        var data = JUBGrid.jubGrid.model.getRow(rowIndex);
+        if (!(data && data.purchase_order)) return '';
+        return "<a href='[% ctx.base_uri %]/acq/po/view/" + data.purchase_order+"'>"+data.purchase_order+"</a>";
+    }
+    JUBGrid.jubGridLayout = [{
+        //noscroll: true,
+        cells: [[
+            {name: 'ID', field: 'id', width:'auto'},
+            {name: 'Title', width: "180px", get:JUBGrid.getJUBTitle},
+            {name: 'Author', get:JUBGrid.getJUBAuthor, width:'auto'},
+            {name: 'ISBN', get:JUBGrid.getJUBIsbn, width:'auto'},
+            {name: 'Pubdate', get:JUBGrid.getJUBPubdate, width:'auto'},
+            {name: 'Actual Price', 
+                field:'actual_price',
+                get:JUBGrid.getJUBActualPrice,
+                editor:dojox.grid.editors.Dijit, width:'auto', 
+                editorClass: "dijit.form.CurrencyTextBox" 
+            },
+            {name: 'Estimated Price', 
+                field:'estimated_price',
+                get:JUBGrid.getJUBEstimatedPrice, width:'auto',
+                editor:dojox.grid.editors.Dijit, 
+                editorClass: "dijit.form.CurrencyTextBox" 
+            },
+            {name: 'Vendor', width:'auto',
+            field: 'provider', get:JUBGrid.getProvider,
+            editor:openils.editors.ProviderSelectEditor,
+           },
+            {name: 'No. Copies', field: 'item_count', width:'auto'},
+            {name: 'State', field: 'state', width:'auto'},
+            {name: 'PO', get:JUBGrid.getPO, width:'auto'}
+        ]]
+    }];
+
+    JUBGrid.jubDetailGridLayout = [{
+        cells: [[
+            {name:"ID", field:"id"},
+            {name:"Fund", field:"fund",
+                get:JUBGrid.getLIDFundCode,
+                editor: openils.editors.FundSelectEditor,
+            },
+            {name:"Branch", field:"owning_lib",
+                   get:JUBGrid.getLIDLibName,
+                   editor: openils.editors.OrgUnitSelectEditor
+               },
+            {name:"Barcode", field:"barcode", width:'auto',
+                editor:dojox.grid.editors.Dijit, 
+                editorClass: "dijit.form.TextBox" 
+               },
+            {name:"Call Number", field:"cn_label", width:'auto',
+                editor:dojox.grid.editors.Dijit, 
+                editorClass: "dijit.form.TextBox" 
+               },
+            {name:"Shelving Location", field:"location", width:'auto',
+                editor:openils.editors.CopyLocationSelectEditor,
+                get:JUBGrid.getCopyLocation
+               },
+            {name:"Receive Time", width:'auto',
+                get:JUBGrid.getRecvTime
+               },
+        ]]
+    }];
+
+    JUBGrid.jubDetailGridLayoutReadOnly = [{
+        cells: [[
+            {name:'ID', field:"id"},
+            {name:'Fund', field:"fund",
+             get:JUBGrid.getLIDFundCode,
+            },
+            {name:'Branch', field:"owning_lib",
+                   get:JUBGrid.getLIDLibName,
+               },
+           {name:'Barcode', field:"barcode", width:'auto'},
+            {name:'Call Number', field:"cn_label", width:'auto'},
+           {name:'Shelving Location', field:"location", 
+                width:'auto', get:JUBGrid.getCopyLocation},
+        ]]
+   &