From b6ffd9ab6969a430cae03e399f2233df30948e7c Mon Sep 17 00:00:00 2001 From: senator <senator@dcc99617-32d9-48b4-a31d-7c20da2025e4> Date: Sat, 20 Nov 2010 22:55:56 +0000 Subject: [PATCH] Serials: a routing list editor for the alternate serials control view Now we still need to teach the receive interface to offer to print the list when there is one, but I think that should be relatively simple. git-svn-id: svn://svn.open-ils.org/ILS/trunk@18812 dcc99617-32d9-48b4-a31d-7c20da2025e4 --- Open-ILS/examples/fm_IDL.xml | 14 ++ Open-ILS/src/perlmods/OpenILS/Application/Actor.pm | 17 +- .../src/perlmods/OpenILS/Application/Serial.pm | 130 +++++++++++++ Open-ILS/web/js/ui/default/serial/list_stream.js | 216 +++++++++++++++++++++ .../web/templates/default/serial/list_stream.tt2 | 88 ++++++++- 5 files changed, 457 insertions(+), 8 deletions(-) diff --git a/Open-ILS/examples/fm_IDL.xml b/Open-ILS/examples/fm_IDL.xml index cb2a12650e..e00b24c1fa 100644 --- a/Open-ILS/examples/fm_IDL.xml +++ b/Open-ILS/examples/fm_IDL.xml @@ -3400,6 +3400,20 @@ SELECT usr, <link field="reader" reltype="has_a" key="id" map="" class="au"/> </links> <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1"> + <actions> + <create permission="ADMIN_SERIAL_STREAM"> + <context link="stream" jump="distribution" field="holding_lib" /> + </create> + <retrieve permission="RECEIVE_SERIAL"> + <context link="stream" jump="distribution" field="holding_lib" /> + </retrieve> + <update permission="ADMIN_SERIAL_STREAM"> + <context link="stream" jump="distribution" field="holding_lib" /> + </update> + <delete permission="ADMIN_SERIAL_STREAM"> + <context link="stream" jump="distribution" field="holding_lib" /> + </delete> + </actions> </permacrud> </class> diff --git a/Open-ILS/src/perlmods/OpenILS/Application/Actor.pm b/Open-ILS/src/perlmods/OpenILS/Application/Actor.pm index 2fcacd25e2..bb33e89058 100644 --- a/Open-ILS/src/perlmods/OpenILS/Application/Actor.pm +++ b/Open-ILS/src/perlmods/OpenILS/Application/Actor.pm @@ -456,14 +456,19 @@ sub apply_invalid_addr_penalty { sub flesh_user { my $id = shift; my $e = shift; - return new_flesh_user($id, [ + my $home_ou = shift; + + my $fields = [ "cards", "card", "standing_penalties", "addresses", "billing_address", "mailing_address", - "stat_cat_entries" ], $e ); + "stat_cat_entries" + ]; + push @$fields, "home_ou" if $home_ou; + return new_flesh_user($id, $fields, $e ); } @@ -1024,7 +1029,7 @@ __PACKAGE__->register_method( api_name => "open-ils.actor.user.fleshed.retrieve_by_barcode",); sub user_retrieve_by_barcode { - my($self, $client, $auth, $barcode) = @_; + my($self, $client, $auth, $barcode, $flesh_home_ou) = @_; my $e = new_editor(authtoken => $auth); return $e->event unless $e->checkauth; @@ -1032,8 +1037,10 @@ sub user_retrieve_by_barcode { my $card = $e->search_actor_card({barcode => $barcode})->[0] or return $e->event; - my $user = flesh_user($card->usr, $e); - return $e->event unless $e->allowed('VIEW_USER', $user->home_ou); + my $user = flesh_user($card->usr, $e, $flesh_home_ou); + return $e->event unless $e->allowed( + "VIEW_USER", $flesh_home_ou ? $user->home_ou->id : $user->home_ou + ); return $user; } diff --git a/Open-ILS/src/perlmods/OpenILS/Application/Serial.pm b/Open-ILS/src/perlmods/OpenILS/Application/Serial.pm index 3623fb5e36..4393d40c89 100644 --- a/Open-ILS/src/perlmods/OpenILS/Application/Serial.pm +++ b/Open-ILS/src/perlmods/OpenILS/Application/Serial.pm @@ -2794,4 +2794,134 @@ sub get_receivable_issuances { undef; } + +__PACKAGE__->register_method( + "method" => "get_routing_list_users", + "api_name" => "open-ils.serial.routing_list_users.fleshed_and_ordered", + "stream" => 1, + "signature" => { + "desc" => "Return all routing list users with reader fleshed " . + "(with card and home_ou) for a given stream ID, sorted by pos", + "params" => [ + {"desc" => "Authtoken", "type" => "string"}, + {"desc" => "Stream ID", "type" => "number"}, + ], + "return" => { + "desc" => "Stream of routing list users", "type" => "object", + "class" => "srlu" + } + } +); + +sub get_routing_list_users { + my ($self, $client, $auth, $stream_id) = @_; + + return undef unless $stream_id = int $stream_id; # sic, assignment + + my $e = new_editor("authtoken" => $auth); + return $e->die_event unless $e->checkauth; + + my $users = $e->search_serial_routing_list_user([ + {"stream" => $stream_id}, { + "order_by" => {"srlu" => "pos"}, + "flesh" => 2, + "flesh_fields" => { + "srlu" => [qw/reader stream/], + "au" => [qw/card home_ou/], + "sstr" => ["distribution"] + } + } + ]) or return $e->die_event; + + return undef unless @$users; + + # The ADMIN_SERIAL_STREAM permission is used simply to avoid the + # need for any new permission. The context OU will be the same + # for every result of the above query, so we need only check once. + return $e->die_event unless $e->allowed( + "ADMIN_SERIAL_STREAM", $users->[0]->stream->distribution->holding_lib + ); + + $e->disconnect; + + # Now we can strip the stream/distribution info (only used for perm + # checking) and send back the srlu's to the caller. + $client->respond($_) for map { $_->stream($_->stream->id); $_ } @$users; + + undef; +} + + +__PACKAGE__->register_method( + "method" => "replace_routing_list_users", + "api_name" => "open-ils.serial.routing_list_users.replace", + "signature" => { + "desc" => "Replace all routing list users on the specified streams " . + "with those in the list argument", + "params" => [ + {"desc" => "Authtoken", "type" => "string"}, + {"desc" => "List of srlu objects", "type" => "array"}, + ], + "return" => { + "desc" => "event on failure, undef on success" + } + } +); + +sub replace_routing_list_users { + my ($self, $client, $auth, $users) = @_; + + return undef unless ref $users eq "ARRAY"; + + if (grep { ref $_ ne "Fieldmapper::serial::routing_list_user" } @$users) { + return new OpenILS::Event("BAD_PARAMS", "note" => "Only srlu objects"); + } + + my $e = new_editor("authtoken" => $auth, "xact" => 1); + return $e->die_event unless $e->checkauth; + + my %streams_ok = (); + my $pos = 0; + + foreach my $user (@$users) { + unless (exists $streams_ok{$user->stream}) { + my $stream = $e->retrieve_serial_stream([ + $user->stream, { + "flesh" => 1, + "flesh_fields" => {"sstr" => ["distribution"]} + } + ]) or return $e->die_event; + $e->allowed( + "ADMIN_SERIAL_STREAM", $stream->distribution->holding_lib + ) or return $e->die_event; + + my $to_delete = $e->search_serial_routing_list_user( + {"stream" => $user->stream} + ) or return $e->die_event; + + $logger->info( + "Deleting srlu: [" . + join(", ", map { $_->id; } @$to_delete) . + "]" + ); + + foreach (@$to_delete) { + $e->delete_serial_routing_list_user($_) or + return $e->die_event; + } + + $streams_ok{$user->stream} = 1; + } + + next if $user->isdeleted; + + $user->clear_id; + $user->pos($pos++); + $e->create_serial_routing_list_user($user) or return $e->die_event; + } + + $e->commit or return $e->die_event; + undef; +} + 1; diff --git a/Open-ILS/web/js/ui/default/serial/list_stream.js b/Open-ILS/web/js/ui/default/serial/list_stream.js index befc46e434..403b317120 100644 --- a/Open-ILS/web/js/ui/default/serial/list_stream.js +++ b/Open-ILS/web/js/ui/default/serial/list_stream.js @@ -1,6 +1,8 @@ dojo.require("dijit.form.Button"); +dojo.require("dijit.form.RadioButton"); dojo.require("dijit.form.NumberSpinner"); dojo.require("dijit.form.TextBox"); +dojo.require("dojo.dnd.Source"); dojo.require("openils.widget.AutoGrid"); dojo.require("openils.widget.ProgressDialog"); dojo.require("openils.PermaCrud"); @@ -8,6 +10,7 @@ dojo.require("openils.CGI"); var pcrud; var dist_id; +var rlu_editor; var cgi; function format_routing_label(routing_label) { @@ -72,10 +75,223 @@ function create_many_streams(fields) { ); } +function RLUEditor() { + var self = this; + + function _reader_xor_dept_toggle(value) { + var reader = dijit.byId("reader"); + var department = dijit.byId("department"); + + if (this.id.match(/\w+$/).pop() == "reader") + _reader_toggle(value, reader, department); + else + _department_toggle(value, reader, department); + } + + function _reader_toggle(value, reader, department) { + if (value) { + reader.attr("disabled", false); + department.attr("disabled", true); + setTimeout(function() { reader.focus(); }, 125); + } + } + + function _department_toggle(value, reader, department) { + if (value) { + reader.attr("disabled", true); + department.attr("disabled", false); + setTimeout(function() { department.focus(); }, 125); + } + } + + this.user_to_source_entry = function(user) { + var node = dojo.create("li"); + var s; + if (user.reader()) { + s = dojo.string.substitute( + this.template.reader, [ + user.reader().card().barcode(), + user.reader().family_name(), + user.reader().first_given_name(), + user.reader().second_given_name(), + user.reader().home_ou().shortname() + ].map(function(o) { return o == null ? "" : o; }) + ); + } else { + s = dojo.string.substitute( + this.template.department, [user.department()] + ); + } + + if (user.note()) { + s += dojo.string.substitute(this.template.note, [user.note()]); + } + + node.innerHTML = " " + s; + + dojo.create( + "a", { + "href": "javascript:void(0);", + "onclick": function() { self.toggle_deleted(node); }, + "innerHTML": this.template.remove + }, node, "first" + ); + + node._user = user; + return node; + }; + + this.toggle_deleted = function(node) { + if (node._user.isdeleted()) { + dojo.style(node, "textDecoration", "none"); + node._user.isdeleted(false); + } else { + dojo.style(node, "textDecoration", "line-through"); + node._user.isdeleted(true); + } + }; + + this.new_user = function() { + var form = this.dialog.attr("value"); + var user = new fieldmapper.srlu(); + user.isnew(true); + user.stream(this.stream); + + if (form.note) + user.note(form.note); + + if (form.department) { + user.department(form.department); + } else if (form.reader) { + this.add_button.attr("disabled", true); + fieldmapper.standardRequest( + ["open-ils.actor", + "open-ils.actor.user.fleshed.retrieve_by_barcode"], { + "params": [openils.User.authtoken, form.reader, true], + "timeout": 10, /* sync */ + "onresponse": function(r) { + if (r = openils.Util.readResponse(r)) { + user.reader(r); + } + } + } + ); + this.add_button.attr("disabled", false); + } else { + alert("Provide either a reader or a department."); /* XXX i18n */ + return; + } + + ["reader", "department", "note"].forEach( + function(s) { dijit.byId(s).attr("value", ""); } + ); + + this.source.insertNodes(false, [self.user_to_source_entry(user)]); + } + + this.show = function() { + if (sstr_grid.getSelectedRows().length != 1) { + alert( + "Use the checkboxes to select exactly one stream " + + "for this operation." /* XXX i18n */ + ); + } else { + /* AutoGrid.getSelectedItems() yields a weird, non-FM object */ + this.stream = sstr_grid.getSelectedItems()[0].id[0]; + + this.source.selectAll(); + this.source.deleteSelectedNodes(); + this.source.clearItems(); + + this.dialog.show(); + + fieldmapper.standardRequest( + ["open-ils.serial", + "open-ils.serial.routing_list_users.fleshed_and_ordered"], { + "params": [openils.User.authtoken, this.stream], + "async": true, + "onresponse": function(r) { + if (r = openils.Util.readResponse(r)) { + self.source.insertNodes( + false, [self.user_to_source_entry(r)] + ); + } + }, + "oncomplete": function() { + setTimeout( + function() { self.save_button.focus(); }, 125 + ); + } + } + ); + } + }; + + this.save = function() { + var obj_list = this.source.getAllNodes().map( + function(node) { + var obj = node._user; + if (obj.reader()) + obj.reader(obj.reader().id()); + + return obj; + } + ); + + this.save_button.attr("disabled", true); + + /* pcrud.apply *almost* could have handled this, but there's a reason + * it doesn't, and it has to do with the unique key constraint on the + * pos field in srlu objects. + */ + try { + fieldmapper.standardRequest( + /* This method will set pos in ascending order. */ + ["open-ils.serial", + "open-ils.serial.routing_list_users.replace"], { + "params": [openils.User.authtoken, obj_list], + "timeout": 10, /* sync */ + "oncomplete": function(r) { + openils.Util.readResponse(r); /* display exceptions */ + } + } + ); + } catch (E) { + alert(E); /* XXX i18n */ + } + + this.save_button.attr("disabled", false); + }; + + this._init = function(dialog) { + this.dialog = dijit.byId("routing_list_dialog"); + this.source = routing_list_source; + + this.template = {}; + ["reader", "department", "note", "remove"].forEach( + function(n) { + self.template[n] = + dojo.byId("routing_list_user_template_" + n).innerHTML; + } + ); + + this.add_button = dijit.byId("routing_list_add_button"); + this.save_button = dijit.byId("routing_list_save_button"); + + dijit.byId("reader_xor_dept-reader").onChange = + _reader_xor_dept_toggle; + dijit.byId("reader_xor_dept-department").onChange = + _reader_xor_dept_toggle; + }; + + this._init.apply(this, arguments); +} + openils.Util.addOnLoad( function() { cgi = new openils.CGI(); pcrud = new openils.PermaCrud(); + rlu_editor = new RLUEditor(); dist_id = cgi.param("distribution"); load_sdist_display(); diff --git a/Open-ILS/web/templates/default/serial/list_stream.tt2 b/Open-ILS/web/templates/default/serial/list_stream.tt2 index 660c761296..52e641c70e 100644 --- a/Open-ILS/web/templates/default/serial/list_stream.tt2 +++ b/Open-ILS/web/templates/default/serial/list_stream.tt2 @@ -1,5 +1,11 @@ [% WRAPPER default/base.tt2 %] [% ctx.page_title = "Streams" %] +<style type="text/css"> + #new-srlu-table { width: 100%; } + #new-srlu-table th { text-align: left; padding-left: 1em; } + #new-srlu-table td { text-align: center; padding-right: 1em; } + #list-source { border: 1px #666 dashed; } +</style> <div dojoType="dijit.layout.ContentPane" layoutAlign="client"> <div dojoType="dijit.layout.ContentPane" layoutAlign="top" class="oils-header-panel"> @@ -11,6 +17,9 @@ onClick="multi_stream_dialog.show()"> Create Many Streams </button> + <button dojoType="dijit.form.Button" onClick="rlu_editor.show()"> + Routing List For Selected Stream + </button> <button dojoType="dijit.form.Button" onClick="sstr_grid.refresh()">Refresh Grid</button> <button dojoType="dijit.form.Button" @@ -25,20 +34,93 @@ <table jsId="sstr_grid" dojoType="openils.widget.AutoGrid" query="{id: '*'}" + fieldOrder="['id','distribution','routing_label']" suppressFields="['distribution']" + showSequenceFields="true" fmClass="sstr" - defaultCellWidth="'auto'" showPaginator="true" editOnEnter="true"> <thead> <tr> - <th field="routing_label" formatter="format_routing_label"> - </th> + <th width="90%" field="routing_label" + formatter="format_routing_label"></th> </tr> </thead> </table> </div> <div class="hidden"> + + <div id="routing_list_user_template_reader"> + Reader: ${0} / ${1}, ${2} ${3} (${4}) + </div> + <div id="routing_list_user_template_department">Department: ${0}</div> + <div id="routing_list_user_template_note"><br /> <em>${0}</em></div> + <div id="routing_list_user_template_remove">[X]</div> + + <div dojoType="dijit.Dialog" id="routing_list_dialog" + execute="rlu_editor.save()" title="Manage Routing List"> + <ol id="list-source" dojoType="dojo.dnd.Source" + jsId="routing_list_source"></ol> + <table id="new-srlu-table"> + <tbody> + <tr> + <td> + <input type="radio" name="reader_xor_dept" + dojoType="dijit.form.RadioButton" + value="reader" id="reader_xor_dept-reader" /> + </td> + <th> + <label for="reader_xor_dept-reader"> + Reader (barcode): + </label> + </th> + <td> + <input dojoType="dijit.form.TextBox" id="reader" + name="reader" disabled="disabled" /> + </td> + <td rowspan="3"> + <button dojoType="dijit.form.Button" + id="routing_list_add_button" + onClick="rlu_editor.new_user()">Add</button> + </td> + </tr> + <tr> + <td> + <input type="radio" name="reader_xor_dept" + dojoType="dijit.form.RadioButton" + value="department" + id="reader_xor_dept-department" /> + </td> + <th> + <label for="reader_xor_dept-department"> + Department: + </label> + </th> + <td> + <input dojoType="dijit.form.TextBox" id="department" + name="department" disabled="disabled" /> + </td> + </tr> + <tr> + <td></td> + <th><label for="note">Note:</label></th> + <td> + <input id="note" name="note" + dojoType="dijit.form.TextBox" /> + </td> + </tr> + <tr> + <td colspan="4" style="padding-top: 1em;"> + <button id="routing_list_save_button" + dojoType="dijit.form.Button" type="submit"> + Save Changes + </button> + </td> + </td> + </tbody> + </table> + </div> + <div dojoType="dijit.Dialog" execute="create_many_streams(arguments[0]);" title="Create Streams" -- 2.11.0