Merge branch 'master' of git.evergreen-ils.org:Evergreen into template-toolkit-opac
authorBill Erickson <berick@esilibrary.com>
Thu, 25 Aug 2011 20:03:27 +0000 (16:03 -0400)
committerBill Erickson <berick@esilibrary.com>
Thu, 25 Aug 2011 20:03:27 +0000 (16:03 -0400)
53 files changed:
Open-ILS/examples/fm_IDL.xml
Open-ILS/examples/oils_ctl.sh
Open-ILS/examples/oils_yaz.xml.example [new file with mode: 0644]
Open-ILS/examples/oils_z3950.xml.example [new file with mode: 0644]
Open-ILS/src/Makefile.am
Open-ILS/src/extras/Makefile.install
Open-ILS/src/perlmods/lib/OpenILS/Application/Acq/Order.pm
Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/Circulate.pm
Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/Money.pm
Open-ILS/src/perlmods/lib/OpenILS/Application/Search/Z3950.pm
Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/Driver/Pg/QueryParser.pm
Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/QueryParser.pm
Open-ILS/src/perlmods/lib/OpenILS/Application/Vandelay.pm
Open-ILS/src/perlmods/lib/OpenILS/Utils/RemoteAccount.pm
Open-ILS/src/sql/Pg/002.schema.config.sql
Open-ILS/src/sql/Pg/005.schema.actors.sql
Open-ILS/src/sql/Pg/020.schema.functions.sql
Open-ILS/src/sql/Pg/200.schema.acq.sql
Open-ILS/src/sql/Pg/800.fkeys.sql
Open-ILS/src/sql/Pg/950.data.seed-values.sql
Open-ILS/src/sql/Pg/create_database.sql [new file with mode: 0644]
Open-ILS/src/sql/Pg/upgrade/0605.schema.lp826844_org_unit_parent_protect_fix.sql [new file with mode: 0644]
Open-ILS/src/sql/Pg/upgrade/0606.schema.czs_use_perm_column.sql [new file with mode: 0644]
Open-ILS/src/sql/Pg/upgrade/0607.schema.oua_force_order.sql [new file with mode: 0644]
Open-ILS/src/sql/Pg/upgrade/0608.data.vandelay-export-error-match-info.sql [new file with mode: 0644]
Open-ILS/src/sql/Pg/upgrade/0609.schema.acq-lineitem-detail-receiver.sql [new file with mode: 0644]
Open-ILS/src/sql/Pg/upgrade/0610.data.acq-copy-creator-from-receiver.sql [new file with mode: 0644]
Open-ILS/src/support-scripts/eg_db_config.pl
Open-ILS/web/js/dojo/openils/acq/nls/acq.js
Open-ILS/web/js/dojo/openils/widget/AutoFieldWidget.js
Open-ILS/web/js/ui/default/acq/common/li_table.js
Open-ILS/web/js/ui/default/acq/lineitem/related.js
Open-ILS/web/js/ui/default/actor/user/register.js
Open-ILS/web/js/ui/default/vandelay/vandelay.js
Open-ILS/web/opac/locale/en-US/lang.dtd
Open-ILS/web/opac/locale/en-US/vandelay.dtd
Open-ILS/web/opac/skin/default/js/holds.js
Open-ILS/web/templates/default/acq/common/li_table.tt2
Open-ILS/web/templates/default/acq/lineitem/related.tt2
Open-ILS/web/templates/default/conify/global/booking/resource.tt2
Open-ILS/web/templates/default/conify/global/booking/resource_attr.tt2
Open-ILS/web/templates/default/conify/global/booking/resource_attr_map.tt2
Open-ILS/web/templates/default/conify/global/booking/resource_attr_value.tt2
Open-ILS/web/templates/default/conify/global/booking/resource_type.tt2
Open-ILS/web/templates/default/vandelay/inc/queue.tt2
Open-ILS/web/templates/default/vandelay/inc/upload.tt2
Open-ILS/xul/staff_client/chrome/content/main/main.js
Open-ILS/xul/staff_client/chrome/content/main/main.xul
Open-ILS/xul/staff_client/chrome/content/main/menu.js
Open-ILS/xul/staff_client/chrome/content/main/menu_frame_menus.xul
Open-ILS/xul/staff_client/server/index.xhtml
Open-ILS/xul/staff_client/server/patron/bill2.js
README

index 9b224df..2f9eb59 100644 (file)
@@ -819,9 +819,11 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
             <field reporter:label="Transmission Format" name="transmission_format"  reporter:datatype="text"/>
             <field reporter:label="Auth" name="auth"  reporter:datatype="bool"/>
             <field reporter:label="Attrs" name="attrs" oils_persist:virtual="true"  reporter:datatype="link"/>
+            <field reporter:label="Use Permission" name="use_perm"  reporter:datatype="link"/>
         </fields>
         <links>
             <link field="attrs" reltype="has_many" key="source" map="" class="cza"/>
+            <link field="use_perm" reltype="has_a" key="id" map="" class="ppl"/>
         </links>
         <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
             <actions>
@@ -3416,10 +3418,10 @@ SELECT  usr,
                </links>
                <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
                        <actions>
-                               <create permission="ADMIN_BOOKING_RESOURCE_TYPE" global_required='true'/>
-                               <retrieve />
-                               <update permission="ADMIN_BOOKING_RESOURCE_TYPE" global_required='true'/>
-                               <delete permission="ADMIN_BOOKING_RESOURCE_TYPE" global_required='true'/>
+                               <create permission="ADMIN_BOOKING_RESOURCE_TYPE" context_field='owner'/>
+                               <retrieve permission="ADMIN_BOOKING_RESOURCE_TYPE" context_field='owner'/>
+                               <update permission="ADMIN_BOOKING_RESOURCE_TYPE" context_field='owner'/>
+                               <delete permission="ADMIN_BOOKING_RESOURCE_TYPE" context_field='owner'/>
                        </actions>
                </permacrud>
        </class>
@@ -3448,10 +3450,10 @@ SELECT  usr,
                </links>
                <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
                        <actions>
-                               <create permission="ADMIN_BOOKING_RESOURCE" global_required='true'/>
-                               <retrieve />
-                               <update permission="ADMIN_BOOKING_RESOURCE" global_required='true'/>
-                               <delete permission="ADMIN_BOOKING_RESOURCE" global_required='true'/>
+                               <create permission="ADMIN_BOOKING_RESOURCE" context_field='owner'/>
+                               <retrieve permission="ADMIN_BOOKING_RESOURCE" context_field='owner'/>
+                               <update permission="ADMIN_BOOKING_RESOURCE" context_field='owner'/>
+                               <delete permission="ADMIN_BOOKING_RESOURCE" context_field='owner'/>
                        </actions>
                </permacrud>
        </class>
@@ -3474,10 +3476,10 @@ SELECT  usr,
                </links>
                <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
                        <actions>
-                               <create permission="ADMIN_BOOKING_RESOURCE_ATTR" global_required='true'/>
-                               <retrieve />
-                               <update permission="ADMIN_BOOKING_RESOURCE_ATTR" global_required='true'/>
-                               <delete permission="ADMIN_BOOKING_RESOURCE_ATTR" global_required='true'/>
+                               <create permission="ADMIN_BOOKING_RESOURCE_ATTR" context_field='owner'/>
+                               <retrieve permission="ADMIN_BOOKING_RESOURCE_ATTR" context_field='owner'/>
+                               <update permission="ADMIN_BOOKING_RESOURCE_ATTR" context_field='owner'/>
+                               <delete permission="ADMIN_BOOKING_RESOURCE_ATTR" context_field='owner'/>
                        </actions>
                </permacrud>
        </class>
@@ -3499,10 +3501,10 @@ SELECT  usr,
                </links>
                <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
                        <actions>
-                               <create permission="ADMIN_BOOKING_RESOURCE_ATTR_VALUE" global_required='true'/>
-                               <retrieve />
-                               <update permission="ADMIN_BOOKING_RESOURCE_ATTR_VALUE" global_required='true'/>
-                               <delete permission="ADMIN_BOOKING_RESOURCE_ATTR_VALUE" global_required='true'/>
+                               <create permission="ADMIN_BOOKING_RESOURCE_ATTR_VALUE" context_field='owner'/>
+                               <retrieve permission="ADMIN_BOOKING_RESOURCE_ATTR_VALUE" context_field='owner'/>
+                               <update permission="ADMIN_BOOKING_RESOURCE_ATTR_VALUE" context_field='owner'/>
+                               <delete permission="ADMIN_BOOKING_RESOURCE_ATTR_VALUE" context_field='owner'/>
                        </actions>
                </permacrud>
        </class>
@@ -3521,10 +3523,18 @@ SELECT  usr,
                </links>
                <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
                        <actions>
-                               <create permission="ADMIN_BOOKING_RESOURCE_ATTR_MAP" global_required='true'/>
-                               <retrieve />
-                               <update permission="ADMIN_BOOKING_RESOURCE_ATTR_MAP" global_required='true'/>
-                               <delete permission="ADMIN_BOOKING_RESOURCE_ATTR_MAP" global_required='true'/>
+                               <create permission="ADMIN_BOOKING_RESOURCE_ATTR_MAP">
+                    <context link="resource" field="owner" />
+                </create>
+                               <retrieve permission="ADMIN_BOOKING_RESOURCE_ATTR_MAP">
+                    <context link="resource" field="owner" />
+                </retrieve>
+                               <update permission="ADMIN_BOOKING_RESOURCE_ATTR_MAP">
+                    <context link="resource" field="owner" />
+                </update>
+                               <delete permission="ADMIN_BOOKING_RESOURCE_ATTR_MAP">
+                    <context link="resource" field="owner" />
+                </delete>
                        </actions>
                </permacrud>
        </class>
@@ -6958,6 +6968,7 @@ SELECT  usr,
                        <field reporter:label="Barcode" name="barcode" reporter:datatype="text" />
                        <field reporter:label="Call Number Label" name="cn_label" reporter:datatype="text" />
                        <field reporter:label="Actual Receive Date" name="recv_time" reporter:datatype="timestamp" />
+                       <field reporter:label="Receiving User" name="receiver" reporter:datatype="link" />
                        <field reporter:label="Fund" name="fund" reporter:datatype="link" />
                        <field reporter:label="Fund Debit" name="fund_debit" reporter:datatype="link" />
                        <field reporter:label="Owning Library" name="owning_lib" reporter:datatype="org_unit" />
@@ -6978,6 +6989,7 @@ SELECT  usr,
                        <link field="circ_modifier" reltype="has_a" key="code" map="" class="ccm"/>
                        <link field="cancel_reason" reltype="has_a" key="id" map="" class="acqcr"/>
                        <link field="claims" reltype="has_many" key="lineitem_detail" map="" class="acqcl"/>
+                       <link field="receiver" reltype="has_a" key="id" map="" class="au"/>
                </links>
        </class>
 
index c88dc67..5363db7 100755 (executable)
@@ -1,9 +1,12 @@
 #!/bin/bash
 
 OPT_ACTION=""
-OPT_SIP_CONFIG=""
-OPT_PID_DIR=""
+OPT_SIP_CONFIG="SYSCONFDIR/oils_sip.xml"
+OPT_PID_DIR="LOCALSTATEDIR/run"
 OPT_SIP_ERR_LOG="/dev/null";
+OPT_Z3950_CONFIG="SYSCONFDIR/oils_z3950.xml"
+OPT_YAZ_CONFIG="SYSCONFDIR/oils_yaz.xml"
+Z3950_LOG="LOCALSTATEDIR/log/oils_z3950.log"
 SIP_DIR="/opt/SIPServer";
 
 # ---------------------------------------------------------------------------
@@ -14,12 +17,18 @@ SIP_DIR="/opt/SIPServer";
 
 function usage {
        echo "";
-       echo "usage: $0 -d <pid_dir> -s <sip_config> -a <action> -l <sip_err_log>";
+       echo "usage: $0 -d <pid_dir> -s <sip_config> -z <z3950_config> -y <yaz_config> -a <action> -l <sip_err_log>";
        echo "";
        echo "Actions include:"
        echo -e "\tstart_sip"
        echo -e "\tstop_sip"
        echo -e "\trestart_sip"
+       echo -e "\tstart_z3950"
+       echo -e "\tstop_z3950"
+       echo -e "\trestart_z3950"
+       echo -e "\tstart_all"
+       echo -e "\tstop_all"
+       echo -e "\trestart_all"
        exit;
 }
 
@@ -33,6 +42,8 @@ while getopts "a:d:s:l:" flag; do
                "s")            OPT_SIP_CONFIG="$OPTARG";;
                "d")            OPT_PID_DIR="$OPTARG";;
                "l")            OPT_SIP_ERR_LOG="$OPTARG";;
+               "z")            OPT_Z3950_CONFIG="$OPTARG";;
+               "y")            OPT_YAZ_CONFIG="$OPTARG";;
                "h"|*)  usage;;
        esac;
 done
@@ -42,7 +53,7 @@ done
 [ -z "$OPT_ACTION" ] && usage;
 
 PID_SIP="$OPT_PID_DIR/oils_sip.pid";
-
+PID_Z3950="$OPT_PID_DIR/oils_z3950.pid";
 
 # ---------------------------------------------------------------------------
 # Utility code for checking the PID files
@@ -72,7 +83,7 @@ function do_action {
 
                pid=$(cat $pidfile);
                echo "Stopping $item : $pid";
-               kill -s INT $pid;
+               kill -s TERM $pid;
                rm -f $pidfile;
 
        fi;
@@ -90,7 +101,7 @@ function start_sip {
        do_action "start" $PID_SIP "OILS SIP Server";
        DIR=$(pwd);
        cd $SIP_DIR;
-    perl SIPServer.pm "$OPT_SIP_CONFIG" >> "$OPT_SIP_ERR_LOG" 2>&1 &
+       perl SIPServer.pm "$OPT_SIP_CONFIG" >> "$OPT_SIP_ERR_LOG" 2>&1 &
        pid=$!;
        cd $DIR;
        echo $pid > $PID_SIP;
@@ -102,6 +113,18 @@ function stop_sip {
        return 0;
 }
 
+function start_z3950 {
+       do_action "start" $PID_Z3950 "OILS Z39.50 Server";
+       simple2zoom -c $OPT_Z3950_CONFIG -- -f $OPT_YAZ_CONFIG >> "$Z3950_LOG" 2>&1 &
+       pid=$!;
+       echo $pid > $PID_Z3950;
+       return 0;
+}
+
+function stop_z3950 {
+       do_action "stop" $PID_Z3950 "OILS Z39.50 Server";
+       return 0;
+}
 
 
 # ---------------------------------------------------------------------------
@@ -111,6 +134,12 @@ case $OPT_ACTION in
        "start_sip") start_sip;;
        "stop_sip") stop_sip;;
        "restart_sip") stop_sip; start_sip;;
+       "start_z3950") start_z3950;;
+       "stop_z3950") stop_z3950;;
+       "restart_z3950") stop_z3950; start_z3950;;
+       "start_all") start_sip; start_z3950;;
+       "stop_all") stop_sip; stop_z3950;;
+       "restart_all") stop_sip; stop_z3950; start_sip; start_z3950;;
        *) usage;;
 esac;
 
diff --git a/Open-ILS/examples/oils_yaz.xml.example b/Open-ILS/examples/oils_yaz.xml.example
new file mode 100644 (file)
index 0000000..df3896d
--- /dev/null
@@ -0,0 +1,14 @@
+<yazgfs>
+  <!-- You can add a listen entry, if you want your z39.50 server to
+       listen on a port other than the default, 9999. -->
+    <server id="server1">
+    <retrievalinfo>
+        <retrieval syntax="xml"/>
+        <retrieval syntax="marc21">
+            <backend syntax="xml">
+                <marc inputformat="xml" outputformat="marc" inputcharset="utf-8" outputcharset="marc-8"/>
+            </backend>
+        </retrieval>
+    </retrievalinfo>
+    </server>
+</yazgfs>
diff --git a/Open-ILS/examples/oils_z3950.xml.example b/Open-ILS/examples/oils_z3950.xml.example
new file mode 100644 (file)
index 0000000..6cacbca
--- /dev/null
@@ -0,0 +1,36 @@
+<client>
+   <database name="CONS">
+     <!-- Change "localhost" to your server's name as appropriate. -->
+     <zurl>http://localhost/opac/extras/sru/-/holdings</zurl>
+     <option name="sru">get</option>
+     <charset>marc-8</charset>
+     <search>
+       <querytype>cql</querytype>
+       <map use="4"><index>eg.title</index></map>
+       <map use="7"><index>eg.isbn</index></map>
+       <map use="8"><index>eg.issn</index></map>
+       <map use="21"><index>eg.subject</index></map>
+       <map use="1003"><index>eg.author</index></map>
+       <map use="1018"><index>eg.publisher</index></map>
+       <map use="1035"><index>eg.keyword</index></map>
+       <map use="1016"><index>eg.keyword</index></map>
+     </search>
+   </database>
+   <database name="BR1">
+     <!-- Change "localhost" to your server's name as appropriate. -->
+     <zurl>http://localhost/opac/extras/sru/BR1/holdings</zurl>
+     <option name="sru">get</option>
+     <charset>marc-8</charset>
+     <search>
+       <querytype>cql</querytype>
+       <map use="4"><index>eg.title</index></map>
+       <map use="7"><index>eg.isbn</index></map>
+       <map use="8"><index>eg.issn</index></map>
+       <map use="21"><index>eg.subject</index></map>
+       <map use="1003"><index>eg.author</index></map>
+       <map use="1018"><index>eg.publisher</index></map>
+       <map use="1035"><index>eg.keyword</index></map>
+       <map use="1016"><index>eg.keyword</index></map>
+     </search>
+   </database>
+</client>
index c7c2a62..796d39c 100644 (file)
@@ -45,7 +45,9 @@ sysconf_DATA = $(examples)/action_trigger_filters.json.example \
               $(examples)/fm_IDL.xml \
               $(examples)/oils_sip.xml.example \
               $(examples)/oils_web.xml.example \
-              $(examples)/lib_ips.txt.example
+              $(examples)/lib_ips.txt.example \
+              $(examples)/oils_yaz.xml.example \
+              $(examples)/oils_z3950.xml.example 
 
 #----------------------------
 # Build ILS CORE
@@ -73,7 +75,7 @@ core_scripts =   $(examples)/oils_ctl.sh \
                 $(supportscr)/action_trigger_runner.pl \
                 $(srcdir)/extras/openurl_map.pl \
                 $(srcdir)/extras/import/marc_add_ids
-        
+
 installautojs = $(autojsbinscripts)
 
 #circ-rules-install
@@ -176,9 +178,6 @@ ilscore-install:
        sed -i 's|BINDIR|@bindir@|g' '$(DESTDIR)@bindir@/autogen.sh'
        sed -i 's|LOCALSTATEDIR|@localstatedir@|g' '$(DESTDIR)@bindir@/autogen.sh'
        sed -i 's|SYSCONFDIR|@sysconfdir@|g' '$(DESTDIR)@bindir@/autogen.sh'
-       sed -i 's|BINDIR|@bindir@|g' '$(DESTDIR)@bindir@/cache-generator.sh'
-       sed -i 's|LOCALSTATEDIR|@localstatedir@|g' '$(DESTDIR)@bindir@/cache-generator.sh'
-       sed -i 's|SYSCONFDIR|@sysconfdir@|g' '$(DESTDIR)@bindir@/cache-generator.sh'
        sed -i 's|BINDIR|@bindir@|g' '$(DESTDIR)@bindir@/reshelving_complete.srfsh'
        sed -i 's|BINDIR|@bindir@|g' '$(DESTDIR)@bindir@/clear_expired_circ_history.srfsh'
        sed -i 's|BINDIR|@bindir@|g' '$(DESTDIR)@bindir@/update_hard_due_dates.srfsh'
@@ -187,6 +186,8 @@ ilscore-install:
        sed -i 's|SYSCONFDIR|@sysconfdir@|g' '$(DESTDIR)@bindir@/long-overdue-status-update.pl'
        sed -i 's|BINDIR|@bindir@|g' '$(DESTDIR)@bindir@/thaw_expired_frozen_holds.srfsh'
        sed -i 's|BINDIR|@bindir@|g' '$(DESTDIR)@bindir@/offline-blocked-list.pl'
+       sed -i 's|LOCALSTATEDIR|@localstatedir@|g' '$(DESTDIR)@bindir@/oils_ctl.sh'
+       sed -i 's|SYSCONFDIR|@sysconfdir@|g' '$(DESTDIR)@bindir@/oils_ctl.sh'
 
 reporter-install:
        sed -i 's|SYSCONFDIR|@sysconfdir@|g' '$(DESTDIR)@bindir@/clark-kent.pl'
index f008ef3..bb69740 100644 (file)
@@ -256,6 +256,10 @@ CPAN_MODULES_MARC_RECORD = \
 CPAN_MODULES_FORCE = \
        Class::DBI::Frozen::301
 
+# Lucid needs this because the XML::RPC that comes by default is broken with Evergreen
+LUCID_CPAN = \
+       XML::RPC
+
 # ----------------------------------------------------------------------------
 
 all: 
@@ -276,7 +280,7 @@ squeeze: install_pgsql_client_debs_90  install_extra_debs_squeeze
 generic_debian:  install_debs test_for_libdbi_pkg install debian_sys_config install_cpan_force
 
 ubuntu-lucid: lucid generic_ubuntu
-lucid: install_pgsql_client_debs_90 install_extra_debs install_cpan_marc_record
+lucid: install_pgsql_client_debs_90 install_extra_debs install_cpan_marc_record install_yaz install_cpan_lucid
 generic_ubuntu: install_debs test_for_libdbi_pkg install debian_sys_config install_cpan_more install_cpan_safe install_cpan_force
 
 # - COMMON TARGETS ---------------------------------------------------------
@@ -311,6 +315,10 @@ install_cpan_fedora:
                echo "force install $$m" | perl -MCPAN -e shell;\
        done
 
+# Install the CPAN modules needed for Lucid
+install_cpan_lucid:
+       for m in $(LUCID_CPAN); do perl -MCPAN -e "install \"$$m\";"; done
+
 # Install a known working version of YAZ
 install_yaz:    
        if [ ! -d $(YAZ) ]; then wget $(YAZ_HOST)/$(YAZ).tar.gz; fi;
index dafa288..7313cc1 100644 (file)
@@ -519,6 +519,7 @@ sub receive_lineitem_detail {
 
     return 1 if $lid->recv_time;
 
+    $lid->receiver($e->requestor->id);
     $lid->recv_time('now');
     $e->update_acq_lineitem_detail($lid) or return 0;
 
@@ -526,6 +527,8 @@ sub receive_lineitem_detail {
     $copy->status(OILS_COPY_STATUS_IN_PROCESS);
     $copy->edit_date('now');
     $copy->editor($e->requestor->id);
+    $copy->creator($e->requestor->id) if $U->ou_ancestor_setting_value(
+        $e->requestor->ws_ou, 'acq.copy_creator_uses_receiver', $e);
     $e->update_asset_copy($copy) or return 0;
 
     $mgr->add_lid;
@@ -554,6 +557,7 @@ sub rollback_receive_lineitem_detail {
 
     return 1 unless $lid->recv_time;
 
+    $lid->clear_receiver;
     $lid->clear_recv_time;
     $e->update_acq_lineitem_detail($lid) or return 0;
 
@@ -3173,5 +3177,54 @@ sub clone_distrib_form {
     return $new_form->id;
 }
 
+__PACKAGE__->register_method(
+       method => 'add_li_to_po',
+       api_name        => 'open-ils.acq.purchase_order.add_lineitem',
+       signature => {
+        desc => q/Adds a lineitem to an existing purchase order/,
+        params => [
+            {desc => 'Authentication token', type => 'string'},
+            {desc => 'The purchase order id', type => 'number'},
+            {desc => 'The lineitem ID', type => 'number'},
+        ],
+        return => {desc => 'Streams a total versus completed counts object, event on error'}
+    }
+);
+
+sub add_li_to_po {
+    my($self, $conn, $auth, $po_id, $li_id) = @_;
+
+    my $e = new_editor(authtoken => $auth, xact => 1);
+    return $e->die_event unless $e->checkauth;
+
+    my $mgr = OpenILS::Application::Acq::BatchManager->new(editor => $e, conn => $conn);
+
+    my $po = $e->retrieve_acq_purchase_order($po_id)
+        or return $e->die_event;
+
+    my $li = $e->retrieve_acq_lineitem($li_id)
+        or return $e->die_event;
+
+    return $e->die_event unless 
+        $e->allowed('CREATE_PURCHASE_ORDER', $po->ordering_agency);
+
+    unless ($po->state =~ /new|pending/) {
+        $e->rollback;
+        return {success => 0, po => $po, error => 'bad-po-state'};
+    }
+
+    unless ($li->state =~ /new|order-ready|pending-order/) {
+        $e->rollback;
+        return {success => 0, li => $li, error => 'bad-li-state'};
+    }
+
+    $li->purchase_order($po_id);
+    $li->state('pending-order');
+    update_lineitem($mgr, $li) or return $e->die_event;
+    
+    $e->commit;
+    return {success => 1};
+}
+
 1;
 
index b569ffd..270f7aa 100644 (file)
@@ -2054,7 +2054,7 @@ sub booking_adjusted_due_date {
         my $booking_ses = OpenSRF::AppSession->create( 'open-ils.booking' );
         my $bookings = $booking_ses->request(
             'open-ils.booking.reservations.filtered_id_list', $self->editor->authtoken,
-            { resource => $booking_item->id, search_start => 'now', search_end => $circ->due_date, fields => { cancel_time => undef }}
+            { resource => $booking_item->id, search_start => 'now', search_end => $circ->due_date, fields => { cancel_time => undef, return_time => undef}}
         )->gather(1);
         $booking_ses->disconnect;
         
@@ -2307,12 +2307,17 @@ sub checkin_retarget {
     return if $self->capture eq 'nocapture'; # Not capturing holds anyway? Move on.
     return if $self->is_precat; # No holds for precats
     return unless $self->circ_lib == $self->copy->circ_lib; # Item isn't "home"? Don't check.
-    return unless $self->copy->holdable; # Not holdable, shouldn't capture holds.
+    return unless $U->is_true($self->copy->holdable); # Not holdable, shouldn't capture holds.
+    my $status = $U->copy_status($self->copy->status);
+    return unless $U->is_true($status->holdable); # Current status not holdable means no hold will ever target the item
     # Specifically target items that are likely new (by status ID)
-    unless ($self->retarget_mode =~ m/\.all/) {
-        my $status = $U->copy_status($self->copy->status)->id;
-        return unless $status == OILS_COPY_STATUS_IN_PROCESS;
+    return unless $status->id == OILS_COPY_STATUS_IN_PROCESS || $self->retarget_mode =~ m/\.all/;
+    my $location = $self->copy->location;
+    if(!ref($location)) {
+        $location = $self->editor->retrieve_asset_copy_location($self->copy->location);
+        $self->copy->location($location);
     }
+    return unless $U->is_true($location->holdable); # Don't bother on non-holdable locations
 
     # Fetch holds for the bib
     my ($result) = $holdcode->method_lookup('open-ils.circ.holds.retrieve_all_from_title')->run(
@@ -2812,7 +2817,11 @@ sub attempt_checkin_hold_capture {
 
     if($self->capture ne 'capture') {
         # see if this item is in a hold-capture-delay location
-        my $location = $self->editor->retrieve_asset_copy_location($self->copy->location);
+        my $location = $self->copy->location;
+        if(!ref($location)) {
+            $location = $self->editor->retrieve_asset_copy_location($self->copy->location);
+            $self->copy->location($location);
+        }
         if($U->is_true($location->hold_verify)) {
             $self->bail_on_events(
                 OpenILS::Event->new('HOLD_CAPTURE_DELAYED', copy_location => $location));
index f8fdb80..1058e8c 100644 (file)
@@ -700,7 +700,7 @@ sub billing_items_create {
     $billing->amount($amt);
 
     $e->create_money_billing($billing) or return $e->die_event;
-    my $evt = OpenILS::Utils::Penalty->calculate_penalties($e, $xact->usr, $U->xact_org($xact->id));
+    my $evt = OpenILS::Utils::Penalty->calculate_penalties($e, $xact->usr, $U->xact_org($xact->id,$e));
     return $evt if $evt;
     $e->commit;
 
index a5cd5ca..6a09529 100644 (file)
@@ -73,6 +73,7 @@ __PACKAGE__->register_method(
     method    => "query_services",
     api_name  => "open-ils.search.z3950.retrieve_services",
     signature => q/
+        @param auth The login session key
         Returns a list of service names that we have config
         data for
     /
@@ -89,7 +90,7 @@ sub query_services {
     return $e->event unless $e->checkauth;
     return $e->event unless $e->allowed('REMOTE_Z3950_QUERY');
 
-    return fetch_service_defs();
+    return fetch_service_defs($e);
 }
 
 # -------------------------------------------------------------------
@@ -97,10 +98,12 @@ sub query_services {
 # -------------------------------------------------------------------
 sub fetch_service_defs {
 
+    my $editor_with_authtoken = shift;
+
     my $hash = $sclient->config_value('z3950', 'services');
 
     # overlay config file values with in-db values
-    my $e = new_editor();
+    my $e = $editor_with_authtoken || new_editor();
     if($e->can('search_config_z3950_source')) {
 
         my $sources = $e->search_config_z3950_source(
@@ -118,6 +121,8 @@ sub fetch_service_defs {
                 record_format => $s->record_format,
                 transmission_format => $s->transmission_format,
                 auth => $s->auth,
+                use_perm => ($s->use_perm) ? 
+                    $e->retrieve_permission_perm_list($s->use_perm)->code : ''
             };
 
             for my $a ( @{ $s->attrs } ) {
@@ -151,6 +156,19 @@ sub fetch_service_defs {
         }
     };
 
+    # then filter out any services which the requestor lacks the perm for
+    foreach my $s (keys %{ $hash }) {
+        if ($$hash{$s}{use_perm}) {
+            if ($U->check_perms(
+                $e->requestor->id,
+                $e->requestor->ws_ou,
+                $$hash{$s}{use_perm}
+            )) {
+                delete $$hash{$s};
+            }
+        };
+    }
+
     %services = %$hash; # cache these internally so we can actually use the db-configured sources
     return $hash;
 }
index e8949ef..5f8f74a 100644 (file)
@@ -497,7 +497,7 @@ sub toSQL {
     my $flat_plan = $self->flatten;
 
     # generate the relevance ranking
-    my $rel = "AVG(\n\t\t(" . join(")+\n\t\t(", @{$$flat_plan{rank_list}}) . ")\n\t)";
+    my $rel = "AVG(\n\t\t(" . join(")+\n\t\t(", @{$$flat_plan{rank_list}}) . ")\n\t)+1";
 
     # find any supplied sort option
     my ($sort_filter) = $self->find_filter('sort');
@@ -720,12 +720,17 @@ sub flatten {
                 my $table = $node->table;
                 my $talias = $node->table_alias;
 
-                my $node_rank = $node->rank . " * ${talias}.weight";
+                my $node_rank = 'COALESCE(' . $node->rank . " * ${talias}.weight, 0.0)";
 
                 my $core_limit = $self->QueryParser->core_limit || 25000;
                 $from .= "\n\tLEFT JOIN (\n\t\tSELECT fe.*, fe_weight.weight, x.tsq /* search */\n\t\t  FROM  $table AS fe";
                 $from .= "\n\t\t\tJOIN config.metabib_field AS fe_weight ON (fe_weight.id = fe.field)";
-                $from .= "\n\t\t\tJOIN (SELECT ".$node->tsquery ." AS tsq ) AS x ON (fe.index_vector @@ x.tsq)";
+
+                if ($node->dummy_count < @{$node->only_atoms} ) {
+                    $from .= "\n\t\t\tJOIN (SELECT ". $node->tsquery ." AS tsq ) AS x ON (fe.index_vector @@ x.tsq)";
+                } else {
+                    $from .= "\n\t\t\t, (SELECT NULL::tsquery AS tsq ) AS x";
+                }
 
                 my @bump_fields;
                 if (@{$node->fields} > 0) {
@@ -760,6 +765,7 @@ sub flatten {
 
                 $where .= '(' . $talias . ".id IS NOT NULL";
                 $where .= ' AND ' . join(' AND ', map {"${talias}.value ~* ".$self->QueryParser->quote_phrase_value($_)} @{$node->phrases}) if (@{$node->phrases});
+                $where .= ' AND ' . join(' AND ', map {"${talias}.value !~* ".$self->QueryParser->quote_phrase_value($_)} @{$node->unphrases}) if (@{$node->unphrases});
                 $where .= ')';
 
                 push @rank_list, $node_rank;
@@ -862,6 +868,8 @@ sub buildSQL {
 
     my $classname = $self->node->classname;
 
+    return $self->sql("to_tsquery('$classname','')") if $self->{dummy};
+
     my $normalizers = $self->node->plan->QueryParser->query_normalizers( $classname );
     my $fields = $self->node->fields;
 
@@ -911,15 +919,23 @@ use base 'QueryParser::query_plan::node';
 sub only_atoms {
     my $self = shift;
 
+    $self->{dummy_count} = 0;
+
     my $atoms = $self->query_atoms;
     my @only_atoms;
     for my $a (@$atoms) {
         push(@only_atoms, $a) if (ref($a) && $a->isa('QueryParser::query_plan::node::atom'));
+        $self->{dummy_count}++ if (ref($a) && $a->{dummy});
     }
 
     return \@only_atoms;
 }
 
+sub dummy_count {
+    my $self = shift;
+    return $self->{dummy_count};
+}
+
 sub table {
     my $self = shift;
     my $table = shift;
index 1b3a37f..3ccd566 100644 (file)
@@ -13,6 +13,7 @@ our %parser_config = (
             group_start => '(',
             group_end => ')',
             required => '+',
+            disallowed => '-',
             modifier => '#'
         }
     }
@@ -497,18 +498,24 @@ sub decompose {
             s/(^|[^|])\b$alias[:=]/$1$class:/g;
         }
 
-        $search_class_re .= '|' unless ($first_class);
-        $first_class = 0;
+        if (!$seen_classes{$class}) {
+            $search_class_re .= '|' unless ($first_class);
+            $first_class = 0;
 
-        $search_class_re .= $class . '(?:\|\w+)*' if (!$seen_classes{$class});
-        $seen_classes{$class} = 1;
+            $search_class_re .= $class . '(?:\|\w+)*';
+            $seen_classes{$class} = 1;
+        }
     }
     $search_class_re .= '):';
 
     warn " ** Search class RE: $search_class_re\n" if $self->debug;
 
     my $required_re = $pkg->operator('required');
-    $required_re = qr/^\s*\Q$required_re\E/;
+    $required_re = qr/\Q$required_re\E/;
+
+    my $disallowed_re = $pkg->operator('disallowed');
+    $disallowed_re = qr/\Q$disallowed_re\E/;
+
     my $and_re = $pkg->operator('and');
     $and_re = qr/^\s*\Q$and_re\E/;
 
@@ -552,7 +559,7 @@ sub decompose {
         } elsif ($self->filter_count && /$filter_re/) { # found a filter
             warn "Encountered search filter: $1$2 set to $3\n" if $self->debug;
 
-            my $negate = ($1 eq '-') ? 1 : 0;
+            my $negate = ($1 eq $pkg->operator('disallowed')) ? 1 : 0;
             $_ = $';
             $struct->new_filter( $2 => [ split '[,]+', $3 ], $negate );
 
@@ -560,7 +567,7 @@ sub decompose {
         } elsif ($self->filter_count && /$filter_as_class_re/) { # found a filter
             warn "Encountered search filter: $1$2 set to $3\n" if $self->debug;
 
-            my $negate = ($1 eq '-') ? 1 : 0;
+            my $negate = ($1 eq $pkg->operator('disallowed')) ? 1 : 0;
             $_ = $';
             $struct->new_filter( $2 => [ split '[,]+', $3 ], $negate );
 
@@ -618,7 +625,7 @@ sub decompose {
         } elsif ($self->facet_class_count && /$facet_re/) { # changing current class
             warn "Encountered facet: $1$2 => $3\n" if $self->debug;
 
-            my $negate = ($1 eq '-') ? 1 : 0;
+            my $negate = ($1 eq $pkg->operator('disallowed')) ? 1 : 0;
             my $facet = $2;
             my $facet_value = [ split '\s*#\s*', $3 ];
             $struct->new_facet( $facet => $facet_value, $negate );
@@ -639,28 +646,37 @@ sub decompose {
             $_ = $';
 
             $last_type = 'CLASS';
-        } elsif (/^\s*"([^"]+)"/) { # phrase, always anded
-            warn "Encountered phrase: $1\n" if $self->debug;
+        } elsif (/^\s*($required_re|$disallowed_re)?"([^"]+)"/) { # phrase, always anded
+            warn 'Encountered' . ($1 ? " ['$1' modified]" : '') . " phrase: $2\n" if $self->debug;
 
             $struct->joiner( '&' );
-            my $phrase = $1;
+            my $req_ness = $1;
+            my $phrase = $2;
 
             my $class_node = $struct->classed_node($current_class);
-            $class_node->add_phrase( $phrase );
-            $_ = $phrase . $';
-
-            $last_type = '';
-        } elsif (/$required_re([^\s)]+)/) { # phrase, always anded
-            warn "Encountered required atom (mini phrase): $1\n" if $self->debug;
-
-            my $phrase = $1;
 
-            my $class_node = $struct->classed_node($current_class);
-            $class_node->add_phrase( $phrase );
+            if ($req_ness eq $pkg->operator('disallowed')) {
+                $class_node->add_dummy_atom( node => $class_node );
+                $class_node->add_unphrase( $phrase );
+                $phrase = '';
+                #$phrase =~ s/(^|\s)\b/$1-/g;
+            } else { 
+                $class_node->add_phrase( $phrase );
+            }
             $_ = $phrase . $';
-            $struct->joiner( '&' );
 
             $last_type = '';
+#        } elsif (/^\s*$required_re([^\s"]+)/) { # phrase, always anded
+#            warn "Encountered required atom (mini phrase): $1\n" if $self->debug;
+#
+#            my $phrase = $1;
+#
+#            my $class_node = $struct->classed_node($current_class);
+#            $class_node->add_phrase( $phrase );
+#            $_ = $phrase . $';
+#            $struct->joiner( '&' );
+#
+#            $last_type = '';
         } elsif (/^\s*([^$group_end\s]+)/o) { # atom
             warn "Encountered atom: $1\n" if $self->debug;
             warn "Remainder: $'\n" if $self->debug;
@@ -671,12 +687,16 @@ sub decompose {
             $_ = $after;
             $last_type = '';
 
-            my $negator = ($atom =~ s/^-//o) ? '!' : '';
+            my $class_node = $struct->classed_node($current_class);
+
+            my $prefix = ($atom =~ s/^$disallowed_re//o) ? '!' : '';
             my $truncate = ($atom =~ s/\*$//o) ? '*' : '';
 
-            if (!grep { $atom eq $_ } ('&','|')) { # throw away & and |, not allowed in tsquery, and not really useful anyway
-                my $class_node = $struct->classed_node($current_class);
-                $class_node->add_fts_atom( $atom, suffix => $truncate, prefix => $negator, node => $class_node );
+            if ($atom ne '' and !grep { $atom =~ /^\Q$_\E+$/ } ('&','|','-','+')) { # throw away & and |, not allowed in tsquery, and not really useful anyway
+#                $class_node->add_phrase( $atom ) if ($atom =~ s/^$required_re//o);
+#                $class_node->add_unphrase( $atom ) if ($prefix eq '!');
+
+                $class_node->add_fts_atom( $atom, suffix => $truncate, prefix => $prefix, node => $class_node );
                 $struct->joiner( '&' );
             }
         } 
@@ -993,6 +1013,15 @@ sub phrases {
     return $self->{phrases};
 }
 
+sub unphrases {
+    my $self = shift;
+    my @phrases = @_;
+
+    $self->{unphrases} ||= [];
+    $self->{unphrases} = \@phrases if (@phrases);
+    return $self->{unphrases};
+}
+
 sub add_phrase {
     my $self = shift;
     my $phrase = shift;
@@ -1002,6 +1031,15 @@ sub add_phrase {
     return $self;
 }
 
+sub add_unphrase {
+    my $self = shift;
+    my $phrase = shift;
+
+    push(@{$self->unphrases}, $phrase);
+
+    return $self;
+}
+
 sub query_atoms {
     my $self = shift;
     my @query_atoms = @_;
@@ -1028,6 +1066,18 @@ sub add_fts_atom {
     return $self;
 }
 
+sub add_dummy_atom {
+    my $self = shift;
+    my @parts = @_;
+
+    my $atom = $self->new_atom( @parts, dummy => 1 );
+
+    push(@{$self->query_atoms}, $self->plan->joiner) if (@{$self->query_atoms});
+    push(@{$self->query_atoms}, $atom);
+
+    return $self;
+}
+
 #-------------------------------
 package QueryParser::query_plan::node::atom;
 
index 2b563a8..62dca2e 100644 (file)
@@ -885,6 +885,7 @@ sub import_record_list_impl {
     my $auto_overlay_best = $$args{auto_overlay_best_match};
     my $match_quality_ratio = $$args{match_quality_ratio};
     my $merge_profile = $$args{merge_profile};
+    my $ft_merge_profile = $$args{fall_through_merge_profile};
     my $bib_source = $$args{bib_source};
     my $import_no_match = $$args{import_no_match};
 
@@ -990,40 +991,17 @@ sub import_record_list_impl {
 
                 if( scalar(keys %match_recs) == 1) { # all matches point to the same record
 
-                    # $auto_overlay_best_func will find the 1 match and 
-                    # overlay if the quality ratio allows it
-
-                    my $res = $e->json_query(
-                        {
-                            from => [
-                                $auto_overlay_best_func,
-                                $rec_id, 
-                                $merge_profile,
-                                $match_quality_ratio
-                            ]
-                        }
+                    ($imported, $error, $rec) = try_auto_overlay(
+                        $e, $type,
+                        $report_args, 
+                        $auto_overlay_best_func,
+                        $retrieve_func,
+                        $rec_class,
+                        $rec_id, 
+                        $match_quality_ratio, 
+                        $merge_profile, 
+                        $ft_merge_profile
                     );
-
-                    if($res and ($res = $res->[0])) {
-    
-                        if($res->{$auto_overlay_best_func} eq 't') {
-                            $logger->info("vl: $type overlay-1match succeeded for queued rec $rec_id");
-                            $imported = 1;
-
-                            # re-fetch the record to pick up the imported_as value from the DB
-                            $$report_args{rec} = $rec = $e->$retrieve_func([
-                                $rec_id, {flesh => 1, flesh_fields => {$rec_class => ['matches']}}]);
-
-
-                        } else {
-                            $$report_args{import_error} = 'overlay.record.quality' if $match_quality_ratio > 0;
-                            $logger->info("vl: $type overlay-1match failed for queued rec $rec_id");
-                        }
-
-                    } else {
-                        $error = 1;
-                        $logger->error("vl: Error attempting overlay with func=$auto_overlay_best_func, profile=$merge_profile, record=$rec_id");
-                    }
                 }
             }
 
@@ -1063,40 +1041,19 @@ sub import_record_list_impl {
             }
 
             if(!$imported and !$error and $auto_overlay_best and scalar(@{$rec->matches}) > 0 ) {
-
                 # caller says to overlay the best match
 
-                my $res = $e->json_query(
-                    {
-                        from => [
-                            $auto_overlay_best_func,
-                            $rec_id,
-                            $merge_profile,
-                            $match_quality_ratio
-                        ]
-                    }
+                ($imported, $error, $rec) = try_auto_overlay(
+                    $e, $type,
+                    $report_args, 
+                    $auto_overlay_best_func,
+                    $retrieve_func,
+                    $rec_class,
+                    $rec_id, 
+                    $match_quality_ratio, 
+                    $merge_profile, 
+                    $ft_merge_profile
                 );
-
-                if($res and ($res = $res->[0])) {
-
-                    if($res->{$auto_overlay_best_func} eq 't') {
-                        $logger->info("vl: $type auto-overlay-best succeeded for queued rec $rec_id");
-                        $imported = 1;
-
-                        # re-fetch the record to pick up the imported_as value from the DB
-                        $$report_args{rec} = $rec = $e->$retrieve_func([
-                            $rec_id, {flesh => 1, flesh_fields => {$rec_class => ['matches']}}]);
-
-                    } else {
-                        $$report_args{import_error} = 'overlay.record.quality' if $match_quality_ratio > 0;
-                        $logger->info("vl: $type auto-overlay-best failed for queued rec $rec_id");
-                    }
-
-                } else {
-                    $error = 1;
-                    $logger->error("vl: Error attempting overlay with func=$auto_overlay_best_func, ".
-                        "quality_ratio=$match_quality_ratio, profile=$merge_profile, record=$rec_id");
-                }
             }
 
             if(!$imported and !$error and $import_no_match and scalar(@{$rec->matches}) == 0) {
@@ -1176,6 +1133,111 @@ sub import_record_list_impl {
     return undef;
 }
 
+
+sub try_auto_overlay {
+    my $e = shift;
+    my $type = shift;
+    my $report_args = shift;
+    my $overlay_func  = shift;
+    my $retrieve_func = shift; 
+    my $rec_class = shift;
+    my $rec_id  = shift;
+    my $match_quality_ratio = shift;
+    my $merge_profile  = shift;
+    my $ft_merge_profile = shift;
+
+    my $imported = 0;
+    my $error = 0;
+
+    # Find the best match and overlay if the quality ratio allows it.
+    my $res = $e->json_query(
+        {
+            from => [
+                $overlay_func,
+                $rec_id, 
+                $merge_profile,
+                $match_quality_ratio
+            ]
+        }
+    );
+
+    if($res and ($res = $res->[0])) {
+
+        if($res->{$overlay_func} eq 't') {
+
+            # first attempt succeeded
+            $imported = 1;
+
+        } else {
+
+            # quality-limited merge failed with insufficient quality.  If there is a 
+            # fall-through merge profile, re-do the merge with the alternate profile
+            # and no quality restriction.
+
+            if($ft_merge_profile and $match_quality_ratio > 0) {
+
+                $logger->info("vl: $type auto-merge failed with profile $merge_profile; ".
+                    "re-merging with fall-through profile $ft_merge_profile");
+
+                my $res = $e->json_query(
+                    {
+                        from => [
+                            $overlay_func,
+                            $rec_id, 
+                            $ft_merge_profile,
+                            0 # minimum quality not required
+                        ]
+                    }
+                );
+
+                if($res and ($res = $res->[0])) {
+
+                    if($res->{$overlay_func} eq 't') {
+
+                        # second attempt succeeded
+                        $imported = 1;
+
+                    } else {
+
+                        # failed to merge on second attempt
+                        $logger->info("vl: $type auto-merge with fall-through failed for queued rec $rec_id");
+                    }
+                } else {
+                    
+                    # second attempt died 
+                    $error = 1;
+                    $logger->error("vl: Error attempting overlay with func=$overlay_func, profile=$merge_profile, record=$rec_id");
+                }
+
+            } else { 
+
+                # failed to merge on first attempt, no fall-through was provided
+                $$report_args{import_error} = 'overlay.record.quality' if $match_quality_ratio > 0;
+                $logger->info("vl: $type auto-merge failed for queued rec $rec_id");
+            }
+        }
+
+    } else {
+
+        # first attempt died 
+        $error = 1;
+        $logger->error("vl: Error attempting overlay with func=$overlay_func, profile=$merge_profile, record=$rec_id");
+    }
+
+    if($imported) {
+
+        # at least 1 of the attempts succeeded
+        $logger->info("vl: $type auto-merge succeeded for queued rec $rec_id");
+
+        # re-fetch the record to pick up the imported_as value from the DB
+        $$report_args{rec} = $e->$retrieve_func([
+            $rec_id, {flesh => 1, flesh_fields => {$rec_class => ['matches']}}]);
+    }
+
+    return ($imported, $error, $$report_args{rec});
+}
+
+
 # tracks any import errors, commits the current xact, responds to the client
 sub finish_rec_import_attempt {
     my $args = shift;
index d8c399c..164e227 100644 (file)
@@ -612,7 +612,7 @@ sub ls_ftp {   # returns full path like: dir/path/file.ext
             next;
         }
         if ($dirtarget and $dirtarget ne '.' and $dirtarget ne './' and
-            $self->_ftp->is_dir($dirtarget)) {
+            $self->_ftp->dir($dirtarget)) {
             foreach my $file (@part) {   # we ensure full(er) path
                 $file =~ /^$dirtarget\// and next;
                 $logger->debug("ls_ftp: prepending $dirtarget/ to $file");
index e72e509..941ea93 100644 (file)
@@ -86,7 +86,7 @@ CREATE TRIGGER no_overlapping_deps
     BEFORE INSERT OR UPDATE ON config.db_patch_dependencies
     FOR EACH ROW EXECUTE PROCEDURE evergreen.array_overlap_check ('deprecates');
 
-INSERT INTO config.upgrade_log (version, applied_to) VALUES ('0604', :eg_version); -- miker/dbs
+INSERT INTO config.upgrade_log (version, applied_to) VALUES ('0610', :eg_version); -- miker/berick
 
 CREATE TABLE config.bib_source (
        id              SERIAL  PRIMARY KEY,
@@ -440,7 +440,8 @@ CREATE TABLE config.z3950_source (
     db                  TEXT    NOT NULL,
     record_format       TEXT    NOT NULL DEFAULT 'FI',
     transmission_format TEXT    NOT NULL DEFAULT 'usmarc',
-    auth                BOOL    NOT NULL DEFAULT TRUE
+    auth                BOOL    NOT NULL DEFAULT TRUE,
+    use_perm            INT     -- REFERENCES permission.perm_list (id)
 );
 
 COMMENT ON TABLE config.z3950_source IS $$
@@ -457,6 +458,10 @@ COMMENT ON COLUMN config.z3950_source.transmission_format IS $$
 Z39.50 preferred record syntax..
 $$;
 
+COMMENT ON COLUMN config.z3950_source.use_perm IS $$
+If set, this permission is required for the source to be listed in the staff
+client Z39.50 interface.  Similar to permission.grp_tree.application_perm.
+$$;
 
 CREATE TABLE config.z3950_attr (
     id          SERIAL  PRIMARY KEY,
index 75b379e..7117157 100644 (file)
@@ -289,31 +289,37 @@ CREATE INDEX actor_org_unit_mailing_address_idx ON actor.org_unit (mailing_addre
 CREATE INDEX actor_org_unit_holds_address_idx ON actor.org_unit (holds_address);
 
 CREATE OR REPLACE FUNCTION actor.org_unit_parent_protect () RETURNS TRIGGER AS $$
-    DECLARE
-        current_aou actor.org_unit%ROWTYPE;
-        seen_ous    INT[];
-        depth_count INT;
+       DECLARE
+               current_aou actor.org_unit%ROWTYPE;
+               seen_ous    INT[];
+               depth_count INT;
        BEGIN
-        current_aou := NEW;
-        depth_count := 0;
-        seen_ous := ARRAY[NEW.id];
-        IF TG_OP = 'INSERT' OR NEW.parent_ou IS DISTINCT FROM OLD.parent_ou THEN
-            LOOP
-                IF current_aou.parent_ou IS NULL THEN -- Top of the org tree?
-                    RETURN NEW; -- No loop. Carry on.
-                END IF;
-                IF current_aou.parent_ou = ANY(seen_ous) THEN -- Parent is one we have seen?
-                    RAISE 'OU LOOP: Saw % twice', current_aou.parent_ou; -- LOOP! ABORT!
-                END IF;
-                -- Get the next one!
-                SELECT INTO current_aou * FROM actor.org_unit WHERE id = current_aou.parent_ou;
-                seen_ous := seen_ous || current_aou.id;
-                depth_count := depth_count + 1;
-                IF depth_count = 100 THEN
-                    RAISE 'OU CHECK TOO DEEP';
-                END IF;
-            END LOOP;
-        END IF;
+               current_aou := NEW;
+               depth_count := 0;
+               seen_ous := ARRAY[NEW.id];
+
+               IF (TG_OP = 'UPDATE') THEN
+                       IF (NEW.parent_ou IS NOT DISTINCT FROM OLD.parent_ou) THEN
+                               RETURN NEW; -- Doing an UPDATE with no change, just return it
+                       END IF;
+               END IF;
+
+               LOOP
+                       IF current_aou.parent_ou IS NULL THEN -- Top of the org tree?
+                               RETURN NEW; -- No loop. Carry on.
+                       END IF;
+                       IF current_aou.parent_ou = ANY(seen_ous) THEN -- Parent is one we have seen?
+                               RAISE 'OU LOOP: Saw % twice', current_aou.parent_ou; -- LOOP! ABORT!
+                       END IF;
+                       -- Get the next one!
+                       SELECT INTO current_aou * FROM actor.org_unit WHERE id = current_aou.parent_ou;
+                       seen_ous := seen_ous || current_aou.id;
+                       depth_count := depth_count + 1;
+                       IF depth_count = 100 THEN
+                               RAISE 'OU CHECK TOO DEEP';
+                       END IF;
+               END LOOP;
+
                RETURN NEW;
        END;
 $$ LANGUAGE PLPGSQL;
index 5653ded..1f78468 100644 (file)
@@ -235,17 +235,14 @@ CREATE OR REPLACE FUNCTION actor.org_unit_descendants_distance( INT ) RETURNS TA
 $$ LANGUAGE SQL STABLE ROWS 1;
 
 CREATE OR REPLACE FUNCTION actor.org_unit_ancestors( INT ) RETURNS SETOF actor.org_unit AS $$
-    WITH RECURSIVE anscestor_depth AS (
-        SELECT  ou.id,
-                ou.parent_ou
-          FROM  actor.org_unit ou
-          WHERE ou.id = $1
-            UNION ALL
-        SELECT  ou.id,
-                ou.parent_ou
-          FROM  actor.org_unit ou
-                JOIN anscestor_depth ot ON (ot.parent_ou = ou.id)
-    ) SELECT ou.* FROM actor.org_unit ou JOIN anscestor_depth USING (id);
+    WITH RECURSIVE org_unit_ancestors_distance(id, distance) AS (
+            SELECT $1, 0
+        UNION
+            SELECT ou.parent_ou, ouad.distance+1
+            FROM actor.org_unit ou JOIN org_unit_ancestors_distance ouad ON (ou.id = ouad.id)
+            WHERE ou.parent_ou IS NOT NULL
+    )
+    SELECT ou.* FROM actor.org_unit ou JOIN org_unit_ancestors_distance ouad USING (id) ORDER BY ouad.distance DESC;
 $$ LANGUAGE SQL ROWS 1;
 
 CREATE OR REPLACE FUNCTION actor.org_unit_ancestor_at_depth ( INT,INT ) RETURNS actor.org_unit AS $$
index e8869a3..b566ff5 100644 (file)
@@ -534,6 +534,7 @@ CREATE TABLE acq.lineitem_detail (
     owning_lib  INT         REFERENCES actor.org_unit (id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED,
     location    INT         REFERENCES asset.copy_location (id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED,
     recv_time   TIMESTAMP WITH TIME ZONE,
+       receiver                INT         REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED,
        cancel_reason   INT     REFERENCES acq.cancel_reason( id ) DEFERRABLE INITIALLY DEFERRED
 );
 
index 4395c64..3777df3 100644 (file)
@@ -122,4 +122,6 @@ ALTER TABLE config.barcode_completion ADD CONSTRAINT config_barcode_completion_o
 CREATE INDEX by_heading_and_thesaurus ON authority.record_entry (authority.normalize_heading(marc)) WHERE deleted IS FALSE or deleted = FALSE;
 CREATE INDEX by_heading ON authority.record_entry (authority.simple_normalize_heading(marc)) WHERE deleted IS FALSE or deleted = FALSE;
 
+ALTER TABLE config.z3950_source ADD CONSTRAINT use_perm_fkey FOREIGN KEY (use_perm) REFERENCES permission.perm_list (id) ON UPDATE CASCADE ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED;
+
 COMMIT;
index 2b39e7b..e23a48e 100644 (file)
@@ -9128,6 +9128,9 @@ Complete? [% target.0.queue.complete %]
  Publication Date | [% helpers.get_queued_bib_attr('pubdate',vqbr.attributes) %]
  Edition          | [% helpers.get_queued_bib_attr('edition',vqbr.attributes) %]
  Item Barcode     | [% helpers.get_queued_bib_attr('item_barcode',vqbr.attributes) %]
+ Import Error     | [% vqbr.import_error %]
+ Error Detail     | [% vqbr.error_detail %]
+ Match Count      | [% vqbr.matches.size %]
 
     [% END %]
 </pre>
@@ -9138,6 +9141,7 @@ $$
 INSERT INTO action_trigger.environment ( event_def, path) VALUES (
     39, 'attributes')
     ,( 39, 'queue')
+    ,( 39, 'matches')
 ;
 
 INSERT INTO action_trigger.event_definition (
@@ -9163,8 +9167,8 @@ INSERT INTO action_trigger.event_definition (
         'print-on-demand',
 $$
 [%- USE date -%]
-"Title of work","Author of work","Language of work","Pagination","ISBN","ISSN","Price","Accession Number","TCN Value","TCN Source","Internal ID","Publisher","Publication Date","Edition","Item Barcode"
-[% FOR vqbr IN target %]"[% helpers.get_queued_bib_attr('title',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('author',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('language',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('pagination',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('isbn',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('issn',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('price',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('rec_identifier',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('eg_tcn',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('eg_tcn_source',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('eg_identifier',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('publisher',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('pubdate',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('edition',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('item_barcode',vqbr.attributes) | replace('"', '""') %]"
+"Title of work","Author of work","Language of work","Pagination","ISBN","ISSN","Price","Accession Number","TCN Value","TCN Source","Internal ID","Publisher","Publication Date","Edition","Item Barcode","Import Error","Error Detail","Match Count"
+[% FOR vqbr IN target %]"[% helpers.get_queued_bib_attr('title',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('author',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('language',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('pagination',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('isbn',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('issn',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('price',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('rec_identifier',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('eg_tcn',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('eg_tcn_source',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('eg_identifier',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('publisher',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('pubdate',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('edition',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('item_barcode',vqbr.attributes) | replace('"', '""') %]","[% vqbr.import_error | replace('"', '""') %]","[% vqbr.error_detail | replace('"', '""') %]","[% vqbr.matches.size %]"
 [% END %]
 $$
     )
@@ -9173,6 +9177,7 @@ $$
 INSERT INTO action_trigger.environment ( event_def, path) VALUES (
     40, 'attributes')
     ,( 40, 'queue')
+    ,( 40, 'matches')
 ;
 
 INSERT INTO action_trigger.event_definition (
@@ -9693,3 +9698,20 @@ INSERT INTO authority.thesaurus (code, name, control_set) VALUES
     ('z', oils_i18n_gettext('z','Other','at','name'), 1),
     ('|', oils_i18n_gettext('|','No attempt to code','at','name'), 1);
 
+INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
+    'acq.copy_creator_uses_receiver',
+    oils_i18n_gettext( 
+        'acq.copy_creator_uses_receiver',
+        'Acq: Set copy creator as receiver',
+        'coust',
+        'label'
+    ),
+    oils_i18n_gettext( 
+        'acq.copy_creator_uses_receiver',
+        'When receiving a copy in acquisitions, set the copy "creator" to be the staff that received the copy',
+        'coust',
+        'label'
+    ),
+    'bool'
+);
+
diff --git a/Open-ILS/src/sql/Pg/create_database.sql b/Open-ILS/src/sql/Pg/create_database.sql
new file mode 100644 (file)
index 0000000..b3be0f0
--- /dev/null
@@ -0,0 +1,30 @@
+-- This file is intended to be called by eg_db_config.pl
+
+-- If manually calling:
+-- Connect to the postgres database initially
+-- Specify the database to create as -vdb_name=DATABASE
+-- Specify the postgres contrib directory as -vcontrib_dir=CONTRIBDIR
+-- You can get the contrib directory using pg_config --sharedir and adding a /contrib to it
+
+-- NOTE: This file does not do transactions
+-- This is intentional. Please do not wrap in BEGIN/COMMIT.
+DROP DATABASE IF EXISTS :db_name;
+
+CREATE DATABASE :db_name TEMPLATE template0 ENCODING 'UNICODE' LC_COLLATE 'C' LC_CTYPE 'C';
+
+\connect :db_name
+
+CREATE LANGUAGE plperl;
+CREATE LANGUAGE plperlu;
+
+-- This dance is because :variable/blah doesn't seem to work when doing \i
+-- But it does when doing \set
+-- So we \set to a single variable, then use that single variable with \i
+\set load_file :contrib_dir/tablefunc.sql
+\i :load_file
+\set load_file :contrib_dir/tsearch2.sql
+\i :load_file
+\set load_file :contrib_dir/pgxml.sql
+\i :load_file
+\set load_file :contrib_dir/hstore.sql
+\i :load_file
diff --git a/Open-ILS/src/sql/Pg/upgrade/0605.schema.lp826844_org_unit_parent_protect_fix.sql b/Open-ILS/src/sql/Pg/upgrade/0605.schema.lp826844_org_unit_parent_protect_fix.sql
new file mode 100644 (file)
index 0000000..4a1f732
--- /dev/null
@@ -0,0 +1,50 @@
+-- Evergreen DB patch XXXX.schema.lp826844_org_unit_parent_protect_fix.sql
+--
+-- Correct the fact that actor.org_unit_parent_protect() may not work
+-- due to 'IF' conditions in PL/pgSQL not necessarily processing in the
+-- order written
+--
+BEGIN;
+
+
+-- check whether patch can be applied
+SELECT evergreen.upgrade_deps_block_check('0605', :eg_version);
+
+CREATE OR REPLACE FUNCTION actor.org_unit_parent_protect () RETURNS TRIGGER AS $$
+       DECLARE
+               current_aou actor.org_unit%ROWTYPE;
+               seen_ous    INT[];
+               depth_count INT;
+       BEGIN
+               current_aou := NEW;
+               depth_count := 0;
+               seen_ous := ARRAY[NEW.id];
+
+               IF (TG_OP = 'UPDATE') THEN
+                       IF (NEW.parent_ou IS NOT DISTINCT FROM OLD.parent_ou) THEN
+                               RETURN NEW; -- Doing an UPDATE with no change, just return it
+                       END IF;
+               END IF;
+
+               LOOP
+                       IF current_aou.parent_ou IS NULL THEN -- Top of the org tree?
+                               RETURN NEW; -- No loop. Carry on.
+                       END IF;
+                       IF current_aou.parent_ou = ANY(seen_ous) THEN -- Parent is one we have seen?
+                               RAISE 'OU LOOP: Saw % twice', current_aou.parent_ou; -- LOOP! ABORT!
+                       END IF;
+                       -- Get the next one!
+                       SELECT INTO current_aou * FROM actor.org_unit WHERE id = current_aou.parent_ou;
+                       seen_ous := seen_ous || current_aou.id;
+                       depth_count := depth_count + 1;
+                       IF depth_count = 100 THEN
+                               RAISE 'OU CHECK TOO DEEP';
+                       END IF;
+               END LOOP;
+
+               RETURN NEW;
+       END;
+$$ LANGUAGE PLPGSQL;
+
+
+COMMIT;
diff --git a/Open-ILS/src/sql/Pg/upgrade/0606.schema.czs_use_perm_column.sql b/Open-ILS/src/sql/Pg/upgrade/0606.schema.czs_use_perm_column.sql
new file mode 100644 (file)
index 0000000..0518f24
--- /dev/null
@@ -0,0 +1,21 @@
+-- Evergreen DB patch 0606.schema.czs_use_perm_column.sql
+--
+-- This adds a column to config.z3950_source called use_perm.
+-- The idea is that if a permission is set for a given source,
+-- then staff will need the referenced permission to use that
+-- source.
+--
+BEGIN;
+
+-- check whether patch can be applied
+SELECT evergreen.upgrade_deps_block_check('0606', :eg_version);
+
+ALTER TABLE config.z3950_source 
+    ADD COLUMN use_perm INT REFERENCES permission.perm_list (id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED;
+
+COMMENT ON COLUMN config.z3950_source.use_perm IS $$
+If set, this permission is required for the source to be listed in the staff
+client Z39.50 interface.  Similar to permission.grp_tree.application_perm.
+$$;
+
+COMMIT;
diff --git a/Open-ILS/src/sql/Pg/upgrade/0607.schema.oua_force_order.sql b/Open-ILS/src/sql/Pg/upgrade/0607.schema.oua_force_order.sql
new file mode 100644 (file)
index 0000000..53905f4
--- /dev/null
@@ -0,0 +1,23 @@
+-- Evergreen DB patch 0607.schema.oua_force_order.sql
+--
+--
+BEGIN;
+
+
+-- check whether patch can be applied
+SELECT evergreen.upgrade_deps_block_check('0607', :eg_version);
+
+CREATE OR REPLACE FUNCTION actor.org_unit_ancestors( INT ) RETURNS SETOF actor.org_unit AS $$
+    WITH RECURSIVE org_unit_ancestors_distance(id, distance) AS (
+            SELECT $1, 0
+        UNION
+            SELECT ou.parent_ou, ouad.distance+1
+            FROM actor.org_unit ou JOIN org_unit_ancestors_distance ouad ON (ou.id = ouad.id)
+            WHERE ou.parent_ou IS NOT NULL
+    )
+    SELECT ou.* FROM actor.org_unit ou JOIN org_unit_ancestors_distance ouad USING (id) ORDER BY ouad.distance DESC;
+$$ LANGUAGE SQL ROWS 1;
+
+
+
+COMMIT;
diff --git a/Open-ILS/src/sql/Pg/upgrade/0608.data.vandelay-export-error-match-info.sql b/Open-ILS/src/sql/Pg/upgrade/0608.data.vandelay-export-error-match-info.sql
new file mode 100644 (file)
index 0000000..2129c67
--- /dev/null
@@ -0,0 +1,62 @@
+-- Evergreen DB patch 0608.data.vandelay-export-error-match-info.sql
+--
+--
+BEGIN;
+
+
+-- check whether patch can be applied
+SELECT evergreen.upgrade_deps_block_check('0608', :eg_version);
+
+-- Add vqbr.import_error, vqbr.error_detail, and vqbr.matches.size to queue print output
+
+UPDATE action_trigger.event_definition SET template = $$
+[%- USE date -%]
+<pre>
+Queue ID: [% target.0.queue.id %]
+Queue Name: [% target.0.queue.name %]
+Queue Type: [% target.0.queue.queue_type %]
+Complete? [% target.0.queue.complete %]
+
+    [% FOR vqbr IN target %]
+=-=-=
+ Title of work    | [% helpers.get_queued_bib_attr('title',vqbr.attributes) %]
+ Author of work   | [% helpers.get_queued_bib_attr('author',vqbr.attributes) %]
+ Language of work | [% helpers.get_queued_bib_attr('language',vqbr.attributes) %]
+ Pagination       | [% helpers.get_queued_bib_attr('pagination',vqbr.attributes) %]
+ ISBN             | [% helpers.get_queued_bib_attr('isbn',vqbr.attributes) %]
+ ISSN             | [% helpers.get_queued_bib_attr('issn',vqbr.attributes) %]
+ Price            | [% helpers.get_queued_bib_attr('price',vqbr.attributes) %]
+ Accession Number | [% helpers.get_queued_bib_attr('rec_identifier',vqbr.attributes) %]
+ TCN Value        | [% helpers.get_queued_bib_attr('eg_tcn',vqbr.attributes) %]
+ TCN Source       | [% helpers.get_queued_bib_attr('eg_tcn_source',vqbr.attributes) %]
+ Internal ID      | [% helpers.get_queued_bib_attr('eg_identifier',vqbr.attributes) %]
+ Publisher        | [% helpers.get_queued_bib_attr('publisher',vqbr.attributes) %]
+ Publication Date | [% helpers.get_queued_bib_attr('pubdate',vqbr.attributes) %]
+ Edition          | [% helpers.get_queued_bib_attr('edition',vqbr.attributes) %]
+ Item Barcode     | [% helpers.get_queued_bib_attr('item_barcode',vqbr.attributes) %]
+ Import Error     | [% vqbr.import_error %]
+ Error Detail     | [% vqbr.error_detail %]
+ Match Count      | [% vqbr.matches.size %]
+
+    [% END %]
+</pre>
+$$
+WHERE id = 39;
+
+
+-- Do the same for the CVS version
+
+UPDATE action_trigger.event_definition SET template = $$
+[%- USE date -%]
+"Title of work","Author of work","Language of work","Pagination","ISBN","ISSN","Price","Accession Number","TCN Value","TCN Source","Internal ID","Publisher","Publication Date","Edition","Item Barcode","Import Error","Error Detail","Match Count"
+[% FOR vqbr IN target %]"[% helpers.get_queued_bib_attr('title',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('author',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('language',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('pagination',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('isbn',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('issn',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('price',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('rec_identifier',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('eg_tcn',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('eg_tcn_source',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('eg_identifier',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('publisher',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('pubdate',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('edition',vqbr.attributes) | replace('"', '""') %]","[% helpers.get_queued_bib_attr('item_barcode',vqbr.attributes) | replace('"', '""') %]","[% vqbr.import_error | replace('"', '""') %]","[% vqbr.error_detail | replace('"', '""') %]","[% vqbr.matches.size %]"
+[% END %]
+$$
+WHERE id = 40;
+
+-- Add matches to the env for both
+INSERT INTO action_trigger.environment (event_def, path) VALUES (39, 'matches');
+INSERT INTO action_trigger.environment (event_def, path) VALUES (40, 'matches');
+
+COMMIT;
+
diff --git a/Open-ILS/src/sql/Pg/upgrade/0609.schema.acq-lineitem-detail-receiver.sql b/Open-ILS/src/sql/Pg/upgrade/0609.schema.acq-lineitem-detail-receiver.sql
new file mode 100644 (file)
index 0000000..49042bd
--- /dev/null
@@ -0,0 +1,11 @@
+-- Evergreen DB patch XXXX.data.acq-copy-creator-from-receiver.sql
+BEGIN;
+
+-- check whether patch can be applied
+SELECT evergreen.upgrade_deps_block_check('0609', :eg_version);
+
+ALTER TABLE acq.lineitem_detail 
+    ADD COLUMN receiver        INT REFERENCES actor.usr (id) DEFERRABLE INITIALLY DEFERRED;
+
+
+COMMIT;
diff --git a/Open-ILS/src/sql/Pg/upgrade/0610.data.acq-copy-creator-from-receiver.sql b/Open-ILS/src/sql/Pg/upgrade/0610.data.acq-copy-creator-from-receiver.sql
new file mode 100644 (file)
index 0000000..afc8fc8
--- /dev/null
@@ -0,0 +1,24 @@
+-- Evergreen DB patch XXXX.data.acq-copy-creator-from-receiver.sql
+BEGIN;
+
+-- check whether patch can be applied
+SELECT evergreen.upgrade_deps_block_check('0610', :eg_version);
+
+INSERT INTO config.org_unit_setting_type ( name, label, description, datatype ) VALUES (
+    'acq.copy_creator_uses_receiver',
+    oils_i18n_gettext( 
+        'acq.copy_creator_uses_receiver',
+        'Acq: Set copy creator as receiver',
+        'coust',
+        'label'
+    ),
+    oils_i18n_gettext( 
+        'acq.copy_creator_uses_receiver',
+        'When receiving a copy in acquisitions, set the copy "creator" to be the staff that received the copy',
+        'coust',
+        'label'
+    ),
+    'bool'
+);
+
+COMMIT;
index 498eaf5..7a3c2a8 100755 (executable)
@@ -31,6 +31,8 @@ my $build_db_sh = '';
 my $offline_file = '';
 my $prefix = '';
 my $sysconfdir = '';
+my $pg_contribdir = '';
+my $create_db_sql = '';
 my @services;
 
 my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
@@ -124,6 +126,20 @@ sub get_settings {
        $settings->{pw} = $settings->{pw} || $opensrf_config->findnodes($pw);
 }
 
+=item create_database() - Creates the database using create_database.sql
+=cut
+sub create_database {
+       my $settings = shift;
+
+       $ENV{'PGUSER'} = $settings->{user};
+       $ENV{'PGPASSWORD'} = $settings->{pw};
+       $ENV{'PGPORT'} = $settings->{port};
+       $ENV{'PGHOST'} = $settings->{host};
+       my $cmd = 'psql -vdb_name=' . $settings->{db} . ' -vcontrib_dir=' . $pg_contribdir .
+               ' -d postgres -f ' . $create_db_sql;
+       system($cmd);
+}
+
 =item create_schema() - Creates the database schema by calling build-db.sh
 =cut
 sub create_schema {
@@ -164,15 +180,21 @@ sub set_admin_account {
 }
 
 my $offline;
+my $cdatabase;
 my $cschema;
 my $uconfig;
+my $pgconfig;
 my %settings;
 
 GetOptions("create-schema" => \$cschema, 
+               "create-database" => \$cdatabase,
                "create-offline" => \$offline,
                "update-config" => \$uconfig,
                "config-file=s" => \$config_file,
                "build-db-file=s" => \$build_db_sh,
+               "pg-contrib-dir=s" => \$pg_contribdir,
+               "create-db-sql=s" => \$create_db_sql,
+               "pg-config" => \$pgconfig,
                "admin-user=s" => \$admin_user,
                "admin-password=s" => \$admin_pw,
                "service=s" => \@services,
@@ -207,25 +229,38 @@ if (!$build_db_sh) {
        $build_db_sh = File::Spec->catfile($script_dir, '../sql/Pg/build-db.sh');
 }
 
+if (!$pg_contribdir) {
+       $pgconfig = 'pg_config' if(!$pgconfig);
+       my @temp = `$pgconfig --sharedir`;
+       chomp $temp[0];
+       $pg_contribdir = File::Spec->catdir($temp[0], 'contrib');
+}
+
+if (!$create_db_sql) {
+       $create_db_sql = File::Spec->catfile($script_dir, '../sql/Pg/create_database.sql');
+}
+
 if (!$offline_file) {
        $offline_file = File::Spec->catfile($sysconfdir, 'offline-config.pl');
 }
 
 unless (-e $build_db_sh) { die "Error: $build_db_sh does not exist. \n"; }
 unless (-e $config_file) { die "Error: $config_file does not exist. \n"; }
+unless (-d $pg_contribdir || !$cdatabase) { die "Error: $pg_contribdir does not exist. \n"; }
 
 if ($uconfig) { update_config(\@services, \%settings); }
 
 # Get our settings from the config file
 get_settings(\%settings);
 
+if ($cdatabase) { create_database(\%settings); }
 if ($cschema) { create_schema(\%settings); }
 if ($admin_user && $admin_pw) {
        set_admin_account($admin_user, $admin_pw, \%settings);
 }
 if ($offline) { create_offline_config($offline_file, \%settings); }
 
-if ((!$cschema && !$uconfig && !$offline && !$admin_pw) || $help) {
+if ((!$cdatabase && !$cschema && !$uconfig && !$offline && !$admin_pw) || $help) {
        print <<HERE;
 
 SYNOPSIS
@@ -261,6 +296,10 @@ COMMANDS
         Creates the Evergreen database schema according to the settings in
         the file specified by --config-file.  
 
+    --create-database
+        Creates the database itself, provided the user and password options
+        represent a superuser.
+
 SERVICE OPTIONS
     --service
         Specify "all" or one or more of the following services to update:
index 2d89f12..0525d48 100644 (file)
@@ -78,5 +78,8 @@
     "LOAD_TERMS_FIRST" : "You can't retrieve records until you've loaded a CSV file\nwith bibliographic IDs in the first column.",
     "SELECT_SEARCH_FIELD": "Select Search Field",
     "LIBRARY_INITIATED": "Library Initiated",
-    "DEL_LI_FROM_PO": "That item has already been ordered!  Deleting it now will not revoke or modify any order that has been placed with a vendor.  Deleting the item may put the system's idea of your purchase order in a state that is inconsistent with reality.  Are you sure you mean to do this?"
+    "DEL_LI_FROM_PO": "That item has already been ordered!  Deleting it now will not revoke or modify any order that has been placed with a vendor.  Deleting the item may put the system's idea of your purchase order in a state that is inconsistent with reality.  Are you sure you mean to do this?",
+    "ADD_LI_TO_PO_BAD_PO_STATE" : "The selected PO has already been activated",
+    "ADD_LI_TO_PO_BAD_LI_STATE" : "The selected lineitem is not in a state that can be added to a purchase order"
+
 }
index 858ae70..41f5b29 100644 (file)
@@ -364,7 +364,12 @@ if(!dojo._hasResource['openils.widget.AutoFieldWidget']) {
                 store.fetch({query:query, onComplete:
                     function(list) {
                         if(list[0]) {
-                            self.widgetValue = store.getValue(list[0], linkInfo.vfield.selector);
+                            var item = list[0];
+                            if(self.labelFormat) {
+                                self.widgetValue = self._applyLabelFormat(item, self.labelFormat);
+                            } else {
+                                self.widgetValue = store.getValue(item, linkInfo.vfield.selector);
+                            }
                             found = true;
                         }
                     }
@@ -374,8 +379,10 @@ if(!dojo._hasResource['openils.widget.AutoFieldWidget']) {
             }
 
             // then try the single object cache
-            if(this.cache[this.auth].single[lclass] && this.cache[this.auth].single[lclass][this.widgetValue]) {
-                this.widgetValue = this.cache[this.auth].single[lclass][this.widgetValue];
+            if(this.cache[this.auth].single[lclass] && 
+                    this.cache[this.auth].single[lclass][this.widgetValue] &&
+                    this.cache[this.auth].single[lclass][this.widgetValue][self.labelFormat || '']) {
+                this.widgetValue = this.cache[this.auth].single[lclass][this.widgetValue][self.labelFormat || ''];
                 return;
             }
 
@@ -388,13 +395,24 @@ if(!dojo._hasResource['openils.widget.AutoFieldWidget']) {
                 async : !this.forceSync,
                 oncomplete : function(r) {
                     var item = openils.Util.readResponse(r);
+
                     var newvalue = item[linkInfo.vfield.selector]();
 
+                    var labelCacheKey = ''; 
+
+                    if(self.labelFormat) {
+                        labelCacheKey = self.labelFormat;
+                        self.widgetValue = self._applyLabelFormat(item.toStoreItem(), self.labelFormat);
+                    } else {
+                        self.widgetValue = newvalue;
+                    }
+
                     if(!self.cache[self.auth].single[lclass])
                         self.cache[self.auth].single[lclass] = {};
-                    self.cache[self.auth].single[lclass][self.widgetValue] = newvalue;
+                    if(!self.cache[self.auth].single[lclass][self.widgetValue])
+                        self.cache[self.auth].single[lclass][self.widgetValue] = {};
+                    self.cache[self.auth].single[lclass][self.widgetValue][labelCacheKey] = newvalue;
 
-                    self.widgetValue = newvalue;
                     self.widget.startup();
                     self._widgetLoaded();
                 }
@@ -428,6 +446,25 @@ if(!dojo._hasResource['openils.widget.AutoFieldWidget']) {
             };
         },
 
+        _applyLabelFormat : function (item, formatList) {
+
+            try {
+
+                // formatList[1..*] are names of fields.  Pull the field
+                // values from each object to determine the values for string substitution
+                var values = [];
+                var format = formatList[0];
+                for(var i = 1; i< formatList.length; i++) 
+                    values.push(item[formatList[i]]);
+
+                return dojo.string.substitute(format, values);
+
+            } catch(E) {
+                throw new Error(
+                    "openils.widget.AutoFieldWidget: Invalid formatList ["+formatList+"] : "+E);
+            }
+        },
+
         _buildLinkSelector : function() {
             var self = this;
             var selectorInfo = this._getLinkSelector();
@@ -465,33 +502,13 @@ if(!dojo._hasResource['openils.widget.AutoFieldWidget']) {
                 if(self.searchFormat)
                     self.widget.searchAttr = '_search';
 
-                function formatString(item, formatList) {
-
-                    try {
-
-                        // formatList[1..*] are names of fields.  Pull the field
-                        // values from each object to determine the values for string substitution
-                        var values = [];
-                        var format = formatList[0];
-                        for(var i = 1; i< formatList.length; i++) 
-                            values.push(item[formatList[i]]);
-
-                        return dojo.string.substitute(format, values);
-
-                    } catch(E) {
-                        throw new Error(
-                            "openils.widget.AutoFieldWidget: Invalid formatList ["+formatList+"] : "+E);
-                    }
-
-                }
-
                 if(list) {
                     var storeData = {data:fieldmapper[linkClass].toStoreData(list)};
 
                     if(self.labelFormat) {
                         dojo.forEach(storeData.data.items, 
                             function(item) {
-                                item._label = formatString(item, self.labelFormat);
+                                item._label = self._applyLabelFormat(item, self.labelFormat);
                             }
                         );
                     }
@@ -499,7 +516,7 @@ if(!dojo._hasResource['openils.widget.AutoFieldWidget']) {
                     if(self.searchFormat) {
                         dojo.forEach(storeData.data.items, 
                             function(item) {
-                                item._search = formatString(item, self.searchFormat);
+                                item._search = self._applyLabelFormat(item, self.searchFormat);
                             }
                         );
                     }
@@ -593,8 +610,8 @@ if(!dojo._hasResource['openils.widget.AutoFieldWidget']) {
             dojo.require('fieldmapper.OrgUtils');
             dojo.require('openils.widget.FilteringTreeSelect');
             this.widget = new openils.widget.FilteringTreeSelect(this.dijitArgs, this.parentNode);
-            this.widget.searchAttr = 'shortname';
-            this.widget.labelAttr = 'shortname';
+            this.widget.searchAttr = this.searchAttr || 'shortname';
+            this.widget.labelAttr = this.searchAttr || 'shortname';
             this.widget.parentField = 'parent_ou';
             var user = new openils.User();
 
index 0b9b642..cf54585 100644 (file)
@@ -29,6 +29,8 @@ function nodeByName(name, context) {
     return dojo.query('[name='+name+']', context)[0];
 }
 
+// for caching linked users.  e.g. lineitem_detail.receiver
+var userCache = {};
 
 var liDetailBatchFields = ['fund', 'owning_lib', 'location', 'collection_code', 'circ_modifier', 'cn_label'];
 var liDetailFields = liDetailBatchFields.concat(['barcode', 'note']);
@@ -1439,6 +1441,21 @@ function AcqLiTable() {
 
         acqLitCopyCountInput.attr('value', self.copyCount()+'');
 
+        var rcvr = copy.receiver();
+        if (rcvr) {
+            if (!userCache[rcvr]) {
+                if(rcvr == openils.User.user.id()) {
+                    userCache[rcvr] = openils.User.user;
+                } else {
+                    userCache[rcvr] = fieldmapper.standardRequest(
+                        ['open-ils.actor', 'open-ils.actor.user.retrieve'],
+                        {params: [openils.User.authtoken, rcvr]}
+                    );
+                }
+            }
+            dojo.query('[name=receiver]', row)[0].innerHTML =  userCache[rcvr].usrname();
+        }
+
         dojo.forEach(liDetailFields,
             function(field) {
                 var searchFilter;
@@ -1464,6 +1481,7 @@ function AcqLiTable() {
                     readOnly = true;
                 }
 
+
                 var widget = new openils.widget.AutoFieldWidget({
                     fmObject : copy,
                     fmField : field,
index c898314..0f3b91b 100644 (file)
@@ -6,6 +6,9 @@ dojo.require("openils.PermaCrud");
 dojo.require('openils.BibTemplate');
 dojo.require('fieldmapper.OrgUtils');
 
+dojo.requireLocalization('openils.acq', 'acq');
+var localeStrings = dojo.i18n.getLocalization('openils.acq', 'acq');
+
 var liTable;
 var identTarget;
 var bibRecord;
@@ -112,6 +115,11 @@ function prepareButtons() {
             acqLitSavePlDialog.show();
         }
     );
+    addToPoButton.onClick = createLi(
+        function() { /* oncomplete */
+            addToPoDialog.show();
+        }
+    );
     createPoButton.onClick = createLi(
         function() { /* oncomplete */
             liTable._loadPOSelect();
@@ -139,6 +147,44 @@ function load() {
 
     prepareButtons();
     fetchRelated();
+    dojo.connect(addToPoSave, 'onClick', addToPo)
+    openils.Util.registerEnterHandler(addToPoInput.domNode, addToPo);
+}
+
+var _addToPoHappened = false;
+function addToPo(args) {
+    var poId = addToPoInput.attr('value');
+    if (!poId) return false;
+    if (_addToPoHappened) return false;
+
+    var liId =  liTable.getSelected()[0].id();
+    console.log("adding li " + liId + " to PO " + poId);
+
+    // hmm, addToPo is invoked twice for some reason...
+    _addToPoHappened = true;
+
+    fieldmapper.standardRequest(
+        ['open-ils.acq', 'open-ils.acq.purchase_order.add_lineitem'],
+        {   async : true,
+            params : [openils.User.authtoken, poId, liId],
+            oncomplete : function(r) {
+                var resp = openils.Util.readResponse(r);
+                if (resp.success) {
+                    location.href = oilsBasePath + '/acq/po/view/' + poId;
+                } else {
+                    _addToPoHappened = false;
+                    if (resp.error == 'bad-po-state') {
+                        alert(localeStrings.ADD_LI_TO_PO_BAD_PO_STATE);
+                    } else if (resp.error == 'bad-li-state') {
+                        alert(localeStrings.ADD_LI_TO_PO_BAD_LI_STATE);
+                    }
+                }
+            }
+        }
+    );
+
+    addToPoDialog.hide();
+    return false; // prevent form submission
 }
 
 openils.Util.addOnLoad(load);
index be1c111..ac9b45c 100644 (file)
@@ -820,6 +820,11 @@ function fleshFMRow(row, fmcls, args) {
         wargs.forceSync = false;
     }
 
+    if(fmcls == 'au' && fmfield == 'home_ou'){
+       wargs.labelAttr = 'name';
+       wargs.searchAttr = 'name';
+    }
+
     var widget = new openils.widget.AutoFieldWidget(wargs);
     widget.build(
         function(w, ww) {
index fbca54e..75da815 100644 (file)
@@ -120,6 +120,16 @@ function vlInit() {
     vlUploadMergeProfile2.searchAttr = 'name';
     vlUploadMergeProfile2.startup();
 
+    vlUploadFtMergeProfile.store = new dojo.data.ItemFileReadStore({data:fieldmapper.vmp.toStoreData(mergeProfiles)});
+    vlUploadFtMergeProfile.labelAttr = 'name';
+    vlUploadFtMergeProfile.searchAttr = 'name';
+    vlUploadFtMergeProfile.startup();
+
+    vlUploadFtMergeProfile2.store = new dojo.data.ItemFileReadStore({data:fieldmapper.vmp.toStoreData(mergeProfiles)});
+    vlUploadFtMergeProfile2.labelAttr = 'name';
+    vlUploadFtMergeProfile2.searchAttr = 'name';
+    vlUploadFtMergeProfile2.startup();
+
 
     // Fetch the bib and authority attribute definitions 
     vlFetchBibAttrDefs(function () { checkInitDone(); });
@@ -672,14 +682,17 @@ function vlGetViewMatches(rowIdx, item) {
         var id = this.grid.store.getValue(item, 'id');
         var rec = queuedRecordsMap[id];
         if(rec.matches().length > 0)
-            return id;
+            return id + ':' + rec.matches().length;
     }
     return -1
 }
 
 function vlFormatViewMatches(id) {
     if(id == -1) return '';
-    return '<a href="javascript:void(0);" onclick="vlLoadMatchUI(' + id + ');">' + this.name + '</a>';
+    var chunks = id.split(':');
+    id = chunks[0];
+    count = chunks[1];
+    return '<a href="javascript:void(0);" onclick="vlLoadMatchUI(' + id + ');">' + this.name + ' (' + count + ')</a>';
 }
 
 function vlGetViewErrors(rowIdx, item) {
@@ -1098,6 +1111,7 @@ function vlHandleQueueItemsAction(action) {
             vlUploadQueueAutoOverlayExact.attr('value',  vlUploadQueueAutoOverlayExact2.attr('value'));
             vlUploadQueueAutoOverlay1Match.attr('value',  vlUploadQueueAutoOverlay1Match2.attr('value'));
             vlUploadMergeProfile.attr('value',  vlUploadMergeProfile2.attr('value'));
+            vlUploadFtMergeProfile.attr('value',  vlUploadFtMergeProfile2.attr('value'));
             vlUploadQueueAutoOverlayBestMatch.attr('value',  vlUploadQueueAutoOverlayBestMatch2.attr('value'));
             vlUploadQueueAutoOverlayBestMatchRatio.attr('value',  vlUploadQueueAutoOverlayBestMatchRatio2.attr('value'));
 
@@ -1116,6 +1130,8 @@ function vlHandleQueueItemsAction(action) {
             vlUploadQueueAutoOverlay1Match2.attr('value', false);
             vlUploadMergeProfile.attr('value', '');
             vlUploadMergeProfile2.attr('value', '');
+            vlUploadFtMergeProfile.attr('value', '');
+            vlUploadFtMergeProfile2.attr('value', '');
             vlUploadQueueAutoOverlayBestMatch.attr('value', false);
             vlUploadQueueAutoOverlayBestMatch2.attr('value', false);
             vlUploadQueueAutoOverlayBestMatchRatio.attr('value', '0.0');
@@ -1196,6 +1212,12 @@ function vlImportRecordQueue(type, queueId, recList, onload) {
         options.merge_profile = profile;
     }
 
+    var ftprofile = vlUploadFtMergeProfile.attr('value');
+    if(ftprofile != null && ftprofile != '') {
+        options.fall_through_merge_profile = ftprofile;
+    }
+
+
     /* determine which method we're calling */
 
     var method = 'open-ils.vandelay.bib_queue.import';
@@ -1690,23 +1712,30 @@ function buildProfileGrid() {
 /* --- Import Item Attr Grid --------------- */
 
 var itemAttrContextOrg;
+var itemAttrGridFirstTime = true;
 function vlShowImportItemAttrEditor() {
     displayGlobalDiv('vl-item-attr-editor-div');
-    buildImportItemAttrGrid();
 
-    var connect = function() {
-        dojo.connect(itemAttrContextOrgSelector, 'onChange',
-            function() {
-                itemAttrContextOrg = this.attr('value');
-                itemAttrGrid.resetStore();
-                vlShowImportItemAttrEditor();
-            }
-        );
-    };
+    if (itemAttrGridFirstTime) {
 
-    new openils.User().buildPermOrgSelector(
-        'ADMIN_IMPORT_ITEM_ATTR_DEF', 
-            itemAttrContextOrgSelector, null, connect);
+        buildImportItemAttrGrid();
+
+        var connect = function() {
+            dojo.connect(itemAttrContextOrgSelector, 'onChange',
+                function() {
+                    itemAttrContextOrg = this.attr('value');
+                    itemAttrGrid.resetStore();
+                    buildImportItemAttrGrid();
+                }
+            );
+        };
+
+        new openils.User().buildPermOrgSelector(
+            'ADMIN_IMPORT_ITEM_ATTR_DEF', 
+                itemAttrContextOrgSelector, null, connect);
+
+        itemAttrGridFirstTime = false;
+    }
 }
 
 function buildImportItemAttrGrid() {
index b0e907f..4003508 100644 (file)
 <!ENTITY staff.main.auth.hostname "Hostname">
 <!ENTITY staff.main.auth.hostname.accesskey "H">
 <!ENTITY staff.main.auth.offline.caption "Offline Use">
+<!ENTITY staff.main.auth.offline.message "Offline Transactions Pending">
 <!ENTITY staff.main.auth.offline.export "Export Transactions">
 <!ENTITY staff.main.auth.offline.import "Import Transactions">
 <!ENTITY staff.main.auth.offline.interface "Standalone Interface">
 <!ENTITY staff.main.menu.file.new.label "New Window">
 <!ENTITY staff.main.menu.file.new_tab.accesskey "T">
 <!ENTITY staff.main.menu.file.new_tab.label "New Tab">
+<!ENTITY staff.main.menu.file.portal.label "Home">
+<!ENTITY staff.main.menu.file.portal.accesskey "H">
 <!ENTITY staff.main.menu.file.join_tabs_horizontal.accesskey "J">
 <!ENTITY staff.main.menu.file.join_tabs_horizontal.label "Join Tabs (Horizontal)">
 <!ENTITY staff.main.menu.file.join_tabs_vertical.accesskey "V">
index aa9649a..99dac5c 100644 (file)
@@ -9,6 +9,7 @@
 <!ENTITY vandelay.auto.import.auto_overlay_best_ratio "Best/Single Match Minimum Quality Ratio">
 <!ENTITY vandelay.auto.import.auto_overlay_best_ratio.desc "New Record Quality / Quality of Best Match">
 <!ENTITY vandelay.auto.import.merge_profile "Merge Profile">
+<!ENTITY vandelay.auto.import.ft_merge_profile "Insufficient Quality Fall-Through Profile">
 <!ENTITY vandelay.auto.width "Auto Width">
 <!ENTITY vandelay.back.to.import.queue "Back To Import Queue">
 <!ENTITY vandelay.bib.attrs "Bibliographic attributes">
index 0363e5b..b8a5e49 100644 (file)
@@ -708,6 +708,7 @@ function holdsSetFormatSelector() {
     for( var i = 0; i < selector.options.length; i++ ) {
         if (selector.options[i].className.indexOf('hide_me') == -1)
             hideMe(selector.options[i]);
+        selector.options[i].disabled = true;
     }
 
        for( var i = 0; i < avail_formats.length; i++ ) {
@@ -716,6 +717,7 @@ function holdsSetFormatSelector() {
         if (!opt) continue;
                if(type=='M') opt.selected=true;
                unHideMe(opt);
+        opt.disabled = false;
        }
 
     // If the user selects a format, P-type holds are no longer an option
index 28ef757..1851833 100644 (file)
                     <td>Callnumber</td>
                     <td>Barcode</td>
                     <td>Notes</td>
+                    <td>Receiver</td>
                     <td colspan='0'></td>
                 </tr>
             </tbody>
                     <td><div name='cn_label'></div></td>
                     <td><div name='barcode'></div></td>
                     <td><div name='note'></div></td>
+                    <td><div name='receiver'></div></td>
                     <td><a href="javascript:void(0);" name="receive">Mark&nbsp;Received</a><a href="javascript:void(0);" name="unreceive">Un-Receive</a>&nbsp;<a href="javascript:void(0);" name="cancel">Cancel</a><span class="hidden" name="cancel_reason"></span>&nbsp;<a href="javascript:void(0);" name="claim">Claim</a></td>
                     <td><div name='delete' dojoType='dijit.form.Button' style='color:red;' scrollOnFocus='false'>X</div></td>
                 </tr>
index 6d47ec9..61ee19f 100644 (file)
         <button jsId="addToPlButton" dojoType="dijit.form.Button">
             Add to Selection List
         </button>
+        <button jsId="addToPoButton" dojoType="dijit.form.Button">
+            Add to Purchase Order
+        </button>
         <button jsId="createPoButton" dojoType="dijit.form.Button">
             Create Purchase Order
         </button>
     </div>
 
+    <div class="hidden">
+        <div dojoType="dijit.Dialog" jsId='addToPoDialog'>
+            <table class='dijitTooltipTable'>
+                <tr>
+                    <td><label>Enter the PO #: </label></td>
+                    <td><input jsId='addToPoInput' dojoType="dijit.form.TextBox" /></td>
+                </tr>
+                <tr>
+                    <td colspan='2' align='center'>
+                        <button dojoType='dijit.form.Button' jsId='addToPoSave' type="submit">Save</button>
+                    </td>
+                </tr>
+            </table>
+        </div>
+    </div>
+
 </div>
 [% INCLUDE "default/acq/common/info.tt2" which = "Related" %]
 [% INCLUDE "default/acq/common/li_table.tt2" %]
index 380edad..4e3fe1f 100644 (file)
@@ -1,13 +1,21 @@
 [% WRAPPER default/base.tt2 %]
 [% ctx.page_title = 'Resources' %]
-<div dojoType="dijit.layout.ContentPane" layoutAlign="client">
-    <div dojoType="dijit.layout.ContentPane" layoutAlign="top" class="oils-header-panel">
-        <div>Resources</div>
-        <div>
-            <button dojoType='dijit.form.Button' onClick='brsrcGrid.showCreateDialog()'>New Resource</button>
-            <button dojoType='dijit.form.Button' onClick='brsrcGrid.deleteSelected()'>Delete Selected</button>
-        </div>
+<div dojoType="dijit.layout.ContentPane" layoutAlign="top" class="oils-header-panel">
+    <div>Resources</div>
+    <div>
+        <button dojoType='dijit.form.Button' onClick='brsrcGrid.showCreateDialog()'>New Resource</button>
+        <button dojoType='dijit.form.Button' onClick='brsrcGrid.deleteSelected()'>Delete Selected</button>
     </div>
+</div>
+<div dojoType="dijit.layout.ContentPane" layoutAlign="client">
+    <span>Context Org Unit</span>
+    <select dojoType="openils.widget.OrgUnitFilteringSelect"
+            jsId='contextOrgSelector'
+            searchAttr='shortname'
+            labelAttr='shortname'>
+    </select>
+</div>
+<div dojoType="dijit.layout.ContentPane" layoutAlign="client">
     <table  jsId="brsrcGrid"
             dojoType="openils.widget.AutoGrid"
             fieldOrder="['owner', 'type', 'barcode',
             query="{id: '*'}"
             fmClass='brsrc'
             showPaginator='true'
+            autoHeight='true'
             editOnEnter='true'>
     </table>
 </div>
-
 <script type ="text/javascript">
     dojo.require('dijit.form.FilteringSelect');
     dojo.require('openils.widget.AutoGrid');
     dojo.require("openils.widget.PCrudAutocompleteBox");
+    dojo.require('openils.widget.OrgUnitFilteringSelect');
+
+    function filterGrid() {
+        brsrcGrid.resetStore();
+        var unit = contextOrgSelector.getValue();
+        var list = fieldmapper.aou.findOrgUnit(unit).orgNodeTrail().map( function (i) {return i.id() } );
+
+        if(unit){
+            brsrcGrid.loadAll({order_by:{brsrc : 'barcode'}}, { 'owner' : list });
+        } else {
+            brsrcGrid.loadAll({order_by:{brsrc : 'barcode'}});
+        }
+    }
 
     openils.Util.addOnLoad(
         function() {
-            var search = {"id": {"!=": null}};
+            var org_id = openils.User.user.ws_ou();
+            var list = fieldmapper.aou.findOrgUnit(org_id).orgNodeTrail().map( function (i) {return i.id() } );
+
+            new openils.User().buildPermOrgSelector('ADMIN_BOOKING_RESOURCE', contextOrgSelector, null, function() {
+                dojo.connect(contextOrgSelector, 'onChange', filterGrid);});
+
+            var search = {'owner':list};
+
             if (xulG && xulG.resultant_brsrc)
                 search = {id: xulG.resultant_brsrc};
 
index b917b8e..b9a3ece 100644 (file)
@@ -1,18 +1,27 @@
 [% WRAPPER default/base.tt2 %]
 [% ctx.page_title = 'Resource Attributes' %]
-<div dojoType="dijit.layout.ContentPane" layoutAlign="client">
-    <div dojoType="dijit.layout.ContentPane" layoutAlign="top" class="oils-header-panel">
-        <div>Resource Attributes</div>
-        <div>
-            <button dojoType='dijit.form.Button' onClick='braGrid.showCreateDialog()'>New Resource Attribute</button>
-            <button dojoType='dijit.form.Button' onClick='braGrid.deleteSelected()'>Delete Selected</button>
-        </div>
+<div dojoType="dijit.layout.ContentPane" layoutAlign="top" class="oils-header-panel">
+    <div>Resource Attributes</div>
+    <div>
+        <button dojoType='dijit.form.Button' onClick='braGrid.showCreateDialog()'>New Resource Attribute</button>
+        <button dojoType='dijit.form.Button' onClick='braGrid.deleteSelected()'>Delete Selected</button>
     </div>
+</div>
+<div dojoType="dijit.layout.ContentPane" layoutAlign="client">
+    <span>Context Org Unit</span>
+    <select dojoType="openils.widget.OrgUnitFilteringSelect"
+            jsId='contextOrgSelector'
+            searchAttr='shortname'
+            labelAttr='shortname'>
+    </select>
+</div>
+<div dojoType="dijit.layout.ContentPane" layoutAlign="client">
     <table  jsId="braGrid"
             dojoType="openils.widget.AutoGrid"
             fieldOrder="['name', 'owner', 'resource_type', 'required']"
             query="{id: '*'}"
             fmClass='bra'
+            autoHeight='true'
             showPaginator='true'
             editOnEnter='true'>
     </table>
 <script type ="text/javascript">
     dojo.require("openils.widget.PCrudAutocompleteBox");
     dojo.require('openils.widget.AutoGrid');
+    dojo.require('openils.widget.OrgUnitFilteringSelect');
+
+    function filterGrid() {
+        braGrid.resetStore();
+        var unit = contextOrgSelector.getValue();
+        var list = fieldmapper.aou.findOrgUnit(unit).orgNodeTrail().map( function (i) {return i.id() } );
+
+        if(unit){
+            braGrid.loadAll({order_by:{bra : 'name'}}, { 'owner' : list });
+        } else {
+            braGrid.loadAll({order_by:{bra : 'name'}});
+        }
+    }
 
     openils.Util.addOnLoad(
         function() {
                     "fmclass": "brt", "searchAttr": "name"
                 });
             braGrid.overrideEditWidgets.resource_type.shove = {"create": ""};
-            braGrid.loadAll({order_by:{bra : 'name'}}, {"id": {"!=": null}});
+            var org_id = openils.User.user.ws_ou();
+            var list = fieldmapper.aou.findOrgUnit(org_id).orgNodeTrail().map( function (i) {return i.id() } );
+
+            new openils.User().buildPermOrgSelector('ADMIN_BOOKING_RESOURCE_ATTR', contextOrgSelector, null, function() {
+                dojo.connect(contextOrgSelector, 'onChange', filterGrid);});
+            braGrid.loadAll({order_by:{bra : 'name'}}, { 'owner' : list });
         }
     );
 </script>
index 5e35b11..a691fa3 100644 (file)
@@ -1,18 +1,27 @@
 [% WRAPPER default/base.tt2 %]
 [% ctx.page_title = 'Resource Attribute Maps' %]
-<div dojoType="dijit.layout.ContentPane" layoutAlign="client">
-    <div dojoType="dijit.layout.ContentPane" layoutAlign="top" class='oils-header-panel'>
-        <div>Resource Attribute Maps</div>
-        <div>
-            <button dojoType='dijit.form.Button' onClick='bramGrid.showCreateDialog()'>New Resource Attribute Map</button>
-            <button dojoType='dijit.form.Button' onClick='bramGrid.deleteSelected()'>Delete Selected</button>
-        </div>
+<div dojoType="dijit.layout.ContentPane" layoutAlign="top" class='oils-header-panel'>
+    <div>Resource Attribute Maps</div>
+    <div>
+        <button dojoType='dijit.form.Button' onClick='bramGrid.showCreateDialog()'>New Resource Attribute Map</button>
+        <button dojoType='dijit.form.Button' onClick='bramGrid.deleteSelected()'>Delete Selected</button>
     </div>
+</div>
+<div dojoType="dijit.layout.ContentPane" layoutAlign="client">
+    <span>Context Org Unit</span>
+    <select dojoType="openils.widget.OrgUnitFilteringSelect"
+            jsId='contextOrgSelector'
+            searchAttr='shortname'
+            labelAttr='shortname'>
+    </select>
+</div>
+<div dojoType="dijit.layout.ContentPane" layoutAlign="client">
     <table  jsId="bramGrid"
             dojoType="openils.widget.AutoGrid"
             fieldOrder="['resource', 'resource_attr', 'value']"
             query="{id: '*'}"
             fmClass='bram'
+            autoHeight='true'
             showPaginator='true'
             editOnEnter='true'>
     </table>
 <script type ="text/javascript">
     dojo.require("openils.widget.PCrudAutocompleteBox");
     dojo.require('openils.widget.AutoGrid');
+    dojo.require('openils.widget.OrgUnitFilteringSelect');
 
+    function filterGrid() {
+        bramGrid.resetStore();
+        var unit = contextOrgSelector.getValue();
+        var list = fieldmapper.aou.findOrgUnit(unit).orgNodeTrail().map( function (i) {return i.id() } );
+
+        if(unit){
+            bramGrid.loadAll({"order_by":"resource_attr"}, {"resource_attr":{"in":{"select":{"bra":["id"]},"from":"bra","where":{"+bra":{"owner": list } } } } } );
+        } else {
+            bramGrid.loadAll({order_by:{bram : 'resource_attr'}});
+        }
+    }
     openils.Util.addOnLoad(
         function() {
+            var org_id = openils.User.user.ws_ou();
+            var list = fieldmapper.aou.findOrgUnit(org_id).orgNodeTrail().map( function (i) {return i.id() } );
+
+            new openils.User().buildPermOrgSelector('ADMIN_BOOKING_RESOURCE_TYPE', contextOrgSelector, null, function() {
+                dojo.connect(contextOrgSelector, 'onChange', filterGrid);});
+
             bramGrid.overrideEditWidgets.resource =
                 new openils.widget.PCrudAutocompleteBox({
                     "fmclass": "brsrc", "searchAttr": "barcode"
                 });
             bramGrid.overrideEditWidgets.resource.shove = {"create": ""};
-            bramGrid.loadAll({"order_by": {"bram": "resource_attr"}});
+            bramGrid.loadAll({"order_by":"resource_attr"}, {"resource_attr":{"in":{"select":{"bra":["id"]},"from":"bra","where":{"+bra":{"owner": list } } } } } );
         }
     );
 </script>
index af1c5a7..3593db9 100644 (file)
@@ -1,18 +1,27 @@
 [% WRAPPER default/base.tt2 %]
 [% ctx.page_title = 'Resource Attribute Values' %]
-<div dojoType="dijit.layout.ContentPane" layoutAlign="client">
-    <div dojoType="dijit.layout.ContentPane" layoutAlign="top" class='oils-header-panel'>
-        <div>Resource Attribute Values</div>
-        <div>
-            <button dojoType='dijit.form.Button' onClick='bravGrid.showCreateDialog()'>New Resource Attribute Value</button>
-            <button dojoType='dijit.form.Button' onClick='bravGrid.deleteSelected()'>Delete Selected</button>
-        </div>
+<div dojoType="dijit.layout.ContentPane" layoutAlign="top" class='oils-header-panel'>
+    <div>Resource Attribute Values</div>
+    <div>
+        <button dojoType='dijit.form.Button' onClick='bravGrid.showCreateDialog()'>New Resource Attribute Value</button>
+        <button dojoType='dijit.form.Button' onClick='bravGrid.deleteSelected()'>Delete Selected</button>
     </div>
+</div>
+<div dojoType="dijit.layout.ContentPane" layoutAlign="client">
+    <span>Context Org Unit</span>
+    <select dojoType="openils.widget.OrgUnitFilteringSelect"
+            jsId='contextOrgSelector'
+            searchAttr='shortname'
+            labelAttr='shortname'>
+    </select>
+</div>
+<div dojoType="dijit.layout.ContentPane" layoutAlign="client">
     <table  jsId="bravGrid"
             dojoType="openils.widget.AutoGrid"
             fieldOrder="['owner', 'attr', 'valid_value']"
             query="{id: '*'}"
             fmClass='brav'
+            autoHeight='true'
             showPaginator='true'
             editOnEnter='true'>
     </table>
 <script type ="text/javascript">
     dojo.require('dijit.form.FilteringSelect');
     dojo.require('openils.widget.AutoGrid');
+    dojo.require('openils.widget.OrgUnitFilteringSelect');
+
+    function filterGrid() {
+        bravGrid.resetStore();
+        var unit = contextOrgSelector.getValue();
+        var list = fieldmapper.aou.findOrgUnit(unit).orgNodeTrail().map( function (i) {return i.id() } );
+
+        if(unit){
+            bravGrid.loadAll({order_by:{brav : 'attr'}}, { 'owner' : list });
+        } else {
+            bravGrid.loadAll({order_by:{brav : 'attr'}});
+        }
+    }
 
     openils.Util.addOnLoad(
         function() {
-            bravGrid.loadAll({order_by:{brav : 'attr'}}, {"id": {"!=": null}});
+            var org_id = openils.User.user.ws_ou();
+            var list = fieldmapper.aou.findOrgUnit(org_id).orgNodeTrail().map( function (i) {return i.id() } );
+
+            new openils.User().buildPermOrgSelector('ADMIN_BOOKING_RESOURCE_ATTR_VALUE', contextOrgSelector, null, function() {
+                dojo.connect(contextOrgSelector, 'onChange', filterGrid);});
+
+            bravGrid.loadAll({order_by:{brav : 'attr'}}, { 'owner' : list });
         }
     );
 </script>
index 36afba7..ac4c3a4 100644 (file)
@@ -1,6 +1,5 @@
 [% WRAPPER default/base.tt2 %]
 [% ctx.page_title = 'Resource Types' %]
-<div dojoType="dijit.layout.ContentPane" layoutAlign="client">
     <div dojoType="dijit.layout.ContentPane" layoutAlign="top" class="oils-header-panel">
         <div>Resource Types</div>
         <div>
@@ -8,6 +7,15 @@
             <button dojoType='dijit.form.Button' onClick='brtGrid.deleteSelected()'>Delete Selected</button>
         </div>
     </div>
+<div dojoType="dijit.layout.ContentPane" layoutAlign="client">
+    <span>Context Org Unit</span>
+    <select dojoType="openils.widget.OrgUnitFilteringSelect"
+            jsId='contextOrgSelector'
+            searchAttr='shortname'
+            labelAttr='shortname'>
+    </select>
+</div>
+<div dojoType="dijit.layout.ContentPane" layoutAlign="client">
     <table  jsId="brtGrid"
             dojoType="openils.widget.AutoGrid"
             fieldOrder="['name', 'fine_interval', 'fine_amount',
@@ -17,6 +25,7 @@
             query="{id: '*'}"
             fmClass='brt'
             showPaginator='true'
+            autoHeight='true'
             editOnEnter='true'>
     </table>
 </div>
 <script type ="text/javascript">
     dojo.require('dijit.form.FilteringSelect');
     dojo.require('openils.widget.AutoGrid');
+    dojo.require('openils.widget.OrgUnitFilteringSelect');
 
+    function filterGrid() {
+        brtGrid.resetStore();
+        var unit = contextOrgSelector.getValue();
+        var list = fieldmapper.aou.findOrgUnit(unit).orgNodeTrail().map( function (i) {return i.id() } );
+
+        if(unit){
+            brtGrid.loadAll({order_by:{brt : 'name'}}, { 'owner' : list });
+        } else {
+            brtGrid.loadAll({order_by:{brt : 'name'}});
+        }
+    }
     openils.Util.addOnLoad(
         function() {
-            brtGrid.loadAll({"order_by": {"brt": "name"}}, {"id": {"!=": null}});
+            var org_id = openils.User.user.ws_ou();
+            var list = fieldmapper.aou.findOrgUnit(org_id).orgNodeTrail().map( function (i) {return i.id() } );
+
+            new openils.User().buildPermOrgSelector('ADMIN_BOOKING_RESOURCE_TYPE', contextOrgSelector, null, function() {
+                dojo.connect(contextOrgSelector, 'onChange', filterGrid);});
+
+            brtGrid.loadAll({"order_by": {"brt": "name"}}, { 'owner' : list });
         }
     );
 </script>
index 466ee4f..749434f 100644 (file)
                     </td>
                 </tr>
                 <tr>
+                    <td>&vandelay.auto.import.ft_merge_profile;</td>
+                    <td colspan='4'>
+                        <div jsId='vlUploadFtMergeProfile2' dojoType='dijit.form.FilteringSelect' required='false' labelAttr='name' searchAttr='name'/>
+                    </td>
+                </tr>
+                <tr>
                     <td>
                         <button dojoType='dijit.form.Button' jsId='queueItemsImportCancelButton'>Cancel</button>
                     </td>
index b432252..0c1bbd5 100644 (file)
                 <span style='padding-left: 10px; font-size:90%'>(&vandelay.auto.import.auto_overlay_best_ratio.desc;)</span>
             </td>
         </tr>
+        <tr>
+            <td>&vandelay.auto.import.ft_merge_profile;</td>
+            <td colspan='4'>
+                <div jsId='vlUploadFtMergeProfile' dojoType='dijit.form.FilteringSelect' required='false' labelAttr='name' searchAttr='name'/>
+            </td>
+        </tr>
+
         <tr><td colspan='2' style='border-bottom:2px solid #888;'></td></tr>
         <tr><td colspan='2' style='padding-bottom: 10px;'></td></tr>
         <tr>
index f13cbfe..77e72af 100644 (file)
@@ -664,6 +664,16 @@ function main_init() {
             false
         );
 
+        /**
+            @brief Stats for Offline Files / Transactions
+            @launchpad #797408
+        */
+        var px = new util.file('pending_xacts');
+        document.getElementById('offline_message').setAttribute('style','display:none;');
+        if (px._file.exists()) {
+            document.getElementById('offline_message').setAttribute('style','background-color:red;display:block;font-weight:bold;padding:2px;');
+            document.getElementById('offline_import_btn').disabled = true;
+        }
 
     } catch(E) {
         var error = offlineStrings.getFormattedString('common.exception', [E, '']);
index 1c9d96f..1ba8226 100644 (file)
             </grid>
         </groupbox>
 
+        <!-- Offline Information Section -->
         <groupbox flex="1">
             <caption label="&staff.main.auth.offline.caption;"/>
+            <hbox><label id="offline_message" value="&staff.main.auth.offline.message;" style="display:none;" /></hbox>
             <hbox><button label="&staff.main.auth.offline.interface;" accesskey="&staff.main.auth.offline.interface.accesskey;" command="cmd_standalone"/></hbox>
-            <hbox><button label="&staff.main.auth.offline.export;" command="cmd_standalone_export"/></hbox>
-            <hbox><button label="&staff.main.auth.offline.import;" command="cmd_standalone_import"/></hbox>
+            <hbox><button id="offline_export_btn" label="&staff.main.auth.offline.export;" command="cmd_standalone_export"/></hbox>
+            <hbox><button id="offline_import_btn" label="&staff.main.auth.offline.import;" command="cmd_standalone_import"/></hbox>
         </groupbox>
+
 </row>
 </rows>
 </grid>
index 8d179c0..a68b9bf 100644 (file)
@@ -265,6 +265,12 @@ main.menu.prototype = {
                     }
                 }
             ],
+            'cmd_portal' : [
+                ['oncommand'],
+                function() {
+                    obj.set_tab();
+                }
+            ],
             'cmd_close_tab' : [
                 ['oncommand'],
                 function() { obj.close_tab(); }
index 480ac2b..014ffab 100644 (file)
@@ -14,6 +14,7 @@
     <command id="cmd_close_tab" />
     <command id="cmd_close_all_tabs" />
     <command id="cmd_shutdown" />
+    <command id="cmd_portal" />
 
     <command id="cmd_edit_copy_buckets" />
     <command id="cmd_edit_volume_buckets" />
     <menupopup id="main.menu.file.popup">
         <menuitem label="&staff.main.menu.file.new.label;" accesskey="&staff.main.menu.file.new.accesskey;" command="cmd_new_window"/>
         <menuitem label="&staff.main.menu.file.new_tab.label;" accesskey="&staff.main.menu.file.new_tab.accesskey;" command="cmd_new_tab"/>
+        <menuitem label="&staff.main.menu.file.portal.label;" accesskey="&staff.main.menu.file.portal.accesskey;" command="cmd_portal"/>
         <menuseparator />
         <menuitem label="&staff.main.menu.file.close_tab.label;" accesskey="&staff.main.menu.file.close_tab.accesskey;" command="cmd_close_tab"/>
         <menuitem label="&staff.main.menu.tabs.close;" accesskey="&staff.main.menu.tabs.close.accesskey;" command="cmd_close_all_tabs"/>
index cfb5439..0068c4d 100644 (file)
@@ -91,6 +91,7 @@
                         data.stash_retrieve(); 
                         document.getElementById('staffname').innerHTML = ", " + data.list.au[0].first_given_name(); 
                         home_ou_id = data.list.au[0].ws_ou();
+                        xulG.set_tab_name('Portal');
                 }
                 function jb_open_eg_web_page(path, label) {
                         var loc = urls.XUL_BROWSER + '?url=' + window.escape(xulG.url_prefix(urls.EG_WEB_BASE) + '/' + path);
index 76eb5af..a234f3f 100644 (file)
@@ -720,12 +720,16 @@ function print_bills() {
         var template = 'bills_historical'; if (xul_param('current')) template = 'bills_current';
         JSAN.use('patron.util');
         g.patron = patron.util.retrieve_fleshed_au_via_id(ses(),g.patron_id,null); 
-        var params = { 
-            'patron' : g.patron,
-            'printer_context' : 'receipt',
-            'template' : template
-        };
-        g.bill_list.print(params);
+        g.bill_list.print({ 
+              'patron' : g.patron
+            , 'printer_context' : 'receipt'
+            , 'template' : template
+            , 'data' : {
+                  grand_total_owed:   $('tb_total_owed').value
+                , grand_total_billed: $('total_billed').value
+                , grand_total_paid:   $('tb_total_paid').value
+            }
+         });
     } catch(E) {
         g.error.standard_unexpected_error_alert($("patronStrings").getString('staff.patron.bill_history.print_bills.print_error'), E);
     }
diff --git a/README b/README
index fe105db..b9c3eb0 100644 (file)
--- a/README
+++ b/README
@@ -280,57 +280,59 @@ cpan MARC::File::XML
 cpan UUID::Tiny
 ------------------------------------------------------------------------------
 
-Once the PostgreSQL database server has been installed, you must
-create the database and add the appropriate languages and extensions to
-support Evergreen. Issue the following commands as the `postgres` user to set
-up a database called `evergreen`. Note that the location of the PostgreSQL
-`contrib` packages may vary depending on your distribution. In the following
-commands, we assume that you are working with PostgreSQL 9.0 on a Debian-based
-system:
-
-[source, bash]
-------------------------------------------------------------------------------
-createdb --template template0 --lc-ctype=C --lc-collate=C --encoding UNICODE evergreen
-createlang plperl evergreen
-createlang plperlu evergreen
-psql -f /usr/share/postgresql/9.0/contrib/tablefunc.sql -d evergreen
-psql -f /usr/share/postgresql/9.0/contrib/tsearch2.sql -d evergreen
-psql -f /usr/share/postgresql/9.0/contrib/pgxml.sql -d evergreen
-psql -f /usr/share/postgresql/9.0/contrib/hstore.sql -d evergreen
-------------------------------------------------------------------------------
-
-Once you have created the Evergreen database, you need to create a PostgreSQL
-user to access the database. Issue the following command as the `postgres`
-user to create a new PostgreSQL superuser named `evergreen`. When prompted,
-enter the new user's password:
+You need to create a PostgreSQL superuser to create and access the database.
+Issue the following command as the `postgres` user to create a new PostgreSQL
+superuser named `evergreen`. When prompted, enter the new user's password:
 
 [source, bash]
 ------------------------------------------------------------------------------
 createuser -s -P evergreen
 ------------------------------------------------------------------------------
 
-Once you have created the Evergreen database, you also need to create the
-database schema and configure your configuration files to point at the
+Once you have created the Evergreen superuser, you also need to create the
+database and schema, and configure your configuration files to point at the
 database server. Issue the following command as root from inside the Evergreen
 source directory, replacing <user>, <password>, <hostname>, <port>, and <dbname>
-with the appropriate values for your PostgreSQL database, and <admin-user> and
-<admin-pass> with the values you want for the default Evergreen administrator
-account:
+with the appropriate values for your PostgreSQL database (where <user> and
+<password> are for the PostgreSQL superuser you just created), and replace
+<admin-user> and <admin-pass> with the values you want for the default
+Evergreen administrator account:
 
 [source, bash]
 ------------------------------------------------------------------------------
 perl Open-ILS/src/support-scripts/eg_db_config.pl --update-config \
-       --service all --create-schema --create-offline \
+       --service all --create-database --create-schema --create-offline \
        --user <user> --password <password> --hostname <hostname> --port <port> \
        --database <dbname> --admin-user <admin-user> --admin-pass <admin-pass>
 ------------------------------------------------------------------------------
 
-This creates the database schema and configures all of the services in
+This creates the database and schema and configures all of the services in
 your `/openils/conf/opensrf.xml` configuration file to point to that database.
 It also creates the configuration files required by the Evergreen cgi-bin
-administration scripts, and set the user name and password for the default
+administration scripts, and sets the user name and password for the default
 Evergreen administrator account to your requested values.
 
+Creating the database on a remote server
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+In a production instance of Evergreen, your PostgreSQL server should be
+installed on a dedicated server. To create the database in that case, you
+can either:
+
+  *  Install the PostgreSQL contrib modules on the machine on which you
+     are installing the Evergreen code, and use the --create-database
+     option from that machine, or
+  *  Copy the `Open-ILS/src/sql/Pg/create-database.sql` script to your
+     PostgreSQL server and invoke it with:
++
+[source, bash]
+------------------------------------------------------------------------------
+psql -vdb_name=<dbname> -vcontrib_dir=`pg_config --sharedir`/contrib
+------------------------------------------------------------------------------
+
+Then you can issue the `eg_db_config.pl` command as above _without_ the
+`--create-database` argument to create your schema and configure your
+configuration files.
+
 Developer instructions:
 -----------------------