Serials: closer to full working receiving in the batch receive interface
authorsenator <senator@dcc99617-32d9-48b4-a31d-7c20da2025e4>
Fri, 27 Aug 2010 21:50:28 +0000 (21:50 +0000)
committersenator <senator@dcc99617-32d9-48b4-a31d-7c20da2025e4>
Fri, 27 Aug 2010 21:50:28 +0000 (21:50 +0000)
Units are actually created now, one per item. Plus misc bug fixes.
For more flexible binding of items into units, see the serial control view.

git-svn-id: svn://svn.open-ils.org/ILS/trunk@17364 dcc99617-32d9-48b4-a31d-7c20da2025e4

Open-ILS/src/extras/ils_events.xml
Open-ILS/src/perlmods/OpenILS/Application/Serial.pm
Open-ILS/web/opac/locale/en-US/lang.dtd
Open-ILS/xul/staff_client/server/locale/en-US/serial.properties
Open-ILS/xul/staff_client/server/serial/batch_receive.js
Open-ILS/xul/staff_client/server/serial/batch_receive_overlay.xul

index a8d0908..85d8b7e 100644 (file)
         <desc xml:lang="en-US">The caption/pattern still has dependent issuances</desc>
     </event>
 
+    <event code='11101' textcode='SERIAL_DISTRIBUTION_HAS_NO_COPY_TEMPLATE'>
+        <desc xml:lang="en-US">Units cannot be created for the given item because its associated distribution does not have a copy template.</desc>
+    </event>
+
+    <event code='11102' textcode='SERIAL_DISTRIBUTION_HAS_NO_CALL_NUMBER'>
+        <desc xml:lang="en-US">Units cannot be created for the given item because its associated distribution does not have a call number.</desc>
+    </event>
+
        <!-- ================================================================ -->
 
 </ils_events>
index a61bc52..bee7f1c 100644 (file)
@@ -37,6 +37,7 @@ package OpenILS::Application::Serial;
 use strict;
 use warnings;
 
+
 use OpenILS::Application;
 use base qw/OpenILS::Application/;
 use OpenILS::Application::AppUtils;
@@ -876,13 +877,125 @@ sub unitize_items {
     return {'num_items_received' => scalar @$items, 'new_unit_id' => $new_unit_id};
 }
 
+__PACKAGE__->register_method(
+    method    => "receive_items_one_unit_per",
+    api_name  => "open-ils.serial.receive_items.one_unit_per",
+    stream => 1,
+    api_level => 1,
+    argc      => 1,
+    signature => {
+        desc     => "Marks items in a list as received, creates a new unit for each item if any unit is fleshed on",
+        "params" => [ {
+                 name => "items",
+                 desc => "array of serial items, possibly fleshed with units and definitely fleshed with stream->distribution",
+                 type => "array"
+            }
+        ],
+        "return" => {
+            desc => "The item ID for each item successfully received",
+            type => "int"
+        }
+    }
+);
+
+sub receive_items_one_unit_per {
+    # XXX This function may be temporary. unitize_items() would seem to aim to
+    # accomodate what this function does as well as other variations on the
+    # operation (binding multiple items into one unit, etc.?) plus generating
+    # summaries.  This is just a minimal get-it-working-now implementation.
+    # In the future, when unitize_items() is ready, perhaps any registered
+    # method names that point to this function can be repointed at
+    # unitize_items()
+
+    my ($self, $client, $auth, $items) = @_;
+
+    my $e = new_editor("authtoken" => $auth, "xact" => 1);
+    return $e->die_event unless $e->checkauth;
+
+    my $user_id = $e->requestor->id;
+
+    # Get a list of all the non-virtual field names in a serial::unit for
+    # merging given unit objects with template-built units later.
+    # XXX move this somewhere global so it isn't re-run all the time
+    my $all_unit_fields =
+        $Fieldmapper::fieldmap->{"Fieldmapper::serial::unit"}->{"fields"};
+    my @real_unit_fields = grep {
+        not $all_unit_fields->{$_}->{"virtual"}
+    } keys %$all_unit_fields;
+
+    foreach my $item (@$items) {
+        # Note that we expect a certain fleshing on the items we're getting.
+        my $sdist = $item->stream->distribution;
+
+        # Create unit if given by user
+        if (ref $item->unit) {
+            # detach from the item, as we need to create separately
+            my $user_unit = $item->unit;
+
+            # get a unit based on associated template
+            my $template_unit = _build_unit($e, $sdist, "receive");
+            if ($U->event_code($template_unit)) {
+                $e->rollback;
+                $template_unit->{"note"} = "Item ID: " . $item->id;
+                return $template_unit;
+            }
+
+            # merge built unit with provided unit from user
+            foreach (@real_unit_fields) {
+                unless ($user_unit->$_) {
+                    $user_unit->$_($template_unit->$_);
+                }
+            }
+
+            # set the incontrovertibles on the unit
+            $user_unit->edit_date("now");
+            $user_unit->create_date("now");
+            $user_unit->editor($user_id);
+            $user_unit->creator($user_id);
+
+            return $e->die_event unless $e->create_serial_unit($user_unit);
+
+            # save reference to new unit
+            $item->unit($e->data->id);
+        }
+
+        # Create notes if given by user
+        if (ref($item->notes) and @{$item->notes}) {
+            foreach my $note (@{$item->notes}) {
+                $note->creator($user_id);
+                $note->create_date("now");
+
+                return $e->die_event unless $e->create_serial_item_note($note);
+            }
+
+            $item->clear_notes; # They're saved; we no longer want them here.
+        }
+
+        # Set the incontrovertibles on the item
+        $item->date_received("now");
+        $item->edit_date("now");
+        $item->editor($user_id);
+
+        return $e->die_event unless $e->update_serial_item($item);
+
+        # send client a response
+        $client->respond($item->id);
+    }
+
+    # XXX TODO update basic/supplementary/index summaries
+
+    $e->commit or return $e->die_event;
+    undef;
+}
+
 sub _build_unit {
     my $editor = shift;
     my $sdist = shift;
     my $mode = shift;
 
     my $attr = $mode . '_unit_template';
-    my $template = $editor->retrieve_asset_copy_template($sdist->$attr);
+    my $template = $editor->retrieve_asset_copy_template($sdist->$attr) or
+        return new OpenILS::Event("SERIAL_DISTRIBUTION_HAS_NO_COPY_TEMPLATE");
 
     my @parts = qw( status location loan_duration fine_level age_protect circulate deposit ref holdable deposit_amount price circ_modifier circ_as_type alert_message opac_visible floating mint_condition );
 
@@ -897,8 +1010,12 @@ sub _build_unit {
     $unit->circ_lib($sdist->holding_lib);
     $unit->creator($editor->requestor->id);
     $unit->editor($editor->requestor->id);
+
     $attr = $mode . '_call_number';
-    $unit->call_number($sdist->$attr);
+    my $cn = $sdist->$attr or
+        return new OpenILS::Event("SERIAL_DISTRIBUTION_HAS_NO_CALL_NUMBER");
+
+    $unit->call_number($cn);
     $unit->barcode('AUTO');
     $unit->sort_key('');
     $unit->summary_contents('');
@@ -1808,14 +1925,19 @@ sub get_receivable_issuances {
     my $issuance_ids = $e->json_query({
         "select" => {
             "siss" => [
-                {"transform" => "distinct", "column" => "id"}
+                {"transform" => "distinct", "column" => "id"},
+                "date_published"
             ]
         },
         "from" => {"siss" => "sitem"},
         "where" => {
             "subscription" => $sub_id,
             "+sitem" => {"date_received" => undef}
+        },
+        "order_by" => {
+            "siss" => {"date_published" => {"direction" => "asc"}}
         }
+
     }) or return $e->die_event;
 
     $client->respond($e->retrieve_serial_issuance($_->{"id"}))
index 383180e..ac37e14 100644 (file)
 <!ENTITY staff.serial.batch_receive.no_items "There are no items to receive for this subscription.">
 <!ENTITY staff.serial.batch_receive.org_unit "Org Unit">
 <!ENTITY staff.serial.batch_receive.barcode "Barcode">
-<!ENTITY staff.serial.batch_receive.circ_mod "Circ Modifier">
+<!ENTITY staff.serial.batch_receive.circ_modifier "Circ Modifier">
 <!ENTITY staff.serial.batch_receive.note "Note">
-<!ENTITY staff.serial.batch_receive.copy_loc "Copy Location">
+<!ENTITY staff.serial.batch_receive.location "Copy Location">
 <!ENTITY staff.serial.batch_receive.price "Price">
 <!ENTITY staff.serial.batch_receive.receive "Receive?">
 <!ENTITY staff.serial.batch_receive.auto_generate "Auto-generate?">
index 45cef07..f63f505 100644 (file)
@@ -63,3 +63,4 @@ batch_receive.autogen_barcodes.questionable=There are already barcodes entered f
 batch_receive.autogen_barcodes.remove=Clear the barcodes that have already been auto-generated?
 batch_receive.none=[None]
 batch_receive.apply=Apply
+batch_receive.receive_time_note=Receive-time Note
index fce90ac..a6b0db0 100644 (file)
@@ -44,8 +44,8 @@ function S(k) {
 }
 
 function T(s) { return document.createTextNode(s); }
-function D(s) {return s ? openils.Util.timeStamp(s, {"selector":"date"}) : "";}
-function node_by_name(s, ctx) {return dojo.query("[name='" + s + "']", ctx)[0];}
+function D(s) {return s ? openils.Util.timeStamp(s,{"selector":"date"}) : "";}
+function node_by_name(s, ctx) {return dojo.query("[name='"+ s +"']",ctx)[0];}
 
 function num_sort(a, b) {
     [a, b] = [Number(a), Number(b)];
@@ -80,13 +80,20 @@ function BatchReceiver() {
 
         this._clear_entry_batch_row();
 
-        this._copy_loc_by_lib = {};
+        this._location_by_lib = {};
 
         /* empty the entry receiving table if we're starting over */
         if (this.item_cache) {
-            for (var id in this.item_cache)
+            for (var id in this.item_cache) {
                 this.finish_receipt(this.item_cache[id]);
+                hard_empty(this.entry_tbody);
+            }
+            /* XXX incredibly, running hard_empty() more than once seems to be
+             * good and necessary.  There's a bug under the covers somewhere,
+             * but this keeps it out of sight for the moment. */
+             hard_empty(this.entry_tbody);
         }
+        hard_empty(this.entry_tbody);
 
         this.rows = {};
         this.item_cache = {};
@@ -174,8 +181,8 @@ function BatchReceiver() {
         return issuances;
     };
 
-    this._build_circ_mod_dropdown = function() {
-        if (!this._built_circ_mod_dropdown) {
+    this._build_circ_modifier_dropdown = function() {
+        if (!this._built_circ_modifier_dropdown) {
             var menulist = dojo.create("menulist");
             var menupopup = dojo.create("menupopup", null, menulist, "only");
             dojo.create(
@@ -185,16 +192,24 @@ function BatchReceiver() {
 
             var mods = [];
             fieldmapper.standardRequest(
-                ["open-ils.circ", "open-ils.circ.circ_modifier.retrieve.all"], {
-                    "params": [],
+                ["open-ils.circ", "open-ils.circ.circ_modifier.retrieve.all"],{
+                    "params": [{"full": true}],
                     "async": false,
                     "onresponse": function(r) {
                         if (mods = openils.Util.readResponse(r)) {
-                            mods.forEach(
+                            mods.sort(
+                                function(a,b) {
+                                    return a.code() > b.code() ? 1 :
+                                        b.code() > a.code() ? -1 :
+                                        0;
+                                }
+                            ).forEach(
                                 function(mod) {
                                     dojo.create(
                                         "menuitem", {
-                                            "value": mod, "label": mod
+                                            "value": mod.code(),
+                                            /* XXX use format string */
+                                            "label": mod.code()+" "+mod.name()
                                         }, menupopup, "last"
                                     );
                                 }
@@ -205,17 +220,17 @@ function BatchReceiver() {
             );
             if (!mods.length) {
                 /* in this case, discard menulist and menupopup */
-                this._built_circ_mod_dropdown =
+                this._built_circ_modifier_dropdown =
                     dojo.create("description", {"value": "-"});
             } else {
-                this._built_circ_mod_dropdown = menulist;
+                this._built_circ_modifier_dropdown = menulist;
             }
         }
 
-        return dojo.clone(this._built_circ_mod_dropdown);
+        return dojo.clone(this._built_circ_modifier_dropdown);
     };
 
-    this._extend_circ_mod_for_batch = function(control) {
+    this._extend_circ_modifier_for_batch = function(control) {
         dojo.create(
             "menuitem", {"value": -1, "label": "---"},
             dojo.query("menupopup", control)[0],
@@ -224,7 +239,7 @@ function BatchReceiver() {
         return control;
     };
 
-    this._build_copy_loc_dropdown = function(locs, add_unset_value) {
+    this._build_location_dropdown = function(locs, add_unset_value) {
         var menulist = dojo.create("menulist");
         var menupopup = dojo.create("menupopup", null, menulist, "only");
 
@@ -249,21 +264,21 @@ function BatchReceiver() {
         return menulist;
     };
 
-    this._get_copy_locs_for_lib = function(lib) {
-        if (!this._copy_loc_by_lib[lib]) {
+    this._get_locations_for_lib = function(lib) {
+        if (!this._location_by_lib[lib]) {
             fieldmapper.standardRequest(
-                ["open-ils.circ", "open-ils.circ.copy_location.retrieve.all"], {
+                ["open-ils.circ", "open-ils.circ.copy_location.retrieve.all"],{
                     "params": [lib, false, true],
                     "async": false,
                     "onresponse": function(r) {
                         if (locs = openils.Util.readResponse(r))
-                            self._copy_loc_by_lib[lib] = locs;
+                            self._location_by_lib[lib] = locs;
                     }
                 }
             );
         }
 
-        return this._copy_loc_by_lib[lib];
+        return this._location_by_lib[lib];
     };
 
     this._build_receive_toggle = function(item) {
@@ -384,8 +399,8 @@ function BatchReceiver() {
                 ],
                 "async": false,
                 "oncomplete": function(r) {
-                    /* These two things better come before readResponse(), which
-                     * can throw exceptions. */
+                    /* These two things better come before readResponse(),
+                     * which can throw exceptions. */
                     busy(false);
                     dojo.byId("bib_lookup_submit").disabled = false;
 
@@ -472,8 +487,8 @@ function BatchReceiver() {
 
             this.issuances.sort(
                 function(a, b) {
-                    if (a.date_published() > b.date_published()) return 1;
-                    else if (b.date_published() > a.date_published()) return -1;
+                    if (a.date_published()>b.date_published()) return 1;
+                    else if (b.date_published()>a.date_published()) return -1;
                     else return 0;
                 }
             ).forEach(
@@ -557,18 +572,21 @@ function BatchReceiver() {
             this.batch_controls.note = dojo.create("textbox", {"size": 20})
         );
 
-        node_by_name("copy_loc", row).appendChild(
-            this.batch_controls.copy_loc = this._build_copy_loc_dropdown(
-                /* XXX is 1 really the right value below? */
-                this._get_copy_locs_for_lib(1),
+        node_by_name("location", row).appendChild(
+            this.batch_controls.location = this._build_location_dropdown(
+                /* XXX TODO build a smarter list. rather than all copy locs
+                 * under OU #1, try building a list of copy locs available to
+                 * all OUs represented in actual items */
+                this._get_locations_for_lib(1),
                 true /* add_unset_value */
             )
         );
 
-        node_by_name("circ_mod", row).appendChild(
-            this.batch_controls.circ_mod = this._extend_circ_mod_for_batch(
-                this._build_circ_mod_dropdown()
-            )
+        node_by_name("circ_modifier", row).appendChild(
+            this.batch_controls.circ_modifier =
+                this._extend_circ_modifier_for_batch(
+                    this._build_circ_modifier_dropdown()
+                )
         );
 
         node_by_name("price", row).appendChild(
@@ -615,16 +633,16 @@ function BatchReceiver() {
             )
         );
 
-        n("copy_loc").appendChild(
-            this._build_copy_loc_dropdown(
-                this._get_copy_locs_for_lib(
+        n("location").appendChild(
+            this._build_location_dropdown(
+                this._get_locations_for_lib(
                     item.stream().distribution().holding_lib().id()
                 )
             )
         );
 
         n("note").appendChild(dojo.create("textbox", {"size": 20}));
-        n("circ_mod").appendChild(this._build_circ_mod_dropdown());
+        n("circ_modifier").appendChild(this._build_circ_modifier_dropdown());
         n("price").appendChild(dojo.create("textbox", {"size": 9}));
         n("receive").appendChild(this._build_receive_toggle(item));
 
@@ -632,22 +650,52 @@ function BatchReceiver() {
     };
 
     this.receive = function() {
-        var recv_ids = [];
+        var items = [];
         for (var id in this.rows) {
-            /* XXX TODO: get field values, send to ML,
-             * and yes do trimming here. */
-            if (!this._row_disabled(id)) recv_ids.push(id);
+            if (this._row_disabled(id)) 
+                continue;
+
+            var item = this.item_cache[id];
+
+            var barcode = this._row_field_value(id, "barcode");
+            if (barcode) {
+                var unit = new sunit();
+                unit.barcode(barcode);
+
+                ["price", "location", "circ_modifier"].forEach(
+                    function(field) {
+                        var value = self._row_field_value(id, field).trim();
+                        if (value) unit[field](value);
+                    }
+                );
+
+
+                item.unit(unit);
+            }
+
+            var note_value = this._row_field_value(id, "note").trim();
+            if (note_value) {
+                var note = new sin();
+                note.item(id);
+                note.pub(false);
+                note.title(S("receive_time_note"));
+                note.value(note_value);
+
+                item.notes([note]);
+            }
+
+            items.push(item);
         }
 
         busy(true);
         fieldmapper.standardRequest(
-            ["open-ils.serial", "open-ils.serial.items.receive_by_id"], {
-                "params": [authtoken, recv_ids],
+            ["open-ils.serial", "open-ils.serial.receive_items.one_unit_per"],{
+                "params": [authtoken, items],
                 "async": true,
                 "oncomplete": function(r) {
                     try {
-                        while (item = openils.Util.readResponse(r))
-                            self.finish_receipt(item);
+                        while (item_id = openils.Util.readResponse(r))
+                            self.finish_receipt(item_id);
                     } catch (E) {
                         alert(E);
                     }
@@ -657,10 +705,11 @@ function BatchReceiver() {
         );
     };
 
-    this.finish_receipt = function(item) {
-        dojo.destroy(this.rows[item.id()]);
-        delete this.rows[item.id()];
-        delete this.item_cache[item.id()];
+    this.finish_receipt = function(item_id) {
+        hard_empty(this.rows[item_id]);
+        dojo.destroy(this.rows[item_id]);
+        delete this.rows[item_id];
+        delete this.item_cache[item_id];
     };
 
     this.autogen_if_appropriate = function(textbox, item_id) {
index a96d4e7..b1bf50a 100644 (file)
                                     &staff.serial.batch_receive.barcode;
                                 </h:th>
                                 <h:th>
-                                    &staff.serial.batch_receive.circ_mod;
+                                    &staff.serial.batch_receive.circ_modifier;
                                 </h:th>
                                 <h:th>
                                     &staff.serial.batch_receive.note;
                                 </h:th>
                                 <h:th>
-                                    &staff.serial.batch_receive.copy_loc;
+                                    &staff.serial.batch_receive.location;
                                 </h:th>
                                 <h:th>
                                     &staff.serial.batch_receive.price;
                                         id="autogen_barcodes"
                                         label="&staff.serial.batch_receive.auto_generate;" />
                                 </h:td>
-                                <h:td name="circ_mod" align="center"></h:td>
+                                <h:td name="circ_modifier" align="center"></h:td>
                                 <h:td name="note"></h:td>
-                                <h:td name="copy_loc" align="center"></h:td>
+                                <h:td name="location" align="center"></h:td>
                                 <h:td name="price"></h:td>
                                 <h:td name="receive"></h:td>
                                 <h:td name="apply"></h:td>
                             <h:tr id="entry_template">
                                 <h:td name="holding_lib" align="center"></h:td>
                                 <h:td name="barcode"></h:td>
-                                <h:td name="circ_mod" align="center"></h:td>
+                                <h:td name="circ_modifier" align="center"></h:td>
                                 <h:td name="note"></h:td>
-                                <h:td name="copy_loc" align="center"></h:td>
+                                <h:td name="location" align="center"></h:td>
                                 <h:td name="price"></h:td>
                                 <h:td name="receive" align="center"></h:td>
                             </h:tr>