use warnings;
use OpenSRF::AppSession;
+use OpenSRF::Utils::Logger qw/:logger/;
use OpenILS::Event;
use OpenILS::Utils::CStoreEditor q/:funcs/;
use OpenILS::Utils::Fieldmapper;
}
);
+__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;
"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;
#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; }
'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."
}
--- /dev/null
+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);
+}
--- /dev/null
+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);
<!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">
--- /dev/null
+<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">
+ « Prev
+ </button>
+ <span id="acq-litpager-controls-batch-range" class="hidden">
+ <span id="acq-litpager-controls-batch-start"></span> —
+ <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 »
+ </button>
+</div>
--- /dev/null
+[% 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 %]
['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'); }
<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 />
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