Serials: an alternative batch receiving interface, to support certain
authorsenator <senator@dcc99617-32d9-48b4-a31d-7c20da2025e4>
Thu, 19 Aug 2010 18:18:21 +0000 (18:18 +0000)
committersenator <senator@dcc99617-32d9-48b4-a31d-7c20da2025e4>
Thu, 19 Aug 2010 18:18:21 +0000 (18:18 +0000)
heavy-barcoding workflows.

Still needs some things hooked up in the middle layer to create serial.units.
Still needs some configurability.  Access from "Actions for this Record" in
the staff-client-wrapped OPAC for a record with subscriptions and items
attached.

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

13 files changed:
Open-ILS/examples/fm_IDL.xml
Open-ILS/src/perlmods/OpenILS/Application/Circ/CopyLocations.pm
Open-ILS/src/perlmods/OpenILS/Application/Serial.pm
Open-ILS/web/opac/locale/en-US/lang.dtd
Open-ILS/xul/staff_client/chrome/content/cat/opac.js
Open-ILS/xul/staff_client/chrome/content/cat/opac.xul
Open-ILS/xul/staff_client/chrome/content/main/constants.js
Open-ILS/xul/staff_client/chrome/locale/en-US/offline.properties
Open-ILS/xul/staff_client/server/locale/en-US/serial.properties
Open-ILS/xul/staff_client/server/serial/batch_receive.js [new file with mode: 0644]
Open-ILS/xul/staff_client/server/serial/batch_receive.xul [new file with mode: 0644]
Open-ILS/xul/staff_client/server/serial/batch_receive_overlay.xul [new file with mode: 0644]
Open-ILS/xul/staff_client/server/skin/serial.css [new file with mode: 0644]

index df5c59c..eb79fa0 100644 (file)
@@ -1781,6 +1781,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
                        <field reporter:label="Flattened MARC Fields " name="full_record_entries" oils_persist:virtual="true" reporter:datatype="link"/>
                        <field reporter:label="Simple Record Extracts " name="simple_record" oils_persist:virtual="true" reporter:datatype="link"/>
                        <field reporter:label="Authority Links" name="authority_links" oils_persist:virtual="true" reporter:datatype="link"/>
+                       <field reporter:label="Subscriptions" name="subscriptions" oils_persist:virtual="true" reporter:datatype="link"/>
                </fields>
                <links>
                        <link field="owner" reltype="has_a" key="id" map="" class="aou"/>
@@ -1800,6 +1801,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
                        <link field="series_field_entries" reltype="has_many" key="source" map="" class="msefe"/>
                        <link field="full_record_entries" reltype="has_many" key="record" map="" class="mfr"/>
                        <link field="authority_links" reltype="has_many" key="bib" map="" class="abl"/>
+                       <link field="subscriptions" reltype="has_many" key="record_entry" map="" class="ssub"/>
                </links>
         <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
             <actions>
@@ -3169,10 +3171,10 @@ SELECT  usr,
                </permacrud>
        </class>
 
-       <class id="ssub" controller="open-ils.cstore" oils_obj:fieldmapper="serial::subscription" oils_persist:tablename="serial.subscription" reporter:label="Subscription">
+       <class id="ssub" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="serial::subscription" oils_persist:tablename="serial.subscription" reporter:label="Subscription">
                <fields oils_persist:primary="id" oils_persist:sequence="serial.subscription_id_seq">
                        <field reporter:label="Id" name="id" reporter:datatype="id"/>
-                       <field reporter:label="Owning Library" name="owning_lib" reporter:datatype="link"/>
+                       <field reporter:label="Owning Library" name="owning_lib" reporter:datatype="org_unit"/>
                        <field reporter:label="Start Date" name="start_date" reporter:datatype="timestamp"/>
                        <field reporter:label="End Date" name="end_date" reporter:datatype="timestamp"/>
                        <field reporter:label="Bibliographic Record Entry" name="record_entry" reporter:datatype="link"/>
@@ -3190,6 +3192,14 @@ SELECT  usr,
                        <link field="scaps" reltype="has_many" key="subscription" map="" class="scap"/>
                        <link field="notes" reltype="has_many" key="subscription" map="" class="ssubn"/>
                </links>
+               <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
+                       <actions>
+                               <create permission="ADMIN_SERIAL_SUBSCRIPTION" context_field="owning_lib"/>
+                               <retrieve permission="VIEW_SERIAL_SUBSCRIPTION" context_field="owning_lib"/>
+                               <update permission="ADMIN_SERIAL_SUBSCRIPTION" context_field="owning_lib"/>
+                               <delete permission="ADMIN_SERIAL_SUBSCRIPTION" context_field="owning_lib"/>
+                       </actions>
+               </permacrud>
        </class>
 
        <class id="ssubn" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="serial::subscription_note" oils_persist:tablename="serial.subscription_note" reporter:label="Subscription Note">
index 0ebe1c7..3930641 100644 (file)
@@ -18,22 +18,29 @@ __PACKAGE__->register_method(
        signature       => q/
                Retrieves the ranged set of copy locations for the requested org.
                If no org is provided, all copy locations are returned
-               @param authtoken The login session key
                @param orgId The org location id
+               @param noi18n No i18n in result
+        @param flesh_owning_lib Flesh owning lib in results
                @return An array of copy location objects
                /);
 
 sub cl_retrieve_all {
-       my( $self, $client, $org_id, $no_i18n ) = @_;
+       my ($self, $client, $org_id, $no_i18n, $flesh_owning_lib) = @_;
 
        if(!$org_id) {
                my $otree = $U->get_org_tree();
                $org_id = $otree->id;
        }
 
+    my $second_cstore_arg = {"no_i18n" => scalar($no_i18n)};
+    if ($flesh_owning_lib) {
+        $second_cstore_arg->{"flesh"} = 1;
+        $second_cstore_arg->{"flesh_fields"} = {"acpl" => ["owning_lib"]};
+    }
+
     return new_editor()->search_asset_copy_location([{
         owning_lib => $U->get_org_full_path($org_id)
-    }, {"no_i18n" => scalar($no_i18n)}]);
+    }, $second_cstore_arg]);
 }
 
 __PACKAGE__->register_method(
index 9bb0a18..a61bc52 100644 (file)
@@ -40,10 +40,12 @@ use warnings;
 use OpenILS::Application;
 use base qw/OpenILS::Application/;
 use OpenILS::Application::AppUtils;
+use OpenILS::Event;
 use OpenSRF::AppSession;
-use OpenSRF::Utils qw/:datetime/;;
-use OpenSRF::Utils::Logger qw($logger);
+use OpenSRF::Utils qw/:datetime/;
+use OpenSRF::Utils::Logger qw/:logger/;
 use OpenILS::Utils::CStoreEditor q/:funcs/;
+use OpenILS::Utils::Fieldmapper;
 use OpenILS::Utils::MFHD;
 use MARC::File::XML (BinaryEncoding => 'utf8');
 my $U = 'OpenILS::Application::AppUtils';
@@ -58,7 +60,6 @@ my %MFHD_TAGS_BY_NAME = (  $MFHD_NAMES[0] => '853',
                         $MFHD_NAMES[1] => '854',
                         $MFHD_NAMES[2] => '855');
 
-
 # helper method for conforming dates to ISO8601
 sub _cleanse_dates {
     my $item = shift;
@@ -70,6 +71,14 @@ sub _cleanse_dates {
     return 0;
 }
 
+sub _get_mvr {
+    $U->simplereq(
+        "open-ils.search",
+        "open-ils.search.biblio.record.mods_slim.retrieve",
+        @_
+    );
+}
+
 
 ##########################################################################
 # item methods
@@ -1604,4 +1613,258 @@ sub serial_caption_and_pattern_retrieve_batch {
     );
 }
 
+__PACKAGE__->register_method(
+    "method" => "bre_by_identifier",
+    "api_name" => "open-ils.serial.biblio.record_entry.by_identifier",
+    "stream" => 1,
+    "signature" => {
+        "desc" => "Find instances of biblio.record_entry given a search token" .
+            " that could be a value for any identifier defined in " .
+            "config.metabib_field",
+        "params" => [
+            {"desc" => "Search token", "type" => "string"},
+            {"desc" => "Options: require_subscriptions, add_mvr, is_actual_id" .
+                " (all boolean)", "type" => "object"}
+        ],
+        "return" => {
+            "desc" => "Any matching BREs, or if the add_mvr option is true, " .
+                "objects with a 'bre' key/value pair, and an 'mvr' " .
+                "key-value pair.  BREs have subscriptions fleshed on.",
+            "type" => "object"
+        }
+    }
+);
+
+sub bre_by_identifier {
+    my ($self, $client, $term, $options) = @_;
+
+    return new OpenILS::Event("BAD_PARAMS") unless $term;
+
+    $options ||= {};
+    my $e = new_editor();
+
+    my @ids;
+
+    if ($options->{"is_actual_id"}) {
+        @ids = ($term);
+    } else {
+        my $cmf =
+            $e->search_config_metabib_field({"field_class" => "identifier"})
+                or return $e->die_event;
+
+        my @identifiers = map { $_->name } @$cmf;
+        my $query = join(" || ", map { "id|$_: $term" } @identifiers);
+
+        my $search = create OpenSRF::AppSession("open-ils.search");
+        my $search_result = $search->request(
+            "open-ils.search.biblio.multiclass.query.staff", {}, $query
+        )->gather(1);
+        $search->disconnect;
+
+        # Un-nest results. They tend to look like [[1],[2],[3]] for some reason.
+        @ids = map { @{$_} } @{$search_result->{"ids"}};
+
+        unless (@ids) {
+            $e->disconnect;
+            return undef;
+        }
+    }
+
+    my $bre = $e->search_biblio_record_entry([
+        {"id" => \@ids}, {
+            "flesh" => 2, "flesh_fields" => {
+                "bre" => ["subscriptions"],
+                "ssub" => ["owning_lib"]
+            }
+        }
+    ]) or return $e->die_event;
+
+    if (@$bre && $options->{"require_subscriptions"}) {
+        $bre = [ grep { @{$_->subscriptions} } @$bre ];
+    }
+
+    $e->disconnect;
+
+    if (@$bre) { # re-evaluate after possible grep
+        if ($options->{"add_mvr"}) {
+            $client->respond(
+                {"bre" => $_, "mvr" => _get_mvr($_->id)}
+            ) foreach (@$bre);
+        } else {
+            $client->respond($_) foreach (@$bre);
+        }
+    }
+
+    undef;
+}
+
+__PACKAGE__->register_method(
+    "method" => "get_receivable_items",
+    "api_name" => "open-ils.serial.items.receivable.by_subscription",
+    "stream" => 1,
+    "signature" => {
+        "desc" => "Return all receivable items under a given subscription",
+        "params" => [
+            {"desc" => "Authtoken", "type" => "string"},
+            {"desc" => "Subscription ID", "type" => "number"},
+        ],
+        "return" => {
+            "desc" => "All receivable items under a given subscription",
+            "type" => "object"
+        }
+    }
+);
+
+__PACKAGE__->register_method(
+    "method" => "get_receivable_items",
+    "api_name" => "open-ils.serial.items.receivable.by_issuance",
+    "stream" => 1,
+    "signature" => {
+        "desc" => "Return all receivable items under a given issuance",
+        "params" => [
+            {"desc" => "Authtoken", "type" => "string"},
+            {"desc" => "Issuance ID", "type" => "number"},
+        ],
+        "return" => {
+            "desc" => "All receivable items under a given issuance",
+            "type" => "object"
+        }
+    }
+);
+
+sub get_receivable_items {
+    my ($self, $client, $auth, $term)  = @_;
+
+    my $e = new_editor("authtoken" => $auth);
+    return $e->die_event unless $e->checkauth;
+
+    # XXX permissions
+
+    my $by = ($self->api_name =~ /by_(\w+)$/)[0];
+
+    my %where = (
+        "issuance" => {"issuance" => $term},
+        "subscription" => {"+siss" => {"subscription" => $term}}
+    );
+
+    my $item_ids = $e->json_query(
+        {
+            "select" => {"sitem" => ["id"]},
+            "from" => {"sitem" => "siss"},
+            "where" => {
+                %{$where{$by}}, "date_received" => undef
+            },
+            "order_by" => {"sitem" => ["id"]}
+        }
+    ) or return $e->die_event;
+
+    return undef unless @$item_ids;
+
+    foreach (map { $_->{"id"} } @$item_ids) {
+        $client->respond(
+            $e->retrieve_serial_item([
+                $_, {
+                    "flesh" => 3,
+                    "flesh_fields" => {
+                        "sitem" => ["stream", "issuance"],
+                        "sstr" => ["distribution"],
+                        "sdist" => ["holding_lib"]
+                    }
+                }
+            ])
+        );
+    }
+
+    $e->disconnect;
+    undef;
+}
+
+__PACKAGE__->register_method(
+    "method" => "get_receivable_issuances",
+    "api_name" => "open-ils.serial.issuances.receivable",
+    "stream" => 1,
+    "signature" => {
+        "desc" => "Return all issuances with receivable items given " .
+            "a subscription ID",
+        "params" => [
+            {"desc" => "Authtoken", "type" => "string"},
+            {"desc" => "Subscription ID", "type" => "number"},
+        ],
+        "return" => {
+            "desc" => "All issuances with receivable items " .
+                "(but not the items themselves)", "type" => "object"
+        }
+    }
+);
+
+sub get_receivable_issuances {
+    my ($self, $client, $auth, $sub_id) = @_;
+
+    my $e = new_editor("authtoken" => $auth);
+    return $e->die_event unless $e->checkauth;
+
+    # XXX permissions
+
+    my $issuance_ids = $e->json_query({
+        "select" => {
+            "siss" => [
+                {"transform" => "distinct", "column" => "id"}
+            ]
+        },
+        "from" => {"siss" => "sitem"},
+        "where" => {
+            "subscription" => $sub_id,
+            "+sitem" => {"date_received" => undef}
+        }
+    }) or return $e->die_event;
+
+    $client->respond($e->retrieve_serial_issuance($_->{"id"}))
+        foreach (@$issuance_ids);
+
+    $e->disconnect;
+    undef;
+}
+
+__PACKAGE__->register_method(
+    "method" => "receive_items_by_id",
+    "api_name" => "open-ils.serial.items.receive_by_id",
+    "stream" => 1,
+    "signature" => {
+        "desc" => "Given sitem IDs, just set their date_received to now()",
+        "params" => [
+            {"desc" => "Authtoken", "type" => "string"},
+            {"desc" => "Serial Item IDs", "type" => "array"},
+        ],
+        "return" => {
+            "desc" => "Stream of updated items", "type" => "object"
+        }
+    }
+);
+
+sub receive_items_by_id {
+    my ($self, $client, $auth, $id_list) = @_;
+
+    my $e = new_editor("authtoken" => $auth, "xact" => 1);
+    return $e->die_event unless $e->checkauth;
+
+    # XXX permissions
+
+    # for now this function doesn't do nearly enough. simply sets
+    # date_received to now()
+
+    my @results = ();
+    foreach (@$id_list) {
+        my $sitem = $e->retrieve_serial_item($_) or return $e->die_event;
+
+        $sitem->date_received("now");
+        $e->update_serial_item($sitem) or return $e->die_event;
+
+        push @results, $sitem;
+    }
+
+    $e->commit;
+    $client->respond($_) foreach @results;
+    undef;
+}
+
 1;
index f61896e..f9d41a7 100644 (file)
 <!ENTITY staff.cat.opac.view_holds.label "View Holds">
 <!ENTITY staff.cat.opac.view_orders.accesskey "r">
 <!ENTITY staff.cat.opac.view_orders.label "View/Place Orders">
+<!ENTITY staff.cat.opac.batch_receive.accesskey "i">
+<!ENTITY staff.cat.opac.batch_receive.label "Serials Batch Receive">
 <!ENTITY staff.cat.popup.add_to_bucket "Add to Bucket">
 <!ENTITY staff.cat.popup.add_to_bucket.key "">
 <!ENTITY staff.cat.popup.browse.record.tab.key "">
 <!ENTITY staff.main.menu.admin.server_admin.booking.resource_attr_map.label "Resource Attribute Maps">
 <!ENTITY staff.main.menu.admin.server_admin.booking.resource_attr_map.accesskey "M">
 
+<!ENTITY staff.main.menu.admin.server_admin.serial.label "Serials">
+<!ENTITY staff.main.menu.admin.server_admin.serial.accesskey "S">
+<!ENTITY staff.main.menu.admin.server_admin.serial.subscription.label "Subscriptions">
+<!ENTITY staff.main.menu.admin.server_admin.serial.subscription.accesskey "S">
+<!ENTITY staff.main.menu.admin.server_admin.serial.distribution.label "Distributions">
+<!ENTITY staff.main.menu.admin.server_admin.serial.distribution.accesskey "D">
+<!ENTITY staff.main.menu.admin.server_admin.serial.stream.label "Streams">
+<!ENTITY staff.main.menu.admin.server_admin.serial.stream.accesskey "T">
+<!ENTITY staff.main.menu.admin.server_admin.serial.routing_list_user.label "Routing List Users">
+<!ENTITY staff.main.menu.admin.server_admin.serial.routing_list_user.accesskey "R">
+<!ENTITY staff.main.menu.admin.server_admin.serial.caption_and_pattern.label "Captions and Patterns">
+<!ENTITY staff.main.menu.admin.server_admin.serial.caption_and_pattern.accesskey "C">
+
 <!ENTITY staff.main.menu.admin.developer.label "For developers...">
 <!ENTITY staff.main.menu.admin.download_patrons.accesskey "D">
 <!ENTITY staff.main.menu.admin.download_patrons.label "Download Offline Patron List">
 <!ENTITY staff.main.menu.acq.create_invoice.label "Create Invoice">
 <!ENTITY staff.main.menu.acq.create_invoice.accesskey "V">
 
+<!ENTITY staff.main.menu.serial.label "Serials">
+<!ENTITY staff.main.menu.serial.accesskey "S">
+<!ENTITY staff.main.menu.serial.batch_receive.label "Batch Receive">
+<!ENTITY staff.main.menu.serial.batch_receive.accesskey "B">
+
 <!ENTITY staff.main.menu.booking.label "Booking">
 <!ENTITY staff.main.menu.booking.accesskey "B">
 <!ENTITY staff.main.menu.booking.reservation.label_alt "Create or Cancel Reservations">
 <!ENTITY staff.serial.ssub_editor.create.accesskey "C">
 <!ENTITY staff.serial.ssub_editor.notes "Subscription Notes">
 <!ENTITY staff.serial.ssub_editor.notes.accesskey "N">
+
+<!ENTITY staff.serial.batch_receive "Batch Receive">
+<!ENTITY staff.serial.batch_receive.bib_search_term "Enter an identifier for a bibliographic record:">
+<!ENTITY staff.serial.batch_receive.bib_search_term.accesskey "B">
+<!ENTITY staff.serial.batch_receive.find_record "Find Record">
+<!ENTITY staff.serial.batch_receive.find_record.accesskey "F">
+<!ENTITY staff.serial.batch_receive.title "Title:">
+<!ENTITY staff.serial.batch_receive.author "Author:">
+<!ENTITY staff.serial.batch_receive.fulfilling_sub "Fulfilling Subscription:">
+<!ENTITY staff.serial.batch_receive.choose_sub "Choose a Subscription:">
+<!ENTITY staff.serial.batch_receive.choose_sub.accesskey "S">
+<!ENTITY staff.serial.batch_receive.choose_issuance "Choose an Issuance:">
+<!ENTITY staff.serial.batch_receive.choose_issuance.accesskey "I">
+<!ENTITY staff.serial.batch_receive.next "Next">
+<!ENTITY staff.serial.batch_receive.next.accesskey "N">
+<!ENTITY staff.serial.batch_receive.issuance "Issuance:">
+<!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.note "Note">
+<!ENTITY staff.serial.batch_receive.copy_loc "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?">
+<!ENTITY staff.serial.batch_receive.recieve_selected "Receive Selected Items">
+<!ENTITY staff.serial.batch_receive.start_over "Start Over">
+<!ENTITY staff.serial.batch_receive.start_over.accesskey "O">
+
 <!ENTITY staff.survey.wizard.page1 "Initial Settings">
 <!ENTITY staff.survey.wizard.page2 "Add Questions for Survey:">
 <!ENTITY staff.survey.wizard.title "Add a Survey Wizard">
index cdbf7e9..e07584b 100644 (file)
@@ -538,6 +538,28 @@ function open_marc_editor(rec, label) {
     };
 }
 
+function serials_mgmt_new_tab() {
+    try {
+        /* XXX should the following be put into a function somewhere? the gist
+         * of this setting up of content_params seems to be duplicated all
+         * over the place.
+         */
+        var content_params = {"session": ses(), "authtime": ses("authtime")};
+        ["url_prefix", "new_tab", "set_tab", "close_tab", "new_patron_tab",
+            "set_patron_tab", "volume_item_creator", "get_new_session",
+            "holdings_maintenance_tab", "set_tab_name", "open_chrome_window",
+            "url_prefix", "network_meter", "page_meter", "set_statusbar",
+            "set_help_context"
+        ].forEach(function(k) { content_params[k] = xulG[k]; });
+
+        xulG.new_tab(
+            xulG.url_prefix(urls.XUL_SERIAL_RECORD_ENTRY), {}, content_params
+        );
+    } catch (E) {
+        g.error.sdump('D_ERROR', E);
+    }
+}
+
 function bib_in_new_tab() {
     try {
         var url = browser_frame.contentWindow.g.browser.controller.view.browser_browser.contentWindow.wrappedJSObject.location.href;
@@ -565,6 +587,30 @@ function bib_in_new_tab() {
     }
 }
 
+function batch_receive_in_new_tab() {
+    try {
+        var content_params = {"session": ses(), "authtime": ses("authtime")};
+
+        ["url_prefix", "new_tab", "set_tab", "close_tab", "new_patron_tab",
+            "set_patron_tab", "volume_item_creator", "get_new_session",
+            "holdings_maintenance_tab", "set_tab_name", "open_chrome_window",
+            "url_prefix", "network_meter", "page_meter", "set_statusbar",
+            "set_help_context"
+        ].forEach(function(k) { content_params[k] = xulG[k]; });
+
+        xulG.new_tab(
+            xulG.url_prefix(urls.XUL_SERIAL_BATCH_RECEIVE) +
+                "?docid=" + window.escape(docid), {
+                "tab_name": $("offlineStrings").getString(
+                    "menu.cmd_serial_batch_receive.tab"
+                )
+            }, content_params
+        );
+    } catch (E) {
+        g.error.sdump("D_ERROR", E);
+    }
+}
+
 function remove_me() {
     var url = xulG.url_prefix( urls.XUL_BIB_BRIEF ) + '?docid=' + window.escape(docid);
     dump('removing ' + url + '\n');
index dd26a6d..0c9396f 100644 (file)
@@ -74,6 +74,7 @@
                     </menupopup>
                 </menu>
                 <menuitem id="serctrl_view" label="&staff.serial.serctrl_view.label;" oncommand="set_serctrl_view();"/>
+                <menuitem label="&staff.cat.opac.batch_receive.label;" accesskey="&staff.cat.opac.batch_receive.accesskey;" id="batch_receive" oncommand="batch_receive_in_new_tab();"/>
                 </menupopup>
                 </menu>
             </menubar>
index 070cf40..38d096c 100644 (file)
@@ -458,5 +458,6 @@ var urls = {
     'EG_WEB_BASE' : '/eg',
     'XUL_LOCAL_ADMIN_BASE' : '/xul/server/admin',
     'XUL_REPORTS' : '/reports/oils_rpt.xhtml',
-    'EG_ACQ_PO_VIEW' : '/eg/acq/po/view'
+    'EG_ACQ_PO_VIEW' : '/eg/acq/po/view',
+    'XUL_SERIAL_BATCH_RECEIVE': '/xul/server/serial/batch_receive.xul'
 }
index 07c9f4a..d6d4acd 100644 (file)
@@ -233,6 +233,7 @@ menu.cmd_acq_new_brief_record.tab=New Brief Record
 menu.cmd_acq_po.tab=Purchase Orders
 menu.cmd_acq_user_requests.tab=Patron Requests
 menu.cmd_acq_claim_eligible.tab=Claim-Ready Items
+menu.cmd_serial_batch_receive.tab=Batch Receive
 menu.cmd_booking_resource.tab=Resources
 menu.cmd_booking_reservation.tab=Reservations
 menu.cmd_booking_reservation_pickup.tab=Reservation Pickup
index 0abd318..45cef07 100644 (file)
@@ -53,3 +53,13 @@ staff.serial.manage_subs.delete_ssub.confirm=Are you sure you would like to dele
 staff.serial.manage_subs.delete_ssub.confirm.plural=Are you sure you would like to delete these %1$s subscriptions?
 staff.serial.manage_subs.delete_ssub.title=Delete Subscriptions?
 staff.serial.manage_subs.delete_ssub.override=Override Delete Failure? Doing so will delete all related data as well!
+batch_receive.bib_lookup.empty=Enter a search term.
+batch_receive.bib_lookup.multiple=Multiple matching records found. Please use a more specific identifier, or use the catalog to find the exact record you want.
+batch_receive.bib_lookup.not_found=No matching records found with any subscriptions attached.
+batch_receive.issuance_lookup.error=Problem retrieving issuances related to subscription.
+batch_receive.issuance_lookup.none=There are no receivable issuances.
+batch_receive.item_lookup.none=Could not retrieve receivable items for this issuance.
+batch_receive.autogen_barcodes.questionable=There are already barcodes entered further down the list than the one you just entered.\nFill the intervening fields with auto-generated barcodes?
+batch_receive.autogen_barcodes.remove=Clear the barcodes that have already been auto-generated?
+batch_receive.none=[None]
+batch_receive.apply=Apply
diff --git a/Open-ILS/xul/staff_client/server/serial/batch_receive.js b/Open-ILS/xul/staff_client/server/serial/batch_receive.js
new file mode 100644 (file)
index 0000000..fce90ac
--- /dev/null
@@ -0,0 +1,711 @@
+dojo.require("dojo.cookie");
+dojo.require("dojo.date.locale");
+dojo.require("dojo.date.stamp");
+dojo.require("openils.Util");
+dojo.require("openils.CGI");
+
+var authtoken;
+var batch_receiver;
+
+String.prototype.trim = function() {return this.replace(/^\s*(.+)\s*$/,"$1");}
+
+/**
+ * hard_empty() is needed because dojo.empty() doesn't seem to work on
+ * XUL nodes. This also means that dojo.place() with a position argument of
+ * "only" doesn't do what it should, but calling hard_empty() on the refnode
+ * first will do the trick.
+ */
+function hard_empty(node) {
+    if (typeof(node) == "string")
+        node = dojo.byId(node);
+    if (node)
+        dojo.forEach(node.childNodes, dojo.destroy);
+}
+
+function hide(e) {
+    if (typeof(e) == "string") e = dojo.byId(e);
+    openils.Util.addCSSClass(e, "hideme");
+}
+
+function show(e) {
+    if (typeof(e) == "string") e = dojo.byId(e);
+    openils.Util.removeCSSClass(e, "hideme");
+}
+
+function busy(on) {
+    if (typeof(busy._window) == "undefined")
+        busy._window = dojo.query("window")[0];
+    busy._window.style.cursor = on ? "wait" : "auto";
+}
+
+function S(k) {
+    return dojo.byId("serialStrings").getString("batch_receive." + k).
+        replace("\\n", "\n");
+}
+
+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 num_sort(a, b) {
+    [a, b] = [Number(a), Number(b)];
+    return a > b ? 1 : (a < b ? -1 : 0);
+}
+
+function BatchReceiver() {
+    var self = this;
+
+    this._init = function(bib_id) {
+        hide("batch_receive_sub");
+        hide("batch_receive_entry");
+        hide("batch_receive_bibdata_bits");
+        hide("batch_receive_sub_bits");
+        hide("batch_receive_issuance_bits");
+        hide("batch_receive_issuance");
+
+        dojo.byId("bib_lookup_submit").disabled = false;
+        dojo.byId("bib_search_term").value = "";
+
+        if (!bib_id) {
+            show("batch_receive_bib");
+            dojo.byId("bib_search_term").focus();
+        }
+
+        if (!this.entry_tbody) {
+            this.entry_tbody = dojo.byId("entry_tbody");
+            this.template = this.entry_tbody.removeChild(
+                dojo.byId("entry_template")
+            );
+        }
+
+        this._clear_entry_batch_row();
+
+        this._copy_loc_by_lib = {};
+
+        /* empty the entry receiving table if we're starting over */
+        if (this.item_cache) {
+            for (var id in this.item_cache)
+                this.finish_receipt(this.item_cache[id]);
+        }
+
+        this.rows = {};
+        this.item_cache = {};
+
+        if (bib_id)
+            this.bib_lookup(bib_id, null, true);
+
+        busy(false);
+    };
+
+    this._clear_entry_batch_row = function() {
+        dojo.forEach(
+            dojo.byId("entry_batch_row").childNodes,
+            function(node) {
+                if (node.nodeType == 1 &&
+                    node.getAttribute("name") != "barcode")
+                    hard_empty(node);
+            }
+        );
+    };
+
+    this._show_bibdata_bits = function() {
+        hard_empty("title_here");
+        dojo.byId("title_here").appendChild(T(this.bibdata.mvr.title()));
+        hard_empty("author_here");
+
+        if (this.bibdata.mvr.author()) {
+            dojo.byId("author_here").appendChild(T(this.bibdata.mvr.author()));
+            show("author_here_holder");
+        } else {
+            hide("author_here_holder");
+        }
+
+        show("batch_receive_bibdata_bits");
+    };
+
+    this._sub_label = function(sub) {
+        /* XXX use a formatting string from serial.properties */
+        return sub.id() + ": (" + sub.owning_lib().shortname() + ") " +
+            D(sub.start_date()) + " - " + D(sub.end_date());
+    };
+
+    this._show_sub_bits = function() {
+        hard_empty("sublabel_here");
+        dojo.place(
+            T(this._sub_label(this.sub)),
+            "sublabel_here",
+            "only"
+        );
+        hide("batch_receive_sub");
+        show("batch_receive_sub_bits");
+    };
+
+    this._show_issuance_bits = function() {
+        hide("batch_receive_issuance");
+        hard_empty("issuance_label_here");
+        dojo.place(
+            T(this.issuance.label()),
+            "issuance_label_here",
+            "only"
+        );
+        show("batch_receive_issuance_bits");
+    }
+
+    this._get_receivable_issuances = function() {
+        var issuances = [];
+
+        busy(true);
+        try {
+            fieldmapper.standardRequest(
+                ["open-ils.serial", "open-ils.serial.issuances.receivable"], {
+                    "params": [authtoken, this.sub.id()],
+                    "async": false,
+                    "onresponse": function(r) {
+                        if (r = openils.Util.readResponse(r))
+                            issuances.push(r);
+                    }
+                }
+            );
+        } catch (E) {
+            alert(E);
+        }
+        busy(false);
+
+        return issuances;
+    };
+
+    this._build_circ_mod_dropdown = function() {
+        if (!this._built_circ_mod_dropdown) {
+            var menulist = dojo.create("menulist");
+            var menupopup = dojo.create("menupopup", null, menulist, "only");
+            dojo.create(
+                "menuitem", {"value": 0, "label": S("none")},
+                menupopup, "first"
+            );
+
+            var mods = [];
+            fieldmapper.standardRequest(
+                ["open-ils.circ", "open-ils.circ.circ_modifier.retrieve.all"], {
+                    "params": [],
+                    "async": false,
+                    "onresponse": function(r) {
+                        if (mods = openils.Util.readResponse(r)) {
+                            mods.forEach(
+                                function(mod) {
+                                    dojo.create(
+                                        "menuitem", {
+                                            "value": mod, "label": mod
+                                        }, menupopup, "last"
+                                    );
+                                }
+                            );
+                        }
+                    }
+                }
+            );
+            if (!mods.length) {
+                /* in this case, discard menulist and menupopup */
+                this._built_circ_mod_dropdown =
+                    dojo.create("description", {"value": "-"});
+            } else {
+                this._built_circ_mod_dropdown = menulist;
+            }
+        }
+
+        return dojo.clone(this._built_circ_mod_dropdown);
+    };
+
+    this._extend_circ_mod_for_batch = function(control) {
+        dojo.create(
+            "menuitem", {"value": -1, "label": "---"},
+            dojo.query("menupopup", control)[0],
+            "first"
+        );
+        return control;
+    };
+
+    this._build_copy_loc_dropdown = function(locs, add_unset_value) {
+        var menulist = dojo.create("menulist");
+        var menupopup = dojo.create("menupopup", null, menulist, "only");
+
+        if (add_unset_value) {
+            dojo.create(
+                "menuitem", {"value": -1, "label": "---"}, menupopup, "first"
+            );
+        }
+
+        locs.forEach(
+            function(loc) {
+                dojo.create(
+                    "menuitem", {
+                        "value": loc.id(),
+                        "label": "(" + loc.owning_lib().shortname() + ") " +
+                            loc.name() /* XXX i18n */
+                    }, menupopup, "last"
+                );
+            }
+        );
+
+        return menulist;
+    };
+
+    this._get_copy_locs_for_lib = function(lib) {
+        if (!this._copy_loc_by_lib[lib]) {
+            fieldmapper.standardRequest(
+                ["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;
+                    }
+                }
+            );
+        }
+
+        return this._copy_loc_by_lib[lib];
+    };
+
+    this._build_receive_toggle = function(item) {
+        return dojo.create(
+            "checkbox", {
+                "oncommand": function(ev) {
+                       self._disable_row(item.id(), !ev.target.checked);
+                },
+                "checked": "true"
+            }
+        );
+    }
+
+    this._disable_row = function(item_id, disabled) {
+        var row = this.rows[item_id];
+        dojo.query("textbox,menulist", row).forEach(
+            function(element) { element.disabled = disabled; }
+        );
+    };
+
+    this._row_disabled = function(row) {
+        if (typeof(row) == "string") row = this.rows[row];
+        return !dojo.query("checkbox", row)[0].checked;
+    };
+
+    this._row_field_value = function(row, field, value) {
+        if (typeof(row) == "string") row = this.rows[row];
+
+        var node = dojo.query("*", node_by_name(field, row))[0];
+
+        if (typeof(value) == "undefined")
+            return node.value;
+        else
+            node.value = value;
+    }
+
+       this._user_wants_autogen = function() {
+        return dojo.byId("autogen_barcodes").checked;
+    };
+
+    this._get_autogen_potentials = function(item_id) {
+        var hit_a_wall = false;
+
+        return [openils.Util.objectProperties(this.rows).sort(num_sort).filter(
+            function(id) {
+                if (hit_a_wall) {
+                    return false;
+                } else if (id <= item_id || self._row_disabled(id)) {
+                    return false;
+                } else if (self._row_field_value(id, "barcode")) {
+                    hit_a_wall = true;
+                    return false;
+                } else {
+                    return true;
+                }
+            }
+        ), hit_a_wall];
+    };
+
+    this._prepare_autogen_control = function() {
+        dojo.attr("autogen_barcodes",
+            "command", function(ev) {
+                if (!ev.target.checked) {
+                    var list = self._have_autogen_barcodes();
+                    if (list.length && confirm(S("autogen_barcodes.remove"))) {
+                        list.forEach(
+                            function(id) {
+                                self._row_field_value(id, "barcode", "");
+                                self.rows[id]._has_autogen_barcode = false;
+                            }
+                        );
+                    }
+                }
+            }
+        );
+    };
+
+    this._have_autogen_barcodes = function() {
+        var list = [];
+        for (var id in this.rows)
+            if (this.rows[id]._has_autogen_barcode) list.push(id);
+        return list;
+    };
+
+    this._set_all_enabled_rows = function(key, value) {
+        /* do NOT do trimming here, set whitespace as is. */
+        for (var id in this.rows) {
+            if (!this._row_disabled(id))
+                this._row_field_value(id, key, value);
+        }
+    };
+
+    this.bib_lookup = function(bib_search_term, evt, is_actual_id) {
+        if (evt && evt.keyCode != 13) return;
+
+        if (!bib_search_term) {
+            var bib_search_term = dojo.byId("bib_search_term").value.trim();
+            if (!bib_search_term.length) {
+                alert(S("bib_lookup.empty"));
+                return;
+            }
+        }
+
+        hide("batch_receive_sub");
+        hide("batch_receive_entry");
+
+        busy(true);
+        dojo.byId("bib_lookup_submit").disabled = true;
+        fieldmapper.standardRequest(
+            ["open-ils.serial",
+                "open-ils.serial.biblio.record_entry.by_identifier.atomic"], {
+                "params": [
+                    bib_search_term, {
+                        "require_subscriptions": true,
+                        "add_mvr": true,
+                        "is_actual_id": is_actual_id
+                    }
+                ],
+                "async": false,
+                "oncomplete": function(r) {
+                    /* These two things better come before readResponse(), which
+                     * can throw exceptions. */
+                    busy(false);
+                    dojo.byId("bib_lookup_submit").disabled = false;
+
+                    var list = openils.Util.readResponse(r, false, true);
+                    if (list && list.length) {
+                        if (list.length > 1) {
+                            /* XXX TODO just let the user pick one from a list,
+                             * although this circumstance seems really
+                             * unlikely.  It just can't happen for TCN, and
+                             * wouldn't be likely for ISxN or UPC... ? */
+                            alert(S("bib_lookup.multiple"));
+                        } else {
+                            self.bibdata = list[0];
+                            self._show_bibdata_bits();
+                            self.choose_subscription();
+                        }
+                    } else {
+                        alert(S("bib_lookup.not_found"));
+                        if (is_actual_id) {
+                            self._init();
+                        } else {
+                            dojo.byId("bib_search_term").reset();
+                            dojo.byId("bib_search_term").focus();
+                        }
+                    }
+                }
+            }
+        );
+    };
+
+    this.choose_subscription = function() {
+        hide("batch_receive_bib");
+        hide("batch_receive_entry");
+        hide("batch_receive_sub_bits");
+        hide("batch_receive_issuance");
+
+        var subs = this.bibdata.bre.subscriptions();
+
+        if (subs.length > 1) {
+            var menulist = dojo.create("menulist", {"id": "sub_chooser"});
+            var menupopup = dojo.create("menupopup", {}, menulist, "only");
+
+            this.bibdata.bre.subscriptions().forEach(
+                function(sub) {
+                    dojo.create(
+                        "menuitem", {
+                            "label": self._sub_label(sub),
+                            "value": sub.id()
+                        }, menupopup, "last"
+                    );
+                }
+            );
+
+            hard_empty(dojo.byId("sub_chooser_here"));
+
+            dojo.place(menulist, dojo.byId("sub_chooser_here"), "only");
+            show("batch_receive_sub");
+        } else {
+            this.choose_issuance(subs[0]);
+        }
+    };
+
+    this.choose_issuance = function(sub) {
+        hide("batch_receive_bib");
+        hide("batch_receive_entry");
+        hide("batch_receive_sub");
+
+        if (typeof(sub) == "undefined") {   /* sub chosen from menu */
+            var sub_id = dojo.byId("sub_chooser").value;
+            this.sub = this.bibdata.bre.subscriptions().filter(
+                function(o) { return o.id() == sub_id; }
+            )[0];
+        } else {    /* only one sub possible, passed in directly */
+            this.sub = sub;
+        }
+
+        this._show_sub_bits();
+
+        this.issuances = this._get_receivable_issuances();   /* sync */
+
+        if (this.issuances.length > 1) {
+            var menulist = dojo.create("menulist", {"id": "issuance_chooser"});
+            var menupopup = dojo.create("menupopup", {}, menulist, "only");
+
+            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;
+                    else return 0;
+                }
+            ).forEach(
+                function(issuance) {
+                    dojo.create(
+                        "menuitem", {
+                            "label": issuance.label(),
+                            "value": issuance.id()
+                        }, menupopup, "last"
+                    );
+                }
+            );
+
+            hard_empty("issuance_chooser_here");
+            dojo.place(menulist, dojo.byId("issuance_chooser_here"), "only");
+
+            show("batch_receive_issuance");
+        } else if (this.issuances.length) {
+            this.load_entry_form(this.issuances[0]);
+        } else {
+            alert(S("issuance_lookup.none"));
+            this._init();
+        }
+
+    };
+
+    this.load_entry_form = function(issuance) {
+        if (typeof(issuance) == "undefined") {
+            var issuance_id = dojo.byId("issuance_chooser").value;
+            this.issuance = this.issuances.filter(
+                function(o) { return o.id() == issuance_id; }
+            )[0];
+        } else {
+            this.issuance = issuance;
+        }
+
+        this._show_issuance_bits();
+        this._prepare_autogen_control();
+
+        busy(true);
+
+        fieldmapper.standardRequest(
+            ["open-ils.serial",
+                "open-ils.serial.items.receivable.by_issuance.atomic"], {
+                "params": [authtoken, this.issuance.id()],
+                "async": true,
+                "onresponse": function(r) {
+                    busy(false);
+
+                    if (list = openils.Util.readResponse(r, false, true)) {
+
+                        if (list.length) {
+                            busy(true);
+                            show("form_holder");
+
+                            list.forEach(function(o) {self.add_entry_row(o);});
+                            if (list.length > 1) {
+                                self.build_batch_entry_row();
+                                show("batch_receive_entry");
+                            }
+
+                            busy(false);
+                        } else {
+                            alert(S("item_lookup.none"));
+                            if (self.issuances.length) self.choose_issuance();
+                            else self._init();
+                        }
+                    }
+                }
+            }
+        );
+
+    };
+
+    this.build_batch_entry_row = function() {
+        var row = dojo.byId("entry_batch_row");
+
+        this.batch_controls = {};
+
+        node_by_name("note", row).appendChild(
+            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),
+                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("price", row).appendChild(
+            this.batch_controls.price = dojo.create("textbox", {"size": 9})
+        );
+
+        node_by_name("apply", row).appendChild(
+            dojo.create("button", {
+                "label": S("apply"),
+                "oncommand": function() { self.apply_batch_values(); }
+            })
+        );
+    };
+
+    this.apply_batch_values = function() {
+        var row = dojo.byId("entry_batch_row");
+
+        for (var key in this.batch_controls) {
+            var value = this.batch_controls[key].value;
+            if (value != "" && value != -1)
+                this._set_all_enabled_rows(key, value);
+        }
+    };
+
+    this.add_entry_row = function(item) {
+        this.item_cache[item.id()] = item;
+        var row = this.rows[item.id()] = dojo.clone(this.template);
+
+        function n(s) { return node_by_name(s, row); }    /* typing saver */
+
+        n("holding_lib").appendChild(
+            T(item.stream().distribution().holding_lib().shortname())
+        );
+
+        n("barcode").appendChild(
+            dojo.create(
+                "textbox", {
+                    "size": 15,
+                    "tabindex": 10000 + Number(item.id()), /* is this right? */
+                    "onchange": function() {
+                        self.autogen_if_appropriate(this, item.id());
+                    }
+                }
+            )
+        );
+
+        n("copy_loc").appendChild(
+            this._build_copy_loc_dropdown(
+                this._get_copy_locs_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("price").appendChild(dojo.create("textbox", {"size": 9}));
+        n("receive").appendChild(this._build_receive_toggle(item));
+
+        this.entry_tbody.appendChild(row);
+    };
+
+    this.receive = function() {
+        var recv_ids = [];
+        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);
+        }
+
+        busy(true);
+        fieldmapper.standardRequest(
+            ["open-ils.serial", "open-ils.serial.items.receive_by_id"], {
+                "params": [authtoken, recv_ids],
+                "async": true,
+                "oncomplete": function(r) {
+                    try {
+                        while (item = openils.Util.readResponse(r))
+                            self.finish_receipt(item);
+                    } catch (E) {
+                        alert(E);
+                    }
+                    busy(false);
+                }
+            }
+        );
+    };
+
+    this.finish_receipt = function(item) {
+        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) {
+        if (this._user_wants_autogen() && textbox.value) {
+            var [list, question] = this._get_autogen_potentials(item_id);
+            if (list.length) {
+                if (question && !confirm(S("autogen_barcodes.questionable")))
+                    return;
+
+                busy(true);
+                try {
+                    fieldmapper.standardRequest(
+                        ["open-ils.cat", "open-ils.cat.item.barcode.autogen"], {
+                            "params": [authtoken, textbox.value, list.length],
+                            "async": false,
+                            "onresponse": function(r) {
+                                r = openils.Util.readResponse(r, false, true);
+                                if (r) {
+                                    for (var i = 0; i < r.length; i++) {
+                                        var row = self.rows[list[i]];
+                                        self._row_field_value(
+                                            row, "barcode", r[i]
+                                        );
+                                        row._has_autogen_barcode = true;
+                                    }
+                                }
+                            }
+                        }
+                    );
+                } catch (E) {
+                    alert(E);
+                }
+                busy(false);
+            } /* do nothing for empty list */
+        }
+    };
+
+    this._init.apply(this, arguments);
+}
+
+function my_init() {
+    var cgi = new openils.CGI();
+
+    authtoken = (typeof ses == "function" ? ses() : 0) ||
+        cgi.param("ses") || dojo.cookie("ses");
+
+    batch_receiver = new BatchReceiver(cgi.param("docid") || null);
+}
diff --git a/Open-ILS/xul/staff_client/server/serial/batch_receive.xul b/Open-ILS/xul/staff_client/server/serial/batch_receive.xul
new file mode 100644 (file)
index 0000000..98c90f9
--- /dev/null
@@ -0,0 +1,32 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="/xul/server/skin/global.css" type="text/css"?>
+<?xml-stylesheet href="/xul/server/skin/serial.css" type="text/css"?>
+<!DOCTYPE window PUBLIC "" ""[
+    <!--#include virtual="/opac/locale/${locale}/lang.dtd"-->
+]>
+<?xul-overlay href="/xul/server/OpenILS/util_overlay.xul"?>
+<?xul-overlay href="/xul/server/serial/batch_receive_overlay.xul"?>
+
+<window id="batch_receive_win"
+    onload="try{my_init();font_helper();persist_helper();}catch(E){alert(E);}"
+    xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+    <script type="text/javascript">
+        var myPackageDir = "open_ils_staff_client";
+        var IAMXUL = true;
+        var g = {};
+    </script>
+
+    <scripts id="openils_util_scripts" />
+
+    <!-- JSAN is still needed for font_helper stuff, but I'm going to try
+        not to use it otherwise.  -->
+    <script type="text/javascript" src="/xul/server/main/JSAN.js" />
+
+    <messagecatalog id="serialStrings"
+        src="/xul/server/locale/<!--#echo var='locale'-->/serial.properties" />
+
+    <commandset />
+    <box id="batch_receive_main" />
+</window>
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
new file mode 100644 (file)
index 0000000..a96d4e7
--- /dev/null
@@ -0,0 +1,159 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE overlay PUBLIC "" ""[
+    <!--#include virtual="/opac/locale/${locale}/lang.dtd"-->
+]>
+<overlay id="batch_receive_overlay"
+    xmlns:h="http://www.w3.org/1999/xhtml"
+    xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+    <script type="text/javascript" src="/xul/server/serial/batch_receive.js" />
+
+    <box id="batch_receive_main" flex="1" orient="vertical" class="my_overflow">
+        <caption label="&staff.serial.batch_receive;" />
+
+        <vbox flex="1" id="batch_receve_main_action">
+            <vbox id="batch_receive_bib" class="hideme">
+                <hbox align="center" flex="0">
+                    <label
+                        control="bib_search_term"
+                        accesskey="&staff.serial.batch_receive.bib_search_term.accesskey;"
+                        value="&staff.serial.batch_receive.bib_search_term;" />
+                    <textbox id="bib_search_term"
+                        onkeypress="batch_receiver.bib_lookup(null, event);" />
+                    <button id="bib_lookup_submit"
+                        oncommand="batch_receiver.bib_lookup();"
+                        label="&staff.serial.batch_receive.find_record;"
+                        accesskey="&staff.serial.batch_receive.find_record.accesskey;" />
+                </hbox>
+            </vbox>
+
+            <vbox id="batch_receive_bibdata_bits" class="hideme">
+                <hbox>
+                    <label value="&staff.serial.batch_receive.title;" />
+                    <description id="title_here" />
+                </hbox>
+                <hbox id="author_here_holder" class="hideme">
+                    <label value="&staff.serial.batch_receive.author;" />
+                    <description id="author_here" />
+                </hbox>
+            </vbox>
+
+            <box id="batch_receive_sub_bits" class="hideme">
+                <label value="&staff.serial.batch_receive.fulfilling_sub;" />
+                <description id="sublabel_here" />
+            </box>
+
+            <vbox id="batch_receive_sub" class="hideme">
+                <hbox align="center">
+                    <label
+                        control="sub_chooser"
+                        value="&staff.serial.batch_receive.choose_sub;"
+                        accesskey="&staff.serial.batch_receive.choose_sub.accesskey;" />
+                    <box id="sub_chooser_here"></box>
+                    <button
+                        oncommand="batch_receiver.choose_issuance();"
+                        label="&staff.serial.batch_receive.next;"
+                        accesskey="&staff.serial.batch_receive.next.accesskey;" />
+                </hbox>
+            </vbox>
+
+            <box id="batch_receive_issuance_bits" class="hideme">
+                <label value="&staff.serial.batch_receive.issuance;" />
+                <description id="issuance_label_here" />
+            </box>
+
+            <vbox id="batch_receive_issuance" class="hideme">
+                <hbox align="center">
+                    <label
+                        control="issuance_chooser"
+                        value="&staff.serial.batch_receive.choose_issuance;"
+                        accesskey="&staff.serial.batch_receive.choose_issuance.accesskey;" />
+                    <box id="issuance_chooser_here"></box>
+                    <button
+                        oncommand="batch_receiver.load_entry_form();"
+                        label="&staff.serial.batch_receive.next;"
+                        accesskey="&staff.serial.batch_receive.next.accesskey;" />
+                </hbox>
+            </vbox>
+
+            <vbox id="batch_receive_no_entry" class="hideme">
+                <description value="&staff.serial.batch_receive.no_items;" />
+            </vbox>
+
+            <vbox id="batch_receive_entry" class="hideme">
+                <box class="hideme" id="form_holder">
+                    <!-- XXX should be a XUL grid instead of an HTML table -->
+                    <h:table>
+                        <h:thead>
+                            <h:tr>
+                                <h:th>
+                                    &staff.serial.batch_receive.org_unit;
+                                </h:th>
+                                <h:th>
+                                    &staff.serial.batch_receive.barcode;
+                                </h:th>
+                                <h:th>
+                                    &staff.serial.batch_receive.circ_mod;
+                                </h:th>
+                                <h:th>
+                                    &staff.serial.batch_receive.note;
+                                </h:th>
+                                <h:th>
+                                    &staff.serial.batch_receive.copy_loc;
+                                </h:th>
+                                <h:th>
+                                    &staff.serial.batch_receive.price;
+                                </h:th>
+                                <h:th>
+                                    &staff.serial.batch_receive.receive;
+                                </h:th>
+                            </h:tr>
+                        </h:thead>
+                        <h:tbody id="entry_batch_tbody">
+                            <h:tr id="entry_batch_row">
+                                <h:td name="holding_lib"><!-- empty --></h:td>
+                                <h:td name="barcode" align="center">
+                                    <checkbox
+                                        id="autogen_barcodes"
+                                        label="&staff.serial.batch_receive.auto_generate;" />
+                                </h:td>
+                                <h:td name="circ_mod" align="center"></h:td>
+                                <h:td name="note"></h:td>
+                                <h:td name="copy_loc" align="center"></h:td>
+                                <h:td name="price"></h:td>
+                                <h:td name="receive"></h:td>
+                                <h:td name="apply"></h:td>
+                            </h:tr>
+                            <h:tr>
+                                <h:td colspan="8">
+                                    <h:hr size="4" />
+                                </h:td>
+                            </h:tr>
+                        </h:tbody>
+                        <h:tbody id="entry_tbody">
+                            <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="note"></h:td>
+                                <h:td name="copy_loc" align="center"></h:td>
+                                <h:td name="price"></h:td>
+                                <h:td name="receive" align="center"></h:td>
+                            </h:tr>
+                        </h:tbody>
+                    </h:table>
+                </box>
+                <hbox id="entry_submitter" flex="1">
+                    <button oncommand="batch_receiver.receive();"
+                        label="&staff.serial.batch_receive.recieve_selected;" />
+                </hbox>
+            </vbox>
+        </vbox>
+
+        <hbox>
+            <button oncommand="batch_receiver._init();"
+                label="&staff.serial.batch_receive.start_over;"
+                accesskey="&staff.serial.batch_receive.start_over.accesskey;" />
+        </hbox>
+    </box>
+</overlay>
diff --git a/Open-ILS/xul/staff_client/server/skin/serial.css b/Open-ILS/xul/staff_client/server/skin/serial.css
new file mode 100644 (file)
index 0000000..95af456
--- /dev/null
@@ -0,0 +1,11 @@
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+caption {
+    background-color: #00246b;
+    color: #ff6308;
+    font-size: 200%;
+    text-align: center;
+}
+label.receiving { width: 20em; text-align: right; }
+.hideme { display: none; }
+#batch_receive_entry { padding-top: 10px; }
+#entry_submitter { padding: 20px 0; }