<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>
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 );
}
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;
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;
}
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;
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");
var pcrud;
var dist_id;
+var rlu_editor;
var cgi;
function format_routing_label(routing_label) {
);
}
+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();
[% 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">
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"
<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"