From c6767e7eef7e1cc3145d0df864c5ce5de110184b Mon Sep 17 00:00:00 2001 From: senator Date: Fri, 27 Aug 2010 21:50:28 +0000 Subject: [PATCH] Serials: closer to full working receiving in the batch receive interface 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 | 8 ++ .../src/perlmods/OpenILS/Application/Serial.pm | 128 +++++++++++++++++- Open-ILS/web/opac/locale/en-US/lang.dtd | 4 +- .../server/locale/en-US/serial.properties | 1 + .../staff_client/server/serial/batch_receive.js | 145 ++++++++++++++------- .../server/serial/batch_receive_overlay.xul | 12 +- 6 files changed, 239 insertions(+), 59 deletions(-) diff --git a/Open-ILS/src/extras/ils_events.xml b/Open-ILS/src/extras/ils_events.xml index a8d0908bed..85d8b7e614 100644 --- a/Open-ILS/src/extras/ils_events.xml +++ b/Open-ILS/src/extras/ils_events.xml @@ -972,6 +972,14 @@ The caption/pattern still has dependent issuances + + Units cannot be created for the given item because its associated distribution does not have a copy template. + + + + Units cannot be created for the given item because its associated distribution does not have a call number. + + diff --git a/Open-ILS/src/perlmods/OpenILS/Application/Serial.pm b/Open-ILS/src/perlmods/OpenILS/Application/Serial.pm index a61bc52926..bee7f1c19c 100644 --- a/Open-ILS/src/perlmods/OpenILS/Application/Serial.pm +++ b/Open-ILS/src/perlmods/OpenILS/Application/Serial.pm @@ -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"})) diff --git a/Open-ILS/web/opac/locale/en-US/lang.dtd b/Open-ILS/web/opac/locale/en-US/lang.dtd index 383180e507..ac37e14832 100644 --- a/Open-ILS/web/opac/locale/en-US/lang.dtd +++ b/Open-ILS/web/opac/locale/en-US/lang.dtd @@ -1619,9 +1619,9 @@ - + - + diff --git a/Open-ILS/xul/staff_client/server/locale/en-US/serial.properties b/Open-ILS/xul/staff_client/server/locale/en-US/serial.properties index 45cef07f53..f63f5053a9 100644 --- a/Open-ILS/xul/staff_client/server/locale/en-US/serial.properties +++ b/Open-ILS/xul/staff_client/server/locale/en-US/serial.properties @@ -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 diff --git a/Open-ILS/xul/staff_client/server/serial/batch_receive.js b/Open-ILS/xul/staff_client/server/serial/batch_receive.js index fce90acba5..a6b0db0251 100644 --- a/Open-ILS/xul/staff_client/server/serial/batch_receive.js +++ b/Open-ILS/xul/staff_client/server/serial/batch_receive.js @@ -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) { diff --git a/Open-ILS/xul/staff_client/server/serial/batch_receive_overlay.xul b/Open-ILS/xul/staff_client/server/serial/batch_receive_overlay.xul index a96d4e773d..b1bf50aee9 100644 --- a/Open-ILS/xul/staff_client/server/serial/batch_receive_overlay.xul +++ b/Open-ILS/xul/staff_client/server/serial/batch_receive_overlay.xul @@ -93,13 +93,13 @@ &staff.serial.batch_receive.barcode; - &staff.serial.batch_receive.circ_mod; + &staff.serial.batch_receive.circ_modifier; &staff.serial.batch_receive.note; - &staff.serial.batch_receive.copy_loc; + &staff.serial.batch_receive.location; &staff.serial.batch_receive.price; @@ -117,9 +117,9 @@ id="autogen_barcodes" label="&staff.serial.batch_receive.auto_generate;" /> - + - + @@ -134,9 +134,9 @@ - + - + -- 2.11.0