Serials: a routing list editor for the alternate serials control view
authorsenator <senator@dcc99617-32d9-48b4-a31d-7c20da2025e4>
Sat, 20 Nov 2010 22:55:56 +0000 (22:55 +0000)
committersenator <senator@dcc99617-32d9-48b4-a31d-7c20da2025e4>
Sat, 20 Nov 2010 22:55:56 +0000 (22:55 +0000)
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
Open-ILS/src/perlmods/OpenILS/Application/Actor.pm
Open-ILS/src/perlmods/OpenILS/Application/Serial.pm
Open-ILS/web/js/ui/default/serial/list_stream.js
Open-ILS/web/templates/default/serial/list_stream.tt2

index cb2a126..e00b24c 100644 (file)
@@ -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>
 
index 2fcacd2..bb33e89 100644 (file)
@@ -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;
 }
 
index 3623fb5..4393d40 100644 (file)
@@ -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;
index befc46e..403b317 100644 (file)
@@ -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 = "&nbsp;" + 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();
index 660c761..52e641c 100644 (file)
@@ -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"
     <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 />&nbsp; <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"