From: senator Date: Wed, 1 Sep 2010 20:25:18 +0000 (+0000) Subject: Backport r17364 from trunk: serial batch receive improvements X-Git-Url: https://old-git.evergreen-ils.org/?a=commitdiff_plain;h=a604a9a8f90a445aaad7bb0d53951cead7d0dc5e;p=contrib%2FConifer.git Backport r17364 from trunk: serial batch receive improvements git-svn-id: svn://svn.open-ils.org/ILS/branches/rel_2_0@17429 dcc99617-32d9-48b4-a31d-7c20da2025e4 --- 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 3240f1d7d3..348d2681b0 100644 --- a/Open-ILS/web/opac/locale/en-US/lang.dtd +++ b/Open-ILS/web/opac/locale/en-US/lang.dtd @@ -1613,9 +1613,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 @@ - + - +