Acq: import file of bib IDs, get paginated LI table from which make new orders
authorsenator <senator@dcc99617-32d9-48b4-a31d-7c20da2025e4>
Wed, 12 May 2010 22:14:49 +0000 (22:14 +0000)
committersenator <senator@dcc99617-32d9-48b4-a31d-7c20da2025e4>
Wed, 12 May 2010 22:14:49 +0000 (22:14 +0000)
This is most of the way to being usable, but isn't all there,
as it probably still needs:
1) trivial parsing of the input file so that we can look for a
specific column in a CSV file to treat as a bib ID instead of a whole line,
2) a way to select and act on lineitems not currently displayed on the page

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

Open-ILS/src/perlmods/OpenILS/Application/Acq/Search.pm
Open-ILS/web/css/skin/default/acq.css
Open-ILS/web/js/dojo/openils/acq/nls/acq.js
Open-ILS/web/js/ui/default/acq/common/li_table_pager.js [new file with mode: 0644]
Open-ILS/web/js/ui/default/acq/picklist/from_bib.js [new file with mode: 0644]
Open-ILS/web/opac/locale/en-US/lang.dtd
Open-ILS/web/templates/default/acq/common/li_table_pager.tt2 [new file with mode: 0644]
Open-ILS/web/templates/default/acq/picklist/from_bib.tt2 [new file with mode: 0644]
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/chrome/locale/en-US/offline.properties

index 6d6e489..e81977b 100644 (file)
@@ -5,6 +5,7 @@ use strict;
 use warnings;
 
 use OpenSRF::AppSession;
+use OpenSRF::Utils::Logger qw/:logger/;
 use OpenILS::Event;
 use OpenILS::Utils::CStoreEditor q/:funcs/;
 use OpenILS::Utils::Fieldmapper;
@@ -455,9 +456,24 @@ __PACKAGE__->register_method(
     }
 );
 
+__PACKAGE__->register_method(
+    method    => "bib_search",
+    api_name  => "open-ils.acq.biblio.create_by_id",
+    stream    => 1,
+    signature => {
+        desc   => q/Returns new lineitems for each matching bib record/,
+        params => [
+            {desc => "Authentication token", type => "string"},
+            {desc => "list of bib IDs", type => "array"},
+            {desc => "options (for lineitem fleshing)", type => "object"}
+        ],
+        return => {desc => "A stream of LIs on success, Event on failure"}
+    }
+);
+
 # This is very similar to zsearch() in Order.pm
 sub bib_search {
-    my ($self, $conn, $auth, $search, $options) = @_;
+    my ($self, $conn, $auth, $search, $opts) = @_;
 
     my $e = new_editor("authtoken" => $auth, "xact" => 1);
     return $e->die_event unless $e->checkauth;
@@ -467,47 +483,101 @@ sub bib_search {
         "editor" => $e, "conn" => $conn
     );
 
-    $options ||= {};
-    $options->{"limit"} ||= 10;
-
-    my $ses = create OpenSRF::AppSession("open-ils.search");
-    my $req = $ses->request(
-        "open-ils.search.biblio.multiclass.query.staff", $options, $search
-    );
+    $opts ||= {};
 
-    my $count = 0;
     my $picklist;
     my @li_ids = ();
-    while (my $resp = $req->recv("timeout" => 60)) {
-        $picklist = OpenILS::Application::Acq::Order::zsearch_build_pl(
-            $mgr, undef # XXX could have per-user name for temp picklist here?
-        ) unless $count++;
+    if ($self->api_name =~ /create_by_id/) {
+        $search = [ sort @$search ]; # for consitency
+        my $bibs = $e->search_biblio_record_entry(
+            {"id" => $search}, {"order_by" => {"bre" => ["id"]}}
+        ) or return $e->die_event;
+
+        if ($opts->{"reuse_picklist"}) {
+            $picklist = $e->retrieve_acq_picklist($opts->{"reuse_picklist"}) or
+                return $e->die_event;
+            return $e->die_event unless
+                $e->allowed("UPDATE_PICKLIST", $picklist->org_unit);
+
+            # If we're reusing an existing picklist, we don't need to
+            # create new lineitems for any bib records for which we already
+
+            my $already_have = $e->search_acq_lineitem({
+                "picklist" => $picklist->id,
+                "eg_bib_id" => [ map { $_->id } @$bibs ]
+            }) or return $e->die_event;
+         
+            # So in that case we a) save the lineitem id's of the relevant
+            # items that already exist so that we can return those items later,
+            # and b) remove the bib id's in question from our list of bib
+            # id's to lineitemize.
+            if (@$already_have) {
+                push @li_ids, $_->id foreach (@$already_have);
+                my @new_bibs = ();
+                foreach my $bib (@$bibs) {
+                    push @new_bibs, $bib unless
+                        grep { $_->eg_bib_id == $bib->id } @$already_have;
+                }
+                $bibs = [ @new_bibs ];
+            }
+        } else {
+            $picklist =
+                OpenILS::Application::Acq::Order::zsearch_build_pl($mgr, undef);
+        }
 
-        my $result = $resp->content;
-        next if not ref $result;
+        $conn->respond($picklist->id);
 
-        # The result object contains a whole heck of a lot more information
-        # than just bib IDs, so maybe we could tell the client something
-        # useful (progress meter at least) in the future...
         push @li_ids, map {
-            my $bib = $_->[0];
             OpenILS::Application::Acq::Order::create_lineitem(
                 $mgr,
                 "picklist" => $picklist->id,
                 "source_label" => "native-evergreen-catalog",
-                "marc" => $e->retrieve_biblio_record_entry($bib)->marc,
-                "eg_bib_id" => $bib
+                "marc" => $_->marc,
+                "eg_bib_id" => $_->id
             )->id;
-        } (@{$result->{"ids"}});
+        } (@$bibs);
+    } else {
+        $opts->{"limit"} ||= 10;
+
+        my $ses = create OpenSRF::AppSession("open-ils.search");
+        my $req = $ses->request(
+            "open-ils.search.biblio.multiclass.query.staff", $opts, $search
+        );
+
+        my $count = 0;
+        while (my $resp = $req->recv("timeout" => 60)) {
+            $picklist = OpenILS::Application::Acq::Order::zsearch_build_pl(
+                $mgr, undef
+            ) unless $count++;
+
+            my $result = $resp->content;
+            next if not ref $result;
+
+            # The result object contains a whole heck of a lot more information
+            # than just bib IDs, so maybe we could tell the client something
+            # useful (progress meter at least) in the future...
+            push @li_ids, map {
+                my $bib = $_->[0];
+                OpenILS::Application::Acq::Order::create_lineitem(
+                    $mgr,
+                    "picklist" => $picklist->id,
+                    "source_label" => "native-evergreen-catalog",
+                    "marc" => $e->retrieve_biblio_record_entry($bib)->marc,
+                    "eg_bib_id" => $bib
+                )->id;
+            } (@{$result->{"ids"}});
+        }
+        $ses->disconnect;
     }
 
     $e->commit;
-    $ses->disconnect;
+
+    $logger->info("created @li_ids new lineitems for picklist $picklist");
 
     # new editor, no transaction needed this time
     $e = new_editor("authtoken" => $auth) or return $e->die_event;
     return $e->die_event unless $e->checkauth;
-    $conn->respond($RETRIEVERS{"lineitem"}->($e, $_, $options)) foreach @li_ids;
+    $conn->respond($RETRIEVERS{"lineitem"}->($e, $_, $opts)) foreach @li_ids;
     $e->disconnect;
 
     undef;
index 74d7e16..b8c5fe6 100644 (file)
@@ -243,3 +243,5 @@ span[name="bib_origin"] img { vertical-align: middle; }
 #acq-po-item-table-items tr td button[name="delete"] { color: #c00; }
 #acq-po-item-table-items tr { margin: 6px 0; }
 #acq-po-item-table-controls { margin-top: 8px; }
+
+#acq-litpager-controls[disabled="true"] { color: #ccc; }
index 1177fd6..6f877b2 100644 (file)
@@ -80,5 +80,6 @@
     'NOT_RECVD': "Not recv'd",
     'PRINT': "Print",
     'INVOICES': "Invoices",
-    'NUM_CLAIMS_EXISTING': "Claims (${0} existing)"
+    'NUM_CLAIMS_EXISTING': "Claims (${0} existing)",
+    'LOAD_TERMS_FIRST': "You can't retrieve records until you've loaded a CSV file\nwith bibliographic IDs in the first column."
 }
diff --git a/Open-ILS/web/js/ui/default/acq/common/li_table_pager.js b/Open-ILS/web/js/ui/default/acq/common/li_table_pager.js
new file mode 100644 (file)
index 0000000..d352e52
--- /dev/null
@@ -0,0 +1,64 @@
+function LiTablePager() {
+    var self = this;
+
+    this.init = function(fetcher, liTable, offset, limit) {
+        this.fetcher = fetcher;
+        this.liTable = liTable;
+        this.limit = limit || 10;
+        this.offset = offset || 0;
+
+        dojo.byId("acq-litpager-controls-prev").onclick =
+            function() { self.go(-1); }
+        dojo.byId("acq-litpager-controls-next").onclick =
+            function() { self.go(1); }
+    };
+
+    this.go = function(n /* generally (-1, 0, 1) */) {
+        if (n) this.offset += n * this.limit;
+
+        this.enableControls(false);
+
+        [this.batch, this.total] = this.fetcher(this.offset, this.limit);
+
+        if (this.batch.length) {
+            this.liTable.reset();
+            this.liTable.show("list");
+            this.batch.forEach(function(li) { self.liTable.addLineitem(li); });
+        }
+
+        this.relabelControls();
+        this.enableControls(true);
+    };
+
+    this.enableControls = function(yes) {
+        dojo.byId("acq-litpager-controls-prev").disabled =
+            (!yes) || this.offset < 1;
+        dojo.byId("acq-litpager-controls-next").disabled =
+            (!yes) || (
+                (typeof(this.total) != "undefined") &&
+                    this.offset + this.limit >= this.total
+            );
+        dojo.attr("acq-litpager-controls", "disabled", String(!yes));
+    }
+
+    this.relabelControls = function() {
+        if (typeof(this.total) != "undefined") {
+            dojo.byId("acq-litpager-controls-total").innerHTML = this.total;
+            openils.Util.show("acq-litpager-controls-total-holder", "inline");
+        } else {
+            openils.Util.hide("acq-litpager-controls-total-holder");
+        }
+
+        if (this.batch && this.batch.length) {
+            dojo.byId("acq-litpager-controls-batch-start").innerHTML =
+                this.offset + 1;
+            dojo.byId("acq-litpager-controls-batch-end").innerHTML =
+                this.offset + this.batch.length;
+            openils.Util.show("acq-litpager-controls-batch-range", "inline");
+        } else {
+            openils.Util.hide("acq-litpager-controls-batch-range");
+        }
+    };
+
+    this.init.apply(this, arguments);
+}
diff --git a/Open-ILS/web/js/ui/default/acq/picklist/from_bib.js b/Open-ILS/web/js/ui/default/acq/picklist/from_bib.js
new file mode 100644 (file)
index 0000000..64befd5
--- /dev/null
@@ -0,0 +1,67 @@
+dojo.require("dijit.form.Button");
+dojo.require("openils.widget.XULTermLoader");
+
+var termLoader = null;
+var liTable = null;
+var pager = null;
+var usingPl = null;
+
+function fetchRecords(offset, limit) {
+    var data = termLoader.attr("value");
+    var results = [];
+    var total = data.length;
+    if (offset < 0 || offset >= data.length) return [results, total];
+
+    progressDialog.show(true);
+    /* notice this call is synchronous */
+    fieldmapper.standardRequest(
+        ["open-ils.acq", "open-ils.acq.biblio.create_by_id"], {
+            "params": [
+                openils.User.authtoken, data.slice(offset, offset + limit), {
+                    "flesh_attrs": true,
+                    "flesh_cancel_reason": true,
+                    "flesh_notes": true,
+                    "reuse_picklist": usingPl
+                }
+            ],
+            "onresponse": function(r) {
+                if (r = openils.Util.readResponse(r)) {
+                    if (typeof(r) != "object")
+                        usingPl = r;
+                    else if (r.classname && r.classname == "jub")
+                        results.push(r);
+                    /* XXX the ML method is buggy and sometimes responds with
+                     * more objects that we don't want, hence the specific
+                     * conditionals above that don't necesarily consume all
+                     * responses. */
+                }
+            }
+        }
+    );
+    progressDialog.hide();
+    return [results, total];
+}
+
+function beginSearch() {
+    var data = termLoader.attr("value");
+    if (!data || !data.length) {
+        alert(localeStrings.LOAD_TERMS_FIRST);
+        return;
+    }
+
+    pager.go(0);
+    openils.Util.hide("acq-frombib-upload-box");
+    openils.Util.show("acq-frombib-reload-box");
+}
+
+function init() {
+    new openils.widget.XULTermLoader(
+        {"parentNode": "acq-frombib-upload"}
+    ).build(function(w) { termLoader = w; });
+    liTable = new AcqLiTable();
+    pager = new LiTablePager(fetchRecords, liTable);
+
+    openils.Util.show("acq-frombib-begin-holder");
+}
+
+openils.Util.addOnLoad(init);
index 63f9cc1..0093f55 100644 (file)
 <!ENTITY staff.main.menu.acq.picklist.accesskey "L">
 <!ENTITY staff.main.menu.acq.bib_search.label "Title Search">
 <!ENTITY staff.main.menu.acq.bib_search.accesskey "T">
+<!ENTITY staff.main.menu.acq.from_bib.label "Import Catalog Records by ID">
+<!ENTITY staff.main.menu.acq.from_bib.accesskey "I">
 <!ENTITY staff.main.menu.acq.unified_search.label "Acquisitions Search">
 <!ENTITY staff.main.menu.acq.unified_search.accesskey "A">
 <!ENTITY staff.main.menu.acq.brief_record.label "New Brief Record">
diff --git a/Open-ILS/web/templates/default/acq/common/li_table_pager.tt2 b/Open-ILS/web/templates/default/acq/common/li_table_pager.tt2
new file mode 100644 (file)
index 0000000..6e30aaa
--- /dev/null
@@ -0,0 +1,17 @@
+<script src="[% ctx.media_prefix %]/js/ui/default/acq/common/li_table_pager.js">
+</script>
+<div id="acq-litpager-controls">
+    <button id="acq-litpager-controls-prev" disabled="disabled">
+        &laquo; Prev
+    </button>
+    <span id="acq-litpager-controls-batch-range" class="hidden">
+        <span id="acq-litpager-controls-batch-start"></span> &mdash;
+        <span id="acq-litpager-controls-batch-end"></span>
+        <span id="acq-litpager-controls-total-holder" class="hidden">
+            of <span id="acq-litpager-controls-total"></span>
+        </span>
+    </span>
+    <button id="acq-litpager-controls-next" disabled="disabled">
+        Next &raquo;
+    </button>
+</div>
diff --git a/Open-ILS/web/templates/default/acq/picklist/from_bib.tt2 b/Open-ILS/web/templates/default/acq/picklist/from_bib.tt2
new file mode 100644 (file)
index 0000000..f916d69
--- /dev/null
@@ -0,0 +1,25 @@
+[% WRAPPER "default/base.tt2" %]
+[% ctx.page_title = "Items by Bibliographic ID" %]
+<script src="[% ctx.media_prefix %]/js/ui/default/acq/picklist/from_bib.js">
+</script>
+<div id="acq-frombib-upload-box">
+    <div class="oils-acq-basic-roomy">
+        Provide one or more CSV files whose first columns
+        contain Evergreen bibliographic record IDs.
+    </div>
+    <div class="oils-acq-basic-roomy">
+        <span id="acq-frombib-upload"></span>
+    </div>
+    <div id="acq-frombib-begin-holder" class="oils-acq-basic-roomy hidden">
+        <button id="acq-frombib-begin" onclick="beginSearch();">
+            Retrieve records
+        </button>
+    </div>
+</div>
+<div id="acq-frombib-reload-box" class="oils-acq-basic-roomy hidden">
+    <button onclick="location.href=location.href;">Begin a new search</button>
+</div>
+<hr />
+[% INCLUDE "default/acq/common/li_table_pager.tt2" %]
+[% INCLUDE "default/acq/common/li_table.tt2" %]
+[% END %]
index 137c9ed..533a6ae 100644 (file)
@@ -768,6 +768,10 @@ main.menu.prototype = {
                 ['oncommand'],
                 function() { open_eg_web_page('acq/search/unified', 'menu.cmd_acq_unified_search.tab'); }
             ],
+            'cmd_acq_from_bib' : [
+                ['oncommand'],
+                function() { open_eg_web_page('acq/picklist/from_bib', 'menu.cmd_acq_from_bib.tab'); }
+            ],
             'cmd_acq_new_brief_record' : [
                 ['oncommand'],
                 function() { open_eg_web_page('acq/picklist/brief_record', 'menu.cmd_acq_new_brief_record.tab'); }
index 014e08b..231c7fe 100644 (file)
@@ -85,6 +85,7 @@
     <command id="cmd_acq_user_requests" />
     <command id="cmd_acq_bib_search" />
     <command id="cmd_acq_unified_search" />
+    <command id="cmd_acq_from_bib" />
     <command id="cmd_acq_new_brief_record" />
     <command id="cmd_acq_view_fund" />
     <command id="cmd_acq_view_funding_source" />
     <menupopup id="main.menu.acq.popup">
         <menuitem label="&staff.main.menu.acq.unified_search.label;" accesskey="&staff.main.menu.acq.unified_search.accesskey;" command="cmd_acq_unified_search"/>
         <menuitem label="&staff.main.menu.acq.bib_search.label;" accesskey="&staff.main.menu.acq.bib_search.accesskey;" command="cmd_acq_bib_search"/>
+        <menuitem label="&staff.main.menu.acq.from_bib.label;" accesskey="&staff.main.menu.acq.from_bib.accesskey;" command="cmd_acq_from_bib"/>
         <menuitem label="&staff.main.menu.acq.po.label;" accesskey="&staff.main.menu.acq.po.accesskey;" command="cmd_acq_view_po" />
         <menuitem label="&staff.main.menu.acq.picklist.label;" accesskey="&staff.main.menu.acq.picklist.accesskey;" command="cmd_acq_view_picklist"/>
         <menuseparator />
index c2d7127..f592087 100644 (file)
@@ -227,6 +227,7 @@ menu.cmd_local_admin_transit_list.tab=Transits
 menu.cmd_acq_create_invoice.tab=New Invoice
 menu.cmd_acq_view_picklist.tab=Selection Lists
 menu.cmd_acq_bib_search.tab=Title Search
+menu.cmd_acq_from_bib.tab=Import Catalog Records
 menu.cmd_acq_unified_search.tab=Acquisitions Search
 menu.cmd_acq_upload.tab=Load Order Record
 menu.cmd_acq_new_brief_record.tab=New Brief Record