From 016eeaa44f40434f7f29446f79855f2b3910e22c Mon Sep 17 00:00:00 2001 From: senator Date: Wed, 5 Jan 2011 19:31:13 +0000 Subject: [PATCH] Serials: provide a way for users to create and modify serial items Generally, these are created by caption and pattern-based prediction, and modified by receiving. However, some circumstances will warrant manually creating items, and there need to be significant bumper rails around that process, as it's otherwise easy to make items that don't respect certain assumptions of the serials logic (you can still accomplish this if you're trying, but it's harder to do than it would be if you had the anarchy of a more straightforward AutoGrid interface). Access the new interface by clicking any link in the Label column of the Issuances tab of the Alternate Serials Control Subscription Details inteface. git-svn-id: svn://svn.open-ils.org/ILS/trunk@19125 dcc99617-32d9-48b4-a31d-7c20da2025e4 --- Open-ILS/examples/fm_IDL.xml | 16 +- .../src/perlmods/OpenILS/Application/Serial.pm | 197 +++++++++++++++++++-- Open-ILS/web/js/dojo/openils/Util.js | 32 ++++ Open-ILS/web/js/ui/default/serial/list_item.js | 171 ++++++++++++++++++ Open-ILS/web/js/ui/default/serial/subscription.js | 23 ++- .../web/templates/default/serial/list_item.tt2 | 123 +++++++++++++ .../web/templates/default/serial/subscription.tt2 | 2 +- .../default/serial/subscription/distribution.tt2 | 2 +- .../default/serial/subscription/issuance.tt2 | 6 + 9 files changed, 546 insertions(+), 26 deletions(-) create mode 100644 Open-ILS/web/js/ui/default/serial/list_item.js create mode 100644 Open-ILS/web/templates/default/serial/list_item.tt2 diff --git a/Open-ILS/examples/fm_IDL.xml b/Open-ILS/examples/fm_IDL.xml index c194cd5bc..88838ee01 100644 --- a/Open-ILS/examples/fm_IDL.xml +++ b/Open-ILS/examples/fm_IDL.xml @@ -3550,10 +3550,18 @@ SELECT usr, - - - - + + + + + + + + + + + + diff --git a/Open-ILS/src/perlmods/OpenILS/Application/Serial.pm b/Open-ILS/src/perlmods/OpenILS/Application/Serial.pm index 6607b0822..bf2e9948f 100644 --- a/Open-ILS/src/perlmods/OpenILS/Application/Serial.pm +++ b/Open-ILS/src/perlmods/OpenILS/Application/Serial.pm @@ -87,6 +87,133 @@ sub _get_mvr { # item methods # __PACKAGE__->register_method( + method => "create_item_safely", + api_name => "open-ils.serial.item.create", + api_level => 1, + stream => 1, + argc => 3, + signature => { + desc => q/Creates any number of items, respecting only a few of the + submitted fields, as the user shouldn't be able to freely set certain + ones/, + params => [ + {name=> "authtoken", desc => "Authtoken for current user session", + type => "string"}, + {name => "item", desc => "serial item", + type => "object", class => "sitem"}, + {name => "count", + desc => "optional: how many items to make " . + "(default 1; 1-100 permitted)", + type => "number"} + ], + return => { + desc => "created items (a stream of them)", + type => "object", class => "sitem" + } + } +); +__PACKAGE__->register_method( + method => "update_item_safely", + api_name => "open-ils.serial.item.update", + api_level => 1, + stream => 1, + argc => 2, + signature => { + desc => q/Edit a serial item, respecting only a few of the + submitted fields, as the user shouldn't be able to freely set certain + ones/, + params => [ + {name=> "authtoken", desc => "Authtoken for current user session", + type => "string"}, + {name => "item", desc => "serial item", + type => "object", class => "sitem"}, + ], + return => { + desc => "created item", type => "object", class => "sitem" + } + } +); + +sub _set_safe_item_fields { + my $dest = shift; + my $source = shift; + my $requestor_id = shift; + # extra fields remain in @_ + + $dest->edit_date("now"); + $dest->editor($requestor_id); + + my @fields = qw/date_expected date_received status/; + + for my $field (@fields, @_) { + $dest->$field($source->$field); + } +} + +sub update_item_safely { + my ($self, $client, $auth, $item) = @_; + + my $e = new_editor("xact" => 1, "authtoken" => $auth); + $e->checkauth or return $e->die_event; + + my $orig = $e->retrieve_serial_item([ + $item->id, { + "flesh" => 2, "flesh_fields" => { + "sitem" => ["stream"], "sstr" => ["distribution"] + } + } + ]) or return $e->die_event; + + return $e->die_event unless $e->allowed( + "ADMIN_SERIAL_ITEM", $orig->stream->distribution->holding_lib + ); + + _set_safe_item_fields($orig, $item, $e->requestor->id); + $e->update_serial_item($orig) or return $e->die_event; + + $client->respond($e->retrieve_serial_item($item->id)); + $e->commit or return $e->die_event; + undef; +} + +sub create_item_safely { + my ($self, $client, $auth, $item, $count) = @_; + + $count = int $count; + $count ||= 1; + return new OpenILS::Event( + "BAD_PARAMS", note => "Count should be from 1 to 100" + ) unless $count >= 1 and $count <= 100; + + my $e = new_editor("xact" => 1, "authtoken" => $auth); + $e->checkauth or return $e->die_event; + + my $stream = $e->retrieve_serial_stream([ + $item->stream, { + "flesh" => 1, "flesh_fields" => {"sstr" => ["distribution"]} + } + ]) or return $e->die_event; + + return $e->die_event unless $e->allowed( + "ADMIN_SERIAL_ITEM", $stream->distribution->holding_lib + ); + + for (my $i = 0; $i < $count; $i++) { + my $actual = new Fieldmapper::serial::item; + $actual->creator($e->requestor->id); + _set_safe_item_fields( + $actual, $item, $e->requestor->id, "issuance", "stream" + ); + + $e->create_serial_item($actual) or return $e->die_event; + $client->respond($e->data); + } + + $e->commit or return $e->die_event; + undef; +} + +__PACKAGE__->register_method( method => 'fleshed_item_alter', api_name => 'open-ils.serial.item.fleshed.batch.update', api_level => 1, @@ -2697,7 +2824,7 @@ sub bre_by_identifier { } __PACKAGE__->register_method( - "method" => "get_receivable_items", + "method" => "get_items_by", "api_name" => "open-ils.serial.items.receivable.by_subscription", "stream" => 1, "signature" => { @@ -2708,13 +2835,13 @@ __PACKAGE__->register_method( ], "return" => { "desc" => "All receivable items under a given subscription", - "type" => "object" + "type" => "object", "class" => "sitem" } } ); __PACKAGE__->register_method( - "method" => "get_receivable_items", + "method" => "get_items_by", "api_name" => "open-ils.serial.items.receivable.by_issuance", "stream" => 1, "signature" => { @@ -2725,52 +2852,90 @@ __PACKAGE__->register_method( ], "return" => { "desc" => "All receivable items under a given issuance", - "type" => "object" + "type" => "object", "class" => "sitem" + } + } +); + +__PACKAGE__->register_method( + "method" => "get_items_by", + "api_name" => "open-ils.serial.items.by_issuance", + "stream" => 1, + "signature" => { + "desc" => "Return all items under a given issuance", + "params" => [ + {"desc" => "Authtoken", "type" => "string"}, + {"desc" => "Issuance ID", "type" => "number"}, + ], + "return" => { + "desc" => "All items under a given issuance", + "type" => "object", "class" => "sitem" } } ); -sub get_receivable_items { - my ($self, $client, $auth, $term) = @_; +sub get_items_by { + my ($self, $client, $auth, $term, $opts) = @_; + + # Not to be used in the json_query, but after limiting by perm check. + $opts = {} unless ref $opts eq "HASH"; + $opts->{"limit"} ||= 10000; # some existing users may want all results + $opts->{"offset"} ||= 0; + $opts->{"limit"} = int($opts->{"limit"}); + $opts->{"offset"} = int($opts->{"offset"}); my $e = new_editor("authtoken" => $auth); return $e->die_event unless $e->checkauth; - # XXX permissions - my $by = ($self->api_name =~ /by_(\w+)$/)[0]; + my $receivable = ($self->api_name =~ /receivable/); my %where = ( "issuance" => {"issuance" => $term}, "subscription" => {"+siss" => {"subscription" => $term}} ); - my $item_ids = $e->json_query( + my $item_rows = $e->json_query( { - "select" => {"sitem" => ["id"]}, - "from" => {"sitem" => "siss"}, + "select" => {"sitem" => ["id"], "sdist" => ["holding_lib"]}, + "from" => { + "sitem" => { + "siss" => {}, + "sstr" => {"join" => {"sdist" => {}}} + } + }, "where" => { - %{$where{$by}}, "date_received" => undef + %{$where{$by}}, $receivable ? ("date_received" => undef) : () }, "order_by" => {"sitem" => ["id"]} } ) or return $e->die_event; - return undef unless @$item_ids; + return undef unless @$item_rows; + + my $skipped = 0; + my $returned = 0; + foreach (@$item_rows) { + last if $returned >= $opts->{"limit"}; + next unless $e->allowed("RECEIVE_SERIAL", $_->{"holding_lib"}); + if ($skipped < $opts->{"offset"}) { + $skipped++; + next; + } - foreach (map { $_->{"id"} } @$item_ids) { $client->respond( $e->retrieve_serial_item([ - $_, { + $_->{"id"}, { "flesh" => 3, "flesh_fields" => { - "sitem" => ["stream", "issuance"], + "sitem" => [qw/stream issuance unit creator editor/], "sstr" => ["distribution"], "sdist" => ["holding_lib"] } } ]) ); + $returned++; } $e->disconnect; diff --git a/Open-ILS/web/js/dojo/openils/Util.js b/Open-ILS/web/js/dojo/openils/Util.js index ace522e62..faae916ae 100644 --- a/Open-ILS/web/js/dojo/openils/Util.js +++ b/Open-ILS/web/js/dojo/openils/Util.js @@ -53,6 +53,38 @@ if(!dojo._hasResource["openils.Util"]) { ); }; + openils.Util._userFullNameFields = [ + "prefix", "first_given_name", "second_given_name", + "family_name", "suffix", "alias", "usrname" + ]; + + /** + * Return an array of all the name-related attributes, with nulls replaced + * by empty strings, from a given actor.usr fieldmapper object, to be used + * as the arguments to any string formatting function that wants them. + * Code to do this is duplicated all over the place and should be + * simplified as we go. + */ + openils.Util.userFullName = function(user) { + return dojo.map( + openils.Util._userFullNameFields, + function(a) { return user[a]() || ""; } + ); + }; + + /** + * Same as openils.Util.userFullName, but with a hash of results instead + * of an array (dojo.string.substitute(), for example, can use this too). + */ + openils.Util.userFullNameHash = function(user) { + var hash = {}; + dojo.forEach( + openils.Util._userFullNameFields, + function(a) { hash[a] = user[a]() || ""; } + ); + return hash; + }; + /** * Wrapper for dojo.addOnLoad that verifies a valid login session is active * before adding the function to the onload set diff --git a/Open-ILS/web/js/ui/default/serial/list_item.js b/Open-ILS/web/js/ui/default/serial/list_item.js new file mode 100644 index 000000000..8455c82d8 --- /dev/null +++ b/Open-ILS/web/js/ui/default/serial/list_item.js @@ -0,0 +1,171 @@ +dojo.require("dijit.form.Button"); +dojo.require("dijit.form.DateTextBox"); +dojo.require("dijit.form.TextBox"); +dojo.require("dijit.form.NumberSpinner"); +dojo.require("dijit.form.FilteringSelect"); +dojo.require("openils.widget.PCrudAutocompleteBox"); +dojo.require("openils.widget.AutoGrid"); +dojo.require("openils.widget.ProgressDialog"); +dojo.require("openils.PermaCrud"); +dojo.require("openils.CGI"); + +var pcrud, cgi, issuance_id; +var sitem_cache = {}; + +function load_sitem_grid() { + sitem_grid.overrideEditWidgets.status = status_selector; + sitem_grid.overrideEditWidgets.status.shove = {}; /* sic */ + + sitem_grid.dataLoader = sitem_data_loader; + sitem_grid.dataLoader(); +} + +function load_siss_display() { + pcrud.retrieve( + "siss", issuance_id, { + "onresponse": function(r) { + if (r = openils.Util.readResponse(r)) { + var link = dojo.byId("siss_label_here"); + link.onclick = function() { + location.href = oilsBasePath + + "/eg/serial/subscription?id=" + + r.subscription() + "&tab=issuances"; + } + link.innerHTML = r.label(); + prepare_create_dialog(r.subscription()); + } + } + } + ); +} + +function sitem_data_loader() { + sitem_grid.resetStore(); + sitem_grid.showLoadProgressIndicator(); + + fieldmapper.standardRequest( + ["open-ils.serial", "open-ils.serial.items.by_issuance"], { + "params": [ + openils.User.authtoken, issuance_id, { + "limit": sitem_grid.displayLimit, + "offset": sitem_grid.displayOffset + } + ], + "async": true, + "onresponse": function(r) { + var item = openils.Util.readResponse(r); + sitem_cache[item.id()] = item; + sitem_grid.store.newItem(item.toStoreItem()); + }, + "oncomplete": function(r) { + sitem_grid.hideLoadProgressIndicator(); + } + } + ); +} + +function _get_field(store, item, field) { + if (!item) return ""; + var id = store.getValue(item, "id"); + return sitem_cache[id][field](); +} + +/* create the get_foo() functions used by our AutoGrid */ +["creator", "editor", "stream", "unit"].forEach( + function(field) { + window["get_" + field] = function(row_index, item) { + return _get_field(this.grid.store, item, field); + }; + } +); + +function format_user(user) { + return user ? user.usrname() : ""; +} + +function format_stream(stream) { + return stream ? (stream.routing_label() || "[None]") : ""; /* XXX i18n */ +} + +function format_unit(unit) { + return unit ? (unit.barcode() || "[None]") : ""; /* XXX i18n */ +} + +function update_sitem_safely(obj, opts, edit_pane) { + fieldmapper.standardRequest( + ["open-ils.serial", "open-ils.serial.item.update"], { + "params": [openils.User.authtoken, obj], + "async": true, + "oncomplete": function(r) { + if (r = openils.Util.readResponse(r)) { + if (edit_pane.onPostSubmit) + edit_pane.onPostSubmit(null, [r]); + } + } + } + ); +} + +function prepare_create_dialog(sub_id) { + pcrud.search( + "sdist", {"subscription": sub_id}, { + "id_list": true, + "async": true, + "oncomplete": function(r) { + if (r = openils.Util.readResponse(r)) { + new openils.widget.PCrudAutocompleteBox({ + "fmclass": "sstr", + "searchAttr": "routing_label", + "hasDownArrow": true, + "name": "stream", + "store_options": { + "base_filter": {"distribution": r}, + "honor_retrieve_all": true + } + }, "stream_selector"); + } + } + } + ); +} + +function create_new_items(form) { + var item = new sitem(); + + item.issuance(issuance_id); /* from global */ + item.stream(form.stream); + item.status(form.status); + item.date_expected( + form.date_expected ? + dojo.date.stamp.toISOString(form.date_expected) : null + ); + item.date_received( + form.date_received ? + dojo.date.stamp.toISOString(form.date_received) : null + ); + + progress_dialog.show(true); + fieldmapper.standardRequest( + ["open-ils.serial", "open-ils.serial.item.create"], { + "params": [openils.User.authtoken, item, form.count], + "async": true, + "oncomplete": function(r) { + progress_dialog.hide(); + if (r = openils.Util.readResponse(r)) { + sitem_grid.refresh(); + } + } + } + ); +} + +openils.Util.addOnLoad( + function() { + cgi = new openils.CGI(); + pcrud = new openils.PermaCrud(); + + issuance_id = cgi.param("issuance"); + load_siss_display(); + load_sitem_grid(); + } +); diff --git a/Open-ILS/web/js/ui/default/serial/subscription.js b/Open-ILS/web/js/ui/default/serial/subscription.js index 873b4d411..a37609bb1 100644 --- a/Open-ILS/web/js/ui/default/serial/subscription.js +++ b/Open-ILS/web/js/ui/default/serial/subscription.js @@ -104,7 +104,7 @@ function format_org_unit(aou_id) { return aou_id ? aou.findOrgUnit(aou_id).shortname() : ""; } -function get_sdist(rowIndex, item) { +function get_id_and_label(rowIndex, item) { if (!item) return {"id": "", "label": ""}; return { "id": this.grid.store.getValue(item, "id"), @@ -112,6 +112,13 @@ function get_sdist(rowIndex, item) { }; } +function format_siss_label(blob) { + if (!blob.id) return ""; + return "" + (blob.label ? blob.label : "[None]") + ""; /* XXX i18n */ +} + function format_sdist_label(blob) { if (!blob.id) return ""; var link = " + + + + + + + + + + + + + + + + +[% END %] diff --git a/Open-ILS/web/templates/default/serial/subscription.tt2 b/Open-ILS/web/templates/default/serial/subscription.tt2 index 011229299..6c40ed04a 100644 --- a/Open-ILS/web/templates/default/serial/subscription.tt2 +++ b/Open-ILS/web/templates/default/serial/subscription.tt2 @@ -88,7 +88,7 @@ -