From 763795b967a0afd1c4f4ab37db6ba40cb33f110e Mon Sep 17 00:00:00 2001
From: senator <senator@dcc99617-32d9-48b4-a31d-7c20da2025e4>
Date: Tue, 20 Apr 2010 20:20:32 +0000
Subject: [PATCH] Acq: large parts of the UI for making claims to a vendor
 against lineitems

Large parts of this are done, but not yet all of them.  Woof.  If lineitems
are claim-ready (have an attached claim policy, which has claim event types
that have intervals that have passed since the items were ordered), and you
want to find them with no search criteria other than ordering agency, then
you can issue claims and print a very primitive voucher.  Bits of niceness
are still to come, such as attaching policies, claiming from other LI table-
based interfaces, etc.


git-svn-id: svn://svn.open-ils.org/ILS/trunk@16282 dcc99617-32d9-48b4-a31d-7c20da2025e4
---
 Open-ILS/examples/fm_IDL.xml                       |   2 +-
 .../src/perlmods/OpenILS/Application/Acq/Claims.pm |  67 +++++--
 .../src/perlmods/OpenILS/Application/Acq/Order.pm  |  22 +--
 Open-ILS/src/sql/Pg/002.schema.config.sql          |   2 +-
 Open-ILS/src/sql/Pg/950.data.seed-values.sql       |  60 ++++++
 .../src/sql/Pg/upgrade/0238.data.claim_voucher.sql |  66 +++++++
 Open-ILS/web/css/skin/default/acq.css              |  13 +-
 Open-ILS/web/js/dojo/openils/acq/nls/acq.js        |   5 +-
 .../js/ui/default/acq/financial/claim_eligible.js  | 220 +++++++++++++++++++++
 Open-ILS/web/js/ui/default/acq/po/view_po.js       |  24 +--
 Open-ILS/web/opac/locale/en-US/lang.dtd            |   2 +
 .../default/acq/financial/claim_eligible.tt2       |  54 +++++
 Open-ILS/web/templates/default/acq/po/view.tt2     |   4 +-
 .../xul/staff_client/chrome/content/main/menu.js   |   4 +
 .../chrome/content/main/menu_frame_menus.xul       |   2 +
 .../chrome/locale/en-US/offline.properties         |   1 +
 16 files changed, 504 insertions(+), 44 deletions(-)
 create mode 100644 Open-ILS/src/sql/Pg/upgrade/0238.data.claim_voucher.sql
 create mode 100644 Open-ILS/web/js/ui/default/acq/financial/claim_eligible.js
 create mode 100644 Open-ILS/web/templates/default/acq/financial/claim_eligible.tt2

diff --git a/Open-ILS/examples/fm_IDL.xml b/Open-ILS/examples/fm_IDL.xml
index 3b758f5595..8066397b2d 100644
--- a/Open-ILS/examples/fm_IDL.xml
+++ b/Open-ILS/examples/fm_IDL.xml
@@ -6162,7 +6162,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 
 	<class id="acqclt" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="acq::claim_type" oils_persist:tablename="acq.claim_type" reporter:label="Claim Type">
 		<fields oils_persist:primary="id" oils_persist:sequence="acq.claim_type_id_seq">
-			<field reporter:label="Claim Type ID" name="id" reporter:datatype="id"/>
+			<field reporter:label="Claim Type ID" name="id" reporter:datatype="id" reporter:selector="code" />
 			<field reporter:label="Org Unit" name="org_unit" reporter:datatype="org_unit"/>
 			<field reporter:label="Code" name="code" reporter:datatype="text"/>
 			<field reporter:label="Description" name="description" reporter:datatype="text"/>
diff --git a/Open-ILS/src/perlmods/OpenILS/Application/Acq/Claims.pm b/Open-ILS/src/perlmods/OpenILS/Application/Acq/Claims.pm
index 6d4964c561..bffb25c51e 100644
--- a/Open-ILS/src/perlmods/OpenILS/Application/Acq/Claims.pm
+++ b/Open-ILS/src/perlmods/OpenILS/Application/Acq/Claims.pm
@@ -15,7 +15,7 @@ __PACKAGE__->register_method(
 	api_name	=> 'open-ils.acq.claim.eligible.lineitem_detail',
     stream => 1,
 	signature => {
-        desc => q/Locates lineitem_detail's that are eligible for claiming/,
+        desc => q/Locates lineitem_details that are eligible for claiming/,
         params => [
             {desc => 'Authentication token', type => 'string'},
             {   desc => q/
@@ -24,6 +24,7 @@ __PACKAGE__->register_method(
                     lineitem
                     lineitem_detail
                     claim_policy_action
+                    ordering_agency
                 /, 
                 type => 'object'
             },
@@ -77,6 +78,30 @@ sub claim_ready_items {
 }
 
 __PACKAGE__->register_method(
+    method => "claim_item",
+    api_name => "open-ils.acq.claim.lineitem",
+    stream => 1,
+    signature => {
+        desc => q/Initiates a claim for a lineitem/,
+        params => [
+            {desc => "Authentication token", type => "string"},
+            {desc => "Lineitem ID", type => "number"},
+            {desc => q/Claim (acqcl) ID.  If defined, attach new claim
+                events to this existing claim object/, type => "number"},
+            {desc => q/Claim Type (acqclt) ID.  If defined (and no claim is
+                defined), create a new claim with this type/, type => "number"},
+            {desc => "Note for the claim event", type => "string"},
+            {desc => q/Optional: Claim Policy Actions.  If not present,
+                claim events for all eligible claim policy actions will be
+                created.  This is an array of acqclpa IDs./,
+                type => "array"},
+        ],
+        return => {desc => "The claim events on success, Event on error",
+            type => "object", class => "acrlid"}
+    }
+);
+
+__PACKAGE__->register_method(
 	method => 'claim_item',
 	api_name	=> 'open-ils.acq.claim.lineitem_detail',
     stream => 1,
@@ -87,6 +112,7 @@ __PACKAGE__->register_method(
             {desc => 'Lineitem Detail ID', type => 'number'},
             {desc => 'Claim (acqcl) ID.  If defined, attach new claim events to this existing claim object', type => 'number'},
             {desc => 'Claim Type (acqclt) ID.  If defined (and no claim is defined), create a new claim with this type', type => 'number'},
+            {desc => "Note for the claim event", type => "string"},
             {   desc => q/
                 
                     Optional: Claim Policy Actions.  If not present, claim events 
@@ -109,7 +135,7 @@ sub claim_item {
     my $claim_type_id = shift;
     my $note = shift;
     my $policy_actions = shift;
-    my $only_eligible = shift;
+#   my $only_eligible = shift; # so far unused
 
     my $e = new_editor(xact => 1, authtoken=>$auth);
     return $e->die_event unless $e->checkauth;
@@ -122,6 +148,13 @@ sub claim_item {
         trigger_stuff => []
     };
 
+    my $lid_flesh = {
+        "flesh" => 2,
+        "flesh_fields" => {
+            "acqlid" => ["lineitem"], "jub" => ["purchase_order"],
+        }
+    };
+
     if($claim_id) {
         $claim = $e->retrieve_acq_claim($claim_id) or return $e->die_event;
     } elsif($claim_type_id) {
@@ -133,30 +166,36 @@ sub claim_item {
 
     if($self->api_name =~ /claim.lineitem_detail/) {
 
-        my $lid = $e->retrieve_acq_lineitem_detail([
-            $object_id,
-            {
-                flesh => 2,
-                flesh_fields => {
-                    acqlid => ['lineitem'],
-                    jub => ['purchase_order'],
-                }
-            }
-        ]) or return $e->die_event;
+        my $lid = $e->retrieve_acq_lineitem_detail([$object_id, $lid_flesh]) or
+            return $e->die_event;
         return $evt if 
             $evt = claim_lineitem_detail(
                 $e, $lid, $claim, $claim_type, $policy_actions, $note, $claim_events); 
 
     } elsif($self->api_name =~ /claim.lineitem/) {
+        my $lids = $e->search_acq_lineitem_detail([
+            {"lineitem" => $object_id, "cancel_reason" => undef},
+            $lid_flesh
+        ]) or return $e->die_event;
 
-        # TODO: add support for claiming from a lineitem
+        foreach my $lid (@$lids) {
+            return $evt if
+                $evt = claim_lineitem_detail(
+                    $e, $lid, $claim, $claim_type, $policy_actions,
+                    $note, $claim_events
+                );
+        }
     }
 
     $e->commit;
-    $conn->respond_complete($claim_events->{events});
 
     # create related A/T events
     $U->create_events_for_hook('claim_event.created', $_->[0], $_->[1]) for @{$claim_events->{trigger_stuff}};
+
+    # do voucher rendering and return result
+    $conn->respond($U->fire_object_event(
+        undef, "format.acqcle.html", $_->[0], $_->[1], "print-on-demand"
+    )) foreach @{$claim_events->{trigger_stuff}};
     return undef;
 }
 
diff --git a/Open-ILS/src/perlmods/OpenILS/Application/Acq/Order.pm b/Open-ILS/src/perlmods/OpenILS/Application/Acq/Order.pm
index 1a3d5f24c2..53c6d5ec76 100644
--- a/Open-ILS/src/perlmods/OpenILS/Application/Acq/Order.pm
+++ b/Open-ILS/src/perlmods/OpenILS/Application/Acq/Order.pm
@@ -761,16 +761,16 @@ sub fund_exceeds_balance_percent {
             ((($balance - $debit_amount) / $allocations) * 100) <
                 $fund->$method_name
         ) {
-                $e->event(
-                    new OpenILS::Event(
-                        $event_name, 
-                        "payload" => {
-                            "fund" => $fund,
-                            "debit_amount" => $debit_amount
-                        }
-                    )
-                );
-                return 1;
+            $logger->info("fund would hit a limit: " . $fund->id . ", $balance, $debit_amount, $allocations, $method_name");
+            $e->event(
+                new OpenILS::Event(
+                    $event_name,
+                    "payload" => {
+                        "fund" => $fund, "debit_amount" => $debit_amount
+                    }
+                )
+            );
+            return 1;
         }
     }
     return 0;
@@ -2079,7 +2079,7 @@ sub activate_purchase_order {
     return $e->die_event unless $e->checkauth;
     my $mgr = OpenILS::Application::Acq::BatchManager->new(editor => $e, conn => $conn);
     my $die_event = activate_purchase_order_impl($mgr, $po_id, $dry_run);
-    return $die_event if $die_event;
+    return $e->die_event if $die_event;
     if ($dry_run) {
         $e->rollback;
     } else {
diff --git a/Open-ILS/src/sql/Pg/002.schema.config.sql b/Open-ILS/src/sql/Pg/002.schema.config.sql
index e6f4e1373f..b5fd021443 100644
--- a/Open-ILS/src/sql/Pg/002.schema.config.sql
+++ b/Open-ILS/src/sql/Pg/002.schema.config.sql
@@ -60,7 +60,7 @@ CREATE TABLE config.upgrade_log (
     install_date    TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
 );
 
-INSERT INTO config.upgrade_log (version) VALUES ('0237'); -- dbs
+INSERT INTO config.upgrade_log (version) VALUES ('0238'); -- senator
 
 CREATE TABLE config.bib_source (
 	id		SERIAL	PRIMARY KEY,
diff --git a/Open-ILS/src/sql/Pg/950.data.seed-values.sql b/Open-ILS/src/sql/Pg/950.data.seed-values.sql
index ae9189a1d1..ddf29942e2 100644
--- a/Open-ILS/src/sql/Pg/950.data.seed-values.sql
+++ b/Open-ILS/src/sql/Pg/950.data.seed-values.sql
@@ -3677,6 +3677,66 @@ INSERT INTO action_trigger.environment ( event_def, path) VALUES
 INSERT INTO action_trigger.environment ( event_def, path) VALUES
     ( 20, 'usr.home_ou' );
 
+
+INSERT INTO action_trigger.hook (key, core_type, description, passive)
+    VALUES (
+        'format.acqcle.html',
+        'acqcle',
+        'Formats claim events into a voucher',
+        TRUE
+    );
+
+INSERT INTO action_trigger.event_definition (
+        id, active, owner, name, hook, group_field,
+        validator, reactor, granularity, template
+    ) VALUES (
+        21,
+        TRUE,
+        1,
+        'Claim Voucher',
+        'format.acqcle.html',
+        'claim',
+        'NOOP_True',
+        'ProcessTemplate',
+        'print-on-demand',
+$$
+[%- USE date -%]
+[%- SET claim = target.0.claim -%]
+<!-- This will need refined/prettified. -->
+<div class="acq-claim-voucher">
+    <h2>Claim: [% claim.id %] ([% claim.type.code %])</h2>
+    <h3>Against: [%- helpers.get_li_attr("title", "", claim.lineitem_detail.lineitem.attributes) -%]</h3>
+    <ul>
+        [% FOR event IN target %]
+        <li>
+            Event type: [% event.type.code %]
+            [% IF event.type.library_initiated %](Library initiated)[% END %]
+            <br />
+            Event date: [% event.event_date %]<br />
+            Order date: [% event.claim.lineitem_detail.lineitem.purchase_order.order_date %]<br />
+            Expected receive date: [% event.claim.lineitem_detail.lineitem.expected_recv_time %]<br />
+            Initiated by: [% event.creator.family_name %], [% event.creator.first_given_name %] [% event.creator.second_given_name %]<br />
+            Barcode: [% event.claim.lineitem_detail.barcode %]; Fund:
+            [% event.claim.lineitem_detail.fund.code %]
+            ([% event.claim.lineitem_detail.fund.year %])
+        </li>
+        [% END %]
+    </ul>
+</div>
+$$
+);
+
+INSERT INTO action_trigger.environment (event_def, path) VALUES
+    (21, 'claim'),
+    (21, 'claim.type'),
+    (21, 'claim.lineitem_detail'),
+    (21, 'claim.lineitem_detail.fund'),
+    (21, 'claim.lineitem_detail.lineitem.attributes'),
+    (21, 'claim.lineitem_detail.lineitem.purchase_order'),
+    (21, 'creator'),
+    (21, 'type')
+;
+
 SELECT SETVAL('action_trigger.event_definition_id_seq'::TEXT, 100);
 
 -- Org Unit Settings for configuring org unit weights and org unit max-loops for hold targeting
diff --git a/Open-ILS/src/sql/Pg/upgrade/0238.data.claim_voucher.sql b/Open-ILS/src/sql/Pg/upgrade/0238.data.claim_voucher.sql
new file mode 100644
index 0000000000..578e996785
--- /dev/null
+++ b/Open-ILS/src/sql/Pg/upgrade/0238.data.claim_voucher.sql
@@ -0,0 +1,66 @@
+BEGIN;
+
+INSERT INTO config.upgrade_log (version) VALUES ('0238');  -- senator
+
+INSERT INTO action_trigger.hook (key, core_type, description, passive)
+    VALUES (
+        'format.acqcle.html',
+        'acqcle',
+        'Formats claim events into a voucher',
+        TRUE
+    );
+
+INSERT INTO action_trigger.event_definition (
+        id, active, owner, name, hook, group_field,
+        validator, reactor, granularity, template
+    ) VALUES (
+        21,
+        TRUE,
+        1,
+        'Claim Voucher',
+        'format.acqcle.html',
+        'claim',
+        'NOOP_True',
+        'ProcessTemplate',
+        'print-on-demand',
+$$
+[%- USE date -%]
+[%- SET claim = target.0.claim -%]
+<!-- This will need refined/prettified. -->
+<div class="acq-claim-voucher">
+    <h2>Claim: [% claim.id %] ([% claim.type.code %])</h2>
+    <h3>Against: [%- helpers.get_li_attr("title", "", claim.lineitem_detail.lineitem.attributes) -%]</h3>
+    <ul>
+        [% FOR event IN target %]
+        <li>
+            Event type: [% event.type.code %]
+            [% IF event.type.library_initiated %](Library initiated)[% END %]
+            <br />
+            Event date: [% event.event_date %]<br />
+            Order date: [% event.claim.lineitem_detail.lineitem.purchase_order.order_date %]<br />
+            Expected receive date: [% event.claim.lineitem_detail.lineitem.expected_recv_time %]<br />
+            Initiated by: [% event.creator.family_name %], [% event.creator.first_given_name %] [% event.creator.second_given_name %]<br />
+            Barcode: [% event.claim.lineitem_detail.barcode %]; Fund:
+            [% event.claim.lineitem_detail.fund.code %]
+            ([% event.claim.lineitem_detail.fund.year %])
+        </li>
+        [% END %]
+    </ul>
+</div>
+$$
+);
+
+
+INSERT INTO action_trigger.environment (event_def, path) VALUES
+    (21, 'claim'),
+    (21, 'claim.type'),
+    (21, 'claim.lineitem_detail'),
+    (21, 'claim.lineitem_detail.fund'),
+    (21, 'claim.lineitem_detail.lineitem.attributes'),
+    (21, 'claim.lineitem_detail.lineitem.purchase_order'),
+    (21, 'creator'),
+    (21, 'type')
+;
+
+COMMIT;
+
diff --git a/Open-ILS/web/css/skin/default/acq.css b/Open-ILS/web/css/skin/default/acq.css
index 92bf05c3d0..0a3836892a 100644
--- a/Open-ILS/web/css/skin/default/acq.css
+++ b/Open-ILS/web/css/skin/default/acq.css
@@ -122,10 +122,10 @@
 #oils-acq-fund-xfer-submit-row { text-align: center; }
 
 /* li search page */
-h1.oils-acq-li-search { font-size: 150%;font-weight: bold;margin-bottom: 12px; }
-h2.oils-acq-li-search { font-size: 138%;font-weight: bold;margin-bottom: 11px; }
-h3.oils-acq-li-search { font-size: 125%;font-weight: bold;margin-bottom: 10px; }
-h4.oils-acq-li-search { font-size: 112%;font-weight: bold;margin-bottom: 9px; }
+h1 { font-size: 150%;font-weight: bold;margin-bottom: 12px; }
+h2 { font-size: 138%;font-weight: bold;margin-bottom: 11px; }
+h3 { font-size: 125%;font-weight: bold;margin-bottom: 10px; }
+h4 { font-size: 112%;font-weight: bold;margin-bottom: 9px; }
 #oils-acq-li-search-form-holder {border-bottom: 2px #666 inset; margin: 6px 0;}
 .oils-acq-li-search-form-row { margin: 6px 0; }
 input.oils-acq-li-search { margin: 0 12px; }
@@ -229,3 +229,8 @@ option[disabled="disabled"] { font-style: italic; }
 .acq-unified-terms-match { width: 15%; }
 .acq-unified-terms-remove { width: 5%; text-align: right; }
 .acq-unified-remover { color: #c00; }
+
+#acq-eligible-li-table { margin: 10px 0; }
+#acq-eligible-li-table th { background-color: #ccc; border: 1px #333 inset; font-weight: bold; padding: 6px; }
+#acq-eligible-li-table td { padding: 2px 6px; border: 1px #333 inset; }
+#acq-eligible-li-table div[name="lid_link_holder"] { margin-left: 10px; }
diff --git a/Open-ILS/web/js/dojo/openils/acq/nls/acq.js b/Open-ILS/web/js/dojo/openils/acq/nls/acq.js
index 74f7d5216b..d8bebc6e68 100644
--- a/Open-ILS/web/js/dojo/openils/acq/nls/acq.js
+++ b/Open-ILS/web/js/dojo/openils/acq/nls/acq.js
@@ -68,5 +68,8 @@
             "<a style='padding-right: 10px;' href='${11}/acq/picklist/view/${14}'>SL: ${15}</a></div>",
     'INVOICE_CONFIRM_PRORATE' : "Prorate charges?\n\nAny subsequent changes to the invoice that would affect prorated amounts should be resolved manually.",
     'UNNAMED': "Unnamed",
-    'NO_FIND_INVOICE': "Could not find that invoice.\nNote that the Invoice # field is case-sensitive."
+    'NO_FIND_INVOICE': "Could not find that invoice.\nNote that the Invoice # field is case-sensitive.",
+    'NO_LI_TO_CLAIM': "You have not selected any lineitems to claim.",
+    'CLAIM_VOUCHERS': "Claim Vouchers",
+    'PRINT': "Print"
 }
diff --git a/Open-ILS/web/js/ui/default/acq/financial/claim_eligible.js b/Open-ILS/web/js/ui/default/acq/financial/claim_eligible.js
new file mode 100644
index 0000000000..c98936535b
--- /dev/null
+++ b/Open-ILS/web/js/ui/default/acq/financial/claim_eligible.js
@@ -0,0 +1,220 @@
+dojo.require("dijit.form.TextBox");
+dojo.require("openils.acq.Lineitem");
+dojo.require("openils.widget.OrgUnitFilteringSelect");
+dojo.require("openils.widget.ProgressDialog");
+dojo.require("openils.widget.AutoFieldWidget");
+
+var eligibleLiTable;
+
+function nodeByName(n, c) { return dojo.query("[name='" + n + "']", c)[0]; }
+
+function EligibleLiTable(filter) {
+    var self = this;
+
+    this.filter = filter;
+    this.liCache = {};
+    this.numClaimableLids = {};
+
+    this.claimNote = dijit.byId("acq-eligible-claim-note");
+    this.table = dojo.byId("acq-eligible-li-table");
+    this.tBody = dojo.query("tbody", this.table)[0];
+    this.tHead = dojo.query("thead", this.table)[0];
+    [this.rowTemplate, this.emptyTemplate] =
+        dojo.query("tr", this.tBody).map(
+            function(o) { return self.tBody.removeChild(o); }
+        );
+
+    nodeByName("selector_all", this.tHead).onclick = function() {
+        var value = this.checked;
+        dojo.query("[name='selector']", self.tBody).forEach(
+            function(o) { o.checked = value; }
+        );
+    };
+
+    new openils.widget.AutoFieldWidget({
+        "fmClass": "acqclt",
+        "selfReference": true,
+        "dijitArgs": {"required": true},
+        "parentNode": dojo.byId("acq-eligible-claim-type")
+    }).build(function(w) { self.claimType = w; });
+
+    new openils.User().buildPermOrgSelector(
+        "VIEW_PURCHASE_ORDER", orderingAgency, null,
+        function() {
+            orderingAgency.attr("value", self.filter.ordering_agency);
+            dojo.connect(
+                orderingAgency, "onChange",
+                function() {
+                    self.filter.ordering_agency = this.attr("value");
+                    self.load();
+                }
+            );
+            self.load();
+        }
+    );
+
+    this.showEmpty = function() {
+        dojo.place(dojo.clone(this.emptyTemplate), this.tBody, "only");
+        openils.Util.hide("acq-eligible-claim-controls");
+    };
+
+    this.load = function() {
+        progressDialog.show(true);
+
+        var count = 0;
+        this.reset();
+        fieldmapper.standardRequest(
+            ["open-ils.acq", "open-ils.acq.claim.eligible.lineitem_detail"], {
+                "params": [openils.User.authtoken, this.filter],
+                "async": true,
+                "onresponse": function(r) {
+                    if (r = openils.Util.readResponse(r)) {
+                        if (!count++)
+                            openils.Util.show("acq-eligible-claim-controls");
+                        self.addIfMissing(r.lineitem());
+                    } else {
+                        progressDialog.hide();
+                    }
+                },
+                "oncomplete": function() {
+                    if (count < 1) self.showEmpty();
+                    progressDialog.hide();
+                }
+            }
+        );
+    };
+
+    this.reset = function() {
+        this.liCache = {};
+        this.numClaimableLids = {};
+        dojo.empty(this.tBody);
+    };
+
+    this._updateLidLink = function(liId) {
+        this.numClaimableLids[liId] = (this.numClaimableLids[liId] || 0) + 1;
+        if (this.numClaimableLids[liId] == 2) {
+            nodeByName("lid_link", "eligible-li-" + liId).onclick =
+                function() {
+                    location.href = oilsBasePath + "/acq/po/view/" +
+                        self.liCache[liId].purchase_order().id() + "," +
+                        liId;
+                };
+            openils.Util.show(
+                nodeByName("lid_link_holder", "eligible-li-" + liId)
+            );
+        }
+    };
+
+    /* Despite being called with an argument that's a lineitem ID, this method
+     * is actually called once per lineitem _detail_. */
+    this.addIfMissing = function(liId) {
+        this._updateLidLink(liId);
+        if (this.liCache[liId]) return;
+
+        var row = dojo.clone(this.rowTemplate);
+
+        var checkbox = nodeByName("selector", row);
+        var desc = nodeByName("description", row);
+
+        openils.acq.Lineitem.fetchAndRender(
+            liId, null, function(li, contents) {
+                self.liCache[liId] = li;
+
+                desc.innerHTML = contents;
+                dojo.attr(row, "id", "eligible-li-" + liId);
+                dojo.attr(checkbox, "value", liId);
+                dojo.place(row, self.tBody, "last");
+            }
+        );
+    };
+
+    /* Despite being called with an argument that's a lineitem ID, this method
+     * is actually called once per lineitem _detail_. */
+    this.removeIfPresent = function(liId) {
+        if (this.liCache[liId]) {
+            delete this.liCache[liId];
+            delete this.numClaimableLids[liId];
+            this.tBody.removeChild(dojo.byId("eligible-li-" + liId));
+        }
+    };
+
+    this.getSelected = function() {
+        return dojo.query("[name='selector']", this.tBody).
+            filter(function(o) { return o.checked; }).
+            map(function(o) { return o.value; });
+    };
+
+    this.resetVoucher = function() { this.voucherWin = null; };
+
+    this.addToVoucher = function(contents) {
+        if (!this.voucherWin) {
+            this.voucherWin = window.open(
+                "", "", "resizable,width=800,height=600,scrollbars=1"
+            );
+            this.voucherWin.document.title = localeStrings.CLAIM_VOUCHERS;
+            this.voucherWin.document.body.innerHTML = (
+                "<button onclick='window.print();'>" +
+                localeStrings.PRINT +
+                "</button><hr /><div id='main'></div>"
+            );
+        }
+        dojo.byId("main", this.voucherWin.document).innerHTML += (
+            contents + "<hr />"
+        );
+    };
+
+    this.claim = function() {
+        var lineitems = this.getSelected();
+        if (!lineitems.length) {
+            alert(localeStrings.NO_LI_TO_CLAIM);
+            return;
+        }
+
+        progressDialog.show(true);
+        self.resetVoucher();
+
+        fieldmapper.standardRequest(
+            ["open-ils.acq", "open-ils.acq.claim.lineitem"], {
+                "params": [
+                    openils.User.authtoken, lineitems, null,
+                    this.claimType.attr("value"), this.claimNote.attr("value")
+                ],
+                "async": true,
+                "onresponse": function(r) {
+                    if (r = openils.Util.readResponse(r))
+                        self.addToVoucher(r.template_output().data());
+                    else
+                        progressDialog.hide();
+                },
+                "oncomplete": function() {
+                    lineitems.forEach(
+                        function(liId) { self.removeIfPresent(liId); }
+                    );
+                    if (!nodeByName("selector", self.tBody)) // emptiness test
+                        self.showEmpty();
+                    progressDialog.hide();
+                }
+            }
+        );
+    };
+}
+
+function init() {
+    var finished_filter = {};
+    if (filter && filter.indexOf(":") != -1) {
+        filter.split(",").forEach(
+            function(chunk) {
+                var [key, value] = chunk.split(":");
+                finished_filter[key] = value;
+            }
+        );
+    }
+    filter = finished_filter;
+
+    if (!filter.ordering_agency)
+        filter.ordering_agency = openils.User.user.ws_ou();
+
+    eligibleLiTable = new EligibleLiTable(filter);
+}
+
+openils.Util.addOnLoad(init);
diff --git a/Open-ILS/web/js/ui/default/acq/po/view_po.js b/Open-ILS/web/js/ui/default/acq/po/view_po.js
index cdfff5218e..dcbe8e9dac 100644
--- a/Open-ILS/web/js/ui/default/acq/po/view_po.js
+++ b/Open-ILS/web/js/ui/default/acq/po/view_po.js
@@ -444,20 +444,22 @@ function activatePo() {
             return false;
     }
 
+    var want_refresh = false;
     progressDialog.show(true);
-    try {
-        fieldmapper.standardRequest(
-            ['open-ils.acq', 'open-ils.acq.purchase_order.activate'],
-            {   async: true,
-                params: [openils.User.authtoken, PO.id()],
-                oncomplete : function() {
+    fieldmapper.standardRequest(
+        ["open-ils.acq", "open-ils.acq.purchase_order.activate"], {
+            "async": true,
+            "params": [openils.User.authtoken, PO.id()],
+            "onresponse": function(r) {
+                want_refresh = Boolean(openils.Util.readResponse(r));
+            },
+            "oncomplete": function() {
+                progressDialog.hide();
+                if (want_refresh)
                     location.href = location.href;
-                }
             }
-        );
-    } catch(E) {
-        progressDialog.hide();
-    }
+        }
+    );
 }
 
 function splitPo() {
diff --git a/Open-ILS/web/opac/locale/en-US/lang.dtd b/Open-ILS/web/opac/locale/en-US/lang.dtd
index 93ac6ce182..c03b5ef58b 100644
--- a/Open-ILS/web/opac/locale/en-US/lang.dtd
+++ b/Open-ILS/web/opac/locale/en-US/lang.dtd
@@ -825,6 +825,8 @@
 <!ENTITY staff.main.menu.acq.currency_type.accesskey "C">
 <!ENTITY staff.main.menu.acq.exchange_rate.label "Exchange Rates">
 <!ENTITY staff.main.menu.acq.exchange_rate.accesskey "X">
+<!ENTITY staff.main.menu.acq.claim_eligible.label "Claim-Eligible Items">
+<!ENTITY staff.main.menu.acq.claim_eligible.accesskey "M">
 
 <!ENTITY staff.main.menu.booking.label "Booking">
 <!ENTITY staff.main.menu.booking.accesskey "B">
diff --git a/Open-ILS/web/templates/default/acq/financial/claim_eligible.tt2 b/Open-ILS/web/templates/default/acq/financial/claim_eligible.tt2
new file mode 100644
index 0000000000..8232f7f3ff
--- /dev/null
+++ b/Open-ILS/web/templates/default/acq/financial/claim_eligible.tt2
@@ -0,0 +1,54 @@
+[% WRAPPER "default/base.tt2" %]
+[% ctx.page_title = "Items Eligible For Claiming" %]
+<script>var filter = "[% ctx.page_args.0 %]";</script>
+<script
+    src="[% ctx.media_prefix %]/js/ui/default/acq/financial/claim_eligible.js">
+</script>
+<div>
+    <h1>Items Eligible For Claiming</h1>
+    <div class="oils-acq-basic-roomy">
+        Show items ready to claim for:
+        <select
+            dojoType="openils.widget.OrgUnitFilteringSelect"
+            jsId="orderingAgency" searchAttr="shortname"
+            labelAttr="shortname"></select>
+        <span dojoType="openils.widget.ProgressDialog"
+            jsId="progressDialog"></span>
+    </div>
+    <table id="acq-eligible-li-table">
+        <thead>
+            <tr>
+                <th><input type="checkbox" name="selector_all" /></th>
+                <th>Items</th>
+            </tr>
+        </thead>
+        <tbody>
+            <tr><!-- item template -->
+                <td>
+                    <input type="checkbox" name="selector" />
+                </td>
+                <td>
+                    <div name="description"></div>
+                    <div name="lid_link_holder" class="hidden">
+                        [ <a href="javascript:void(0);"
+                            name="lid_link">Consider individual copies
+                            for claiming</a> ]
+                    </div>
+                </td>
+            </tr>
+            <tr><!-- empty template -->
+                <td colspan="2">
+                    <em>There were no items matching your search.</em>
+                </td>
+            </tr>
+        </tbody>
+    </table>
+    <div id="acq-eligible-claim-controls" class="hidden">
+        <label for="acq-eligible-claim-type">Claim type:</label>
+        <span id="acq-eligible-claim-type"></span>
+        <label for="acq-eligible-claim-note">Note:</label>
+        <input dojoType="dijit.form.TextBox" id="acq-eligible-claim-note" />
+        <button onclick="eligibleLiTable.claim();">Claim selected items</button>
+    </div>
+</div>
+[% END %]
diff --git a/Open-ILS/web/templates/default/acq/po/view.tt2 b/Open-ILS/web/templates/default/acq/po/view.tt2
index cc401378da..f253912537 100644
--- a/Open-ILS/web/templates/default/acq/po/view.tt2
+++ b/Open-ILS/web/templates/default/acq/po/view.tt2
@@ -77,7 +77,9 @@
             </table>
         </div>
     </div>
-    <script type="text/javascript">var poId = '[% ctx.page_args.0 %]';</script>
+    <script type="text/javascript">
+        var [poId, liFocus] = "[% ctx.page_args.0 %]".split(",");
+    </script>
     [% INCLUDE 'default/acq/common/li_table.tt2' %]
     [% INCLUDE "default/acq/common/notes.tt2" which = "Po" %]
 </div>
diff --git a/Open-ILS/xul/staff_client/chrome/content/main/menu.js b/Open-ILS/xul/staff_client/chrome/content/main/menu.js
index b7d9e79390..6cb2fd4d75 100644
--- a/Open-ILS/xul/staff_client/chrome/content/main/menu.js
+++ b/Open-ILS/xul/staff_client/chrome/content/main/menu.js
@@ -788,6 +788,10 @@ main.menu.prototype = {
                 ['oncommand'],
                 function() { open_eg_web_page('conify/global/acq/distribution_formula', 'menu.cmd_acq_view_distrib_formula.tab'); }
             ],
+            'cmd_acq_claim_eligible' : [
+                ['oncommand'],
+                function() { open_eg_web_page('acq/financial/claim_eligible', 'menu.cmd_acq_claim_eligible.tab'); }
+            ],
             'cmd_booking_reservation' : [
                 ['oncommand'],
                 function() {
diff --git a/Open-ILS/xul/staff_client/chrome/content/main/menu_frame_menus.xul b/Open-ILS/xul/staff_client/chrome/content/main/menu_frame_menus.xul
index 62dfe83db8..16513573f2 100644
--- a/Open-ILS/xul/staff_client/chrome/content/main/menu_frame_menus.xul
+++ b/Open-ILS/xul/staff_client/chrome/content/main/menu_frame_menus.xul
@@ -92,6 +92,7 @@
     <command id="cmd_acq_view_currency_type" />
     <command id="cmd_acq_view_exchange_rate" />
     <command id="cmd_acq_view_distrib_formula" />
+    <command id="cmd_acq_claim_eligible" />
 
     <command id="cmd_booking_reservation" />
     <command id="cmd_booking_pull_list" />
@@ -276,6 +277,7 @@
         <menuitem label="&staff.main.menu.acq.currency_type.label;" accesskey="&staff.main.menu.acq.currency_type.accesskey;" command="cmd_acq_view_currency_type" />
         <menuitem label="&staff.main.menu.acq.exchange_rate.label;" accesskey="&staff.main.menu.acq.exchange_rate.accesskey;" command="cmd_acq_view_exchange_rate" />
         <menuitem label="&staff.main.menu.acq.distrib_formula.label;" accesskey="&staff.main.menu.acq.distrib_formula.accesskey;" command="cmd_acq_view_distrib_formula" />
+        <menuitem label="&staff.main.menu.acq.claim_eligible.label;" accesskey="&staff.main.menu.acq.claim_eligible.accesskey;" command="cmd_acq_claim_eligible" />
     </menupopup>
 </menu>
 
diff --git a/Open-ILS/xul/staff_client/chrome/locale/en-US/offline.properties b/Open-ILS/xul/staff_client/chrome/locale/en-US/offline.properties
index 4869acaeee..51b03d0b50 100644
--- a/Open-ILS/xul/staff_client/chrome/locale/en-US/offline.properties
+++ b/Open-ILS/xul/staff_client/chrome/locale/en-US/offline.properties
@@ -237,6 +237,7 @@ menu.cmd_acq_view_provider.tab=Providers
 menu.cmd_acq_view_currency_type.tab=Currency Types
 menu.cmd_acq_view_exchange_rate.tab=Exchange Rates
 menu.cmd_acq_view_distrib_formula.tab=Distribution Formulas
+menu.cmd_acq_claim_eligible.tab=Claim-Eligible Items
 menu.cmd_booking_resource.tab=Resources
 menu.cmd_booking_reservation.tab=Reservations
 menu.cmd_booking_reservation_pickup.tab=Reservation Pickup
-- 
2.11.0