Added a lineitem summary view to describe details about related
authorerickson <erickson@dcc99617-32d9-48b4-a31d-7c20da2025e4>
Mon, 19 Apr 2010 00:04:26 +0000 (00:04 +0000)
committererickson <erickson@dcc99617-32d9-48b4-a31d-7c20da2025e4>
Mon, 19 Apr 2010 00:04:26 +0000 (00:04 +0000)
lineitem_detail and order data

Added a generic lineitem view function to generate a canned HTML
blob to represent a lineitem using the new summary view

Reduced some fleshing in ML calls in anticipation of larger invoices

Use new summary lineitem fetch/render code in invoice page

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

Open-ILS/examples/fm_IDL.xml
Open-ILS/src/perlmods/OpenILS/Application/Acq/Financials.pm
Open-ILS/src/perlmods/OpenILS/Application/Acq/Invoice.pm
Open-ILS/src/perlmods/OpenILS/Application/Acq/Lineitem.pm
Open-ILS/web/css/skin/default/acq.css
Open-ILS/web/js/dojo/openils/acq/Lineitem.js
Open-ILS/web/js/dojo/openils/acq/nls/acq.js
Open-ILS/web/js/ui/default/acq/invoice/view.js

index f847a20..dc39053 100644 (file)
@@ -5432,6 +5432,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
                        <field reporter:label="Line Item Notes" name="lineitem_notes" oils_persist:virtual="true" reporter:datatype="link" />
                        <field reporter:label="Distribution Formulas" name="distribution_formulas" oils_persist:virtual="true" reporter:datatype="link" />
                        <field reporter:label="Envoice Enties" name="invoice_entries" oils_persist:virtual="true" reporter:datatype="link" />
+                       <field reporter:label="Order Summary" name="order_summary" oils_persist:virtual="true" reporter:datatype="link" />
                </fields>
                <links>
                        <link field="selector" reltype="has_a" key="id" map="" class="au"/>
@@ -5448,6 +5449,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
                        <link field="distribution_formulas" reltype="has_many" key="lineitem" map="" class="acqdfa"/>
                        <link field="claim_policy" reltype="has_a" key="id" map="" class="acqclp"/>
                        <link field="invoice_entries" reltype="has_many" key="lineitem" map="" class="acqie"/>
+                       <link field="order_summary" reltype="might_have" key="lineitem" map="" class="acqlisum"/>
                </links>
                <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
             <retrieve permission="CREATE_PURCHASE_ORDER VIEW_PURCHASE_ORDER CREATE_PICKLIST VIEW_PICKLIST">
@@ -7180,6 +7182,74 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
                </links>
        </class>
 
+       <class id="acqlisum" controller="open-ils.cstore" oils_obj:fieldmapper="acq::lineitem_summary" oils_persist:readonly="true" reporter:label="Lineitem Summary">
+               <oils_persist:source_definition>
+
+            SELECT 
+                li.id AS lineitem, 
+                (
+                    SELECT COUNT(lid.id) 
+                    FROM acq.lineitem_detail lid
+                    WHERE lineitem = li.id
+                ) AS item_count,
+                (
+                    SELECT COUNT(lid.id) 
+                    FROM acq.lineitem_detail lid
+                    WHERE recv_time IS NOT NULL AND lineitem = li.id
+                ) AS recv_count,
+                (
+                    SELECT COUNT(lid.id) 
+                    FROM acq.lineitem_detail lid
+                    WHERE cancel_reason IS NOT NULL AND lineitem = li.id
+                ) AS cancel_count,
+                (
+                    SELECT COUNT(lid.id) 
+                    FROM acq.lineitem_detail lid
+                        JOIN acq.fund_debit debit ON (lid.fund_debit = debit.id)
+                    WHERE NOT debit.encumbrance AND lineitem = li.id
+                ) AS invoice_count,
+                (
+                    SELECT COUNT(DISTINCT(lid.id)) 
+                    FROM acq.lineitem_detail lid
+                        JOIN acq.claim claim ON (claim.lineitem_detail = lid.id)
+                    WHERE lineitem = li.id
+                ) AS claim_count,
+                (
+                    SELECT (COUNT(lid.id) * li.estimated_unit_price)::NUMERIC(8,2)
+                    FROM acq.lineitem_detail lid
+                    WHERE lid.cancel_reason IS NULL AND lineitem = li.id
+                ) AS estimated_amount,
+                (
+                    SELECT SUM(debit.amount)::NUMERIC(8,2)
+                    FROM acq.lineitem_detail lid
+                        JOIN acq.fund_debit debit ON (lid.fund_debit = debit.id)
+                    WHERE debit.encumbrance AND lineitem = li.id
+                ) AS encumbrance_amount,
+                (
+                    SELECT SUM(debit.amount)::NUMERIC(8,2)
+                    FROM acq.lineitem_detail lid
+                        JOIN acq.fund_debit debit ON (lid.fund_debit = debit.id)
+                    WHERE NOT debit.encumbrance AND lineitem = li.id
+                ) AS paid_amount
+
+                FROM acq.lineitem AS li
+        </oils_persist:source_definition>
+               <fields oils_persist:primary="lineitem" oils_persist:sequence="acq.lineitem_id_seq">
+                       <field reporter:label="Lineitem" name="lineitem" reporter:datatype="link"/>
+                       <field reporter:label="Item Count" name="item_count" reporter:datatype="int"/>
+                       <field reporter:label="Receive Count" name="recv_count" reporter:datatype="int"/>
+                       <field reporter:label="Cancel Count" name="cancel_count" reporter:datatype="int"/>
+                       <field reporter:label="Invoice Count" name="invoice_count" reporter:datatype="int"/>
+                       <field reporter:label="Claim Count" name="claim_count" reporter:datatype="int"/>
+                       <field reporter:label="Estimated Amount" name="estimated_amount" reporter:datatype="money"/>
+                       <field reporter:label="Encumbrance Amount" name="encumbrance_amount" reporter:datatype="money"/>
+                       <field reporter:label="Paid Amount" name="paid_amount" reporter:datatype="money"/>
+               </fields>
+               <links>
+                       <link field="lineitem" reltype="has_a" key="id" map="" class="jub"/>
+               </links>
+    </class>
+
 
        <class id="iatc" controller="open-ils.reporter-store" oils_obj:fieldmapper="action::intersystem_transit_copy" oils_persist:readonly="true" reporter:core="true" reporter:label="Inter-system Copy Transit">
                <oils_persist:source_definition>
index 22ce502..bfff04b 100644 (file)
@@ -942,6 +942,9 @@ sub retrieve_purchase_order_impl {
         $po->lineitems($items);
         $po->lineitem_count(scalar(@$items));
 
+    } elsif( $$options{flesh_lineitem_ids} ) {
+        $po->lineitems($e->search_acq_lineitem({purchase_order => $po_id}, {idlist => 1}));
+
     } elsif( $$options{flesh_lineitem_count} ) {
 
         my $items = $e->search_acq_lineitem({purchase_order => $po_id}, {idlist=>1});
index a653325..f0f08e1 100644 (file)
@@ -257,19 +257,12 @@ sub fetch_invoice_impl {
             "flesh" => 6,
             "flesh_fields" => {
                 "acqinv" => ["entries", "items"],
-                "acqie" => ["lineitem", "purchase_order"],
                 "acqii" => ["fund_debit"],
-                "jub" => ["attributes", "lineitem_details"],
-                "acqlid" => ["fund_debit"]
             }
         }
     ];
 
-    my $invoice = $e->retrieve_acq_invoice($args) or return $e->die_event;
-    return $invoice if $options->{no_flesh_misc} or $options->{keep_li_marc};
-
-    $_->lineitem->clear_marc for @{$invoice->entries};
-    return $invoice;
+    return $e->retrieve_acq_invoice($args);
 }
 
 __PACKAGE__->register_method(
index ce22e23..28bf41e 100644 (file)
@@ -92,70 +92,62 @@ sub retrieve_lineitem_impl {
     my ($e, $li_id, $options) = @_;
     $options ||= {};
 
-    # XXX finer grained perms...
-
-    my $flesh = {};
-    if($$options{flesh_attrs} or $$options{flesh_notes}) {
-        $flesh = {flesh => 2, flesh_fields => {jub => []}};
-        if($$options{flesh_notes}) {
-            push(@{$flesh->{flesh_fields}->{jub}}, 'lineitem_notes');
-            $flesh->{flesh_fields}->{acqlin} = ['alert_text'];
+    my $flesh = {
+        flesh => 3,
+        flesh_fields => {
+            jub => ['purchase_order', 'picklist'], # needed for permission check
+            acqlid => [],
+            acqlin => []
         }
-        if($$options{"flesh_cancel_reason"}) {
-            push @{$flesh->{flesh_fields}->{jub}}, "cancel_reason";
-        }
-        push(@{$flesh->{flesh_fields}->{jub}}, 'attributes') if $$options{flesh_attrs};
-    }
+    };
 
-    my $li = $e->retrieve_acq_lineitem([$li_id, $flesh]) or return $e->die_event;
+    my $fields = $flesh->{flesh_fields};
+
+    push(@{$fields->{jub}}, 'attributes') if $$options{flesh_attrs};
+    push(@{$fields->{jub}}, 'lineitem_notes') if $$options{flesh_notes};
+    push(@{$fields->{acqlin}}, 'alert_text') if $$options{flesh_notes};
+    push(@{$fields->{jub}}, 'order_summary') if $$options{flesh_order_summary};
+    push(@{$fields->{acqlin}}, 'cancel_reason') if $$options{flesh_cancel_reason};
 
     if($$options{flesh_li_details}) {
-        my $ops = {
-            flesh => 1,
-            flesh_fields => {acqlid => ["cancel_reason"]} #XXX cancel_reason? always? really?
-        };
-        push(@{$ops->{flesh_fields}->{acqlid}}, 'fund') if $$options{flesh_fund};
-        push(@{$ops->{flesh_fields}->{acqlid}}, 'fund_debit') if $$options{flesh_fund_debit};
-        if (my $details = $e->search_acq_lineitem_detail([{lineitem => $li_id}, $ops])) {
-            $li->lineitem_details($details);
-            $li->item_count(scalar(@$details));
-        } else {
-            $li->lineitem_count(0);
-        }
-    } else {
-        my $details = $e->search_acq_lineitem_detail({lineitem => $li_id}, {idlist=>1});
-        $li->item_count(scalar(@$details));
+        push(@{$fields->{jub}}, 'lineitem_details');
+        push(@{$fields->{acqlid}}, 'fund') if $$options{flesh_fund};
+        push(@{$fields->{acqlid}}, 'fund_debit') if $$options{flesh_fund_debit};
+        push(@{$fields->{acqlid}}, 'cancel_reason') if $$options{flesh_cancel_reason};
     }
 
-    if($li->purchase_order) {
-        my $purchase_order =
-            $e->retrieve_acq_purchase_order($li->purchase_order)
-                or return $e->event;
-
-        if($purchase_order->owner != $e->requestor->id) {
-            return $e->event unless
-                $e->allowed('VIEW_PURCHASE_ORDER', undef, $purchase_order);
-        }
-
-        $li->purchase_order($purchase_order) if $$options{flesh_po};
+    if($$options{clear_marc}) { # avoid fetching marc blob
+        my @fields = grep { $_ ne 'marc' } Fieldmapper::acq::lineitem->new->real_fields;
+        $flesh->{select} = {jub => [@fields]};
+    }
 
-    } elsif($li->picklist) {
-        my $picklist = $e->retrieve_acq_picklist($li->picklist)
-            or return $e->event;
-    
-        if($picklist->owner != $e->requestor->id) {
-            return $e->event unless 
-                $e->allowed('VIEW_PICKLIST', undef, $picklist);
-        }
+    my $li = $e->retrieve_acq_lineitem([$li_id, $flesh]) or return $e->event;
 
-        $li->picklist($picklist) if $$options{flesh_pl};
+    # collect the # of lids
+    if($$options{flesh_li_details}) {
+        $li->item_count(scalar(@{$li->lineitem_details}));
+    } else {
+        my $details = $e->search_acq_lineitem_detail({lineitem => $li_id}, {idlist=>1});
+        $li->item_count(scalar(@$details));
     }
 
-    $li->clear_marc if $$options{clear_marc};
+    return $e->event unless (
+        $li->purchase_order and 
+            $e->allowed(['VIEW_PURCHASE_ORDER', 'CREATE_PURCHASE_ORDER'], 
+                $li->purchase_order->ordering_agency, $li->purchase_order)
+    ) or (
+        $li->picklist and !$li->purchase_order and # user doesn't have view_po perms
+            $e->allowed(['VIEW_PICKLIST', 'CREATE_PICKLIST'], 
+                $li->picklist->org_unit, $li->picklist)
+    );
 
+    $li->clear_purchase_order unless $$options{flesh_po};
+    $li->clear_picklist unless $$options{flesh_pl};
     return $li;
 }
 
+
+
 __PACKAGE__->register_method(
        method => 'delete_lineitem',
        api_name        => 'open-ils.acq.lineitem.delete',
index 62619d3..92bf05c 100644 (file)
@@ -210,6 +210,9 @@ span[name="notes_alert_flag"] {color: #c00;font-weight: bold;font-size: 110%;mar
 .acq-invoice-paid-col {background : #E0E0E0; text-align: center;}
 .acq-invoice-center-col { text-align: center; }
 
+.acq-lineitem-summary { font-weight: bold; }
+.acq-lineitem-summary-extra { padding-left: 10px; }
+
 #acq-unified-heading { margin-bottom: 10px; }
 #acq-unified-heading-actual { float: left; width: 50%; font-size: 120%; font-weight: bold; }
 #acq-unified-heading-controls { float: right; width: 50%; text-align: right; }
index d7d3958..6288d8b 100644 (file)
 if(!dojo._hasResource['openils.acq.Lineitem']) {
 dojo._hasResource['openils.acq.Lineitem'] = true;
 dojo.provide('openils.acq.Lineitem');
-
 dojo.require('dojo.data.ItemFileWriteStore');
-dojo.require('dojox.grid.Grid');
-dojo.require('dojox.grid.compat._data.model');
 dojo.require('fieldmapper.dojoData');
 dojo.require('openils.User');
 dojo.require('openils.Event');
 dojo.require('openils.Util');
 
+dojo.requireLocalization('openils.acq', 'acq');
+var localeStrings = dojo.i18n.getLocalization('openils.acq', 'acq');
+
+
 /** Declare the Lineitem class with dojo */
 dojo.declare('openils.acq.Lineitem', null, {
     /* add instance methods here if necessary */
@@ -103,6 +104,71 @@ dojo.declare('openils.acq.Lineitem', null, {
     },
 });
 
+openils.acq.Lineitem.identDisplayFields = ['isbn', 'upc', 'issn'];
+openils.acq.Lineitem.fetchAndRender = function(liId, args, callback) {
+
+    // below are the args needed for rendering the basic summary template
+    args = dojo.mixin(args || {}, {
+        clear_marc : true, 
+        flesh_attrs : true,
+        flesh_po : true,
+        flesh_pl : true,
+        flesh_order_summary : true
+    });
+
+    fieldmapper.standardRequest(
+        ['open-ils.acq', 'open-ils.acq.lineitem.retrieve'],
+        {
+            params : [ openils.User.authtoken, liId, args ],
+
+            oncomplete : function(r) {
+                var lineitem = openils.Util.readResponse(r);
+                if(!lineitem) return;
+
+                var wrapper = new openils.acq.Lineitem({lineitem : lineitem});
+                var type = 'lineitem_marc_attr_definition';
+
+                var idents = [];
+                dojo.forEach(
+                    openils.acq.Lineitem.identDisplayFields,
+                    function(ident) { 
+                        if(attr = wrapper.findAttr(ident, type))
+                            idents.push(attr)
+                    }
+                );
+
+                var po = lineitem.purchase_order();
+                var li = lineitem.picklist();
+
+                var displayString = dojo.string.substitute(
+                    localeStrings.LINEITEM_SUMMARY, [
+                        wrapper.findAttr('title', type) || '',
+                        wrapper.findAttr('author', type) || '',
+                        idents.join(',') || '',
+                        lineitem.order_summary().item_count() || '0',
+                        lineitem.order_summary().recv_count() || '0',
+                        Number(lineitem.estimated_unit_price()).toFixed(2) | '',
+                        lineitem.order_summary().estimated_amount() || '0.00',
+                        lineitem.order_summary().invoice_count() || '0',
+                        lineitem.order_summary().claim_count() || '0',
+                        lineitem.order_summary().cancel_count() || '0',
+                        lineitem.id(),
+                        oilsBasePath,
+                        (po) ? po.id() : '',
+                        (po) ? po.name() : '',
+                        (li) ? li.id() : '',
+                        (li) ? li.name() : '',
+                        lineitem.order_summary().encumbrance_amount() || '0.00',
+                        lineitem.order_summary().paid_amount() || '0.00',
+                    ]
+                );
+
+                callback(lineitem, displayString);
+            }
+        }
+    );
+}
+
 openils.acq.Lineitem.attrDefs = null;
 
 openils.acq.Lineitem.fetchAttrDefs = function(onload) {
index dca18f6..74f7d52 100644 (file)
     'INVOICE_ITEM_DETAILS' : "${0} <br/> ${1} <br/> ${2}. <br/> Estimated Price: $${3}. <br/> Lineitem ID: ${4} <br/> PO: ${5} <br/> Order Date: ${6}",
     'INVOICE_CONFIRM_ITEM_DELETE' : "Remove this $${0} '${1}' charge from the invoice?",
     'INVOICE_CONFIRM_ENTRY_DETACH' : "Remove $${0} charge for item '${1}, ${2} [${3}] from the invoice?",
-    'INVOICE_TITLE_DETAILS' : "<div class='acq-inoice-item-info'>${0}, by ${1} (${2})</div><div class='acq-inoice-item-extra-info'><a style='padding-right: 10px;' href='${9}/acq/po/view/${10}'>PO: ${11}</a>${3} Ordered, ${4} Received, ${7} Invoiced</div><div class='acq-inoice-item-extra-info'> Estimated Cost Per Item $${5} / Total Estimated Cost $${6}</div>",
+    'LINEITEM_SUMMARY' : "<div class='acq-lineitem-summary'>${0}, by ${1} (${2})</div>" +
+        "<div class='acq-lineitem-summary-extra'>" +
+            "${3} Ordered, ${4} Received, ${7} Invoiced, ${8} Claimed, ${9} Cancelled</div>" +
+        "<div class='acq-lineitem-summary-extra'>Estimated $${6}, Encumbered $${16}, Paid $${17}</div>" +
+        "<div class='acq-lineitem-summary-extra'>" +
+            "<a style='padding-right: 10px;' href='${11}/acq/po/view/${12}'>PO: ${13}</a>" +
+            "<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."
index 69db886..b80c207 100644 (file)
@@ -10,6 +10,7 @@ dojo.require('openils.PermaCrud');
 dojo.require('openils.widget.EditPane');
 dojo.require('openils.widget.AutoFieldWidget');
 dojo.require('openils.widget.ProgressDialog');
+dojo.require('openils.acq.Lineitem');
 
 dojo.requireLocalization('openils.acq', 'acq');
 var localeStrings = dojo.i18n.getLocalization('openils.acq', 'acq');
@@ -117,46 +118,24 @@ function renderInvoice() {
 
 function doAttachLi() {
 
-    fieldmapper.standardRequest(
-        ["open-ils.acq", "open-ils.acq.lineitem.retrieve"], {
-            async: true,
-            params: [openils.User.authtoken, attachLi, {
-                clear_marc : true,
-                flesh_attrs : true,
-                flesh_po : true,
-                flesh_li_details : true,
-                flesh_fund_debit : true
-            }],
-            oncomplete: function(r) { 
-                lineitem = openils.Util.readResponse(r);
-
-                if(cgi.param('create')) {
-                    // render the invoice using some seed data from the Lineitem
-                    var invoiceArgs = {provider : lineitem.provider(), shipper : lineitem.provider()}; 
-                    invoicePane = drawInvoicePane(dojo.byId('acq-view-invoice-div'), null, invoiceArgs);
-                }
-
-                var entry = new fieldmapper.acqie();
-                entry.id(virtualId--);
-                entry.isnew(true);
-                entry.lineitem(lineitem);
-                entry.purchase_order(lineitem.purchase_order());
-                addInvoiceEntry(entry);
-            }
-        }
-    );
+    //var invoiceArgs = {provider : lineitem.provider(), shipper : lineitem.provider()}; 
+    if(cgi.param('create')) {
+        var invoiceArgs = {};
+        invoicePane = drawInvoicePane(dojo.byId('acq-view-invoice-div'), null, invoiceArgs);
+    }
+    var entry = new fieldmapper.acqie();
+    entry.id(virtualId--);
+    entry.isnew(true);
+    entry.lineitem(attachLi);
+    addInvoiceEntry(entry);
 }
 
 function doAttachPo() {
+
     fieldmapper.standardRequest(
         ['open-ils.acq', 'open-ils.acq.purchase_order.retrieve'],
         {   async: true,
-            params: [openils.User.authtoken, attachPo, {
-                flesh_lineitems : true,
-                clear_marc : true,
-                flesh_lineitem_details : true,
-                flesh_fund_debit : true
-            }],
+            params: [openils.User.authtoken, attachPo, {flesh_lineitem_ids : true}],
             oncomplete: function(r) {
                 var po = openils.Util.readResponse(r);
 
@@ -173,7 +152,6 @@ function doAttachPo() {
                         entry.isnew(true);
                         entry.lineitem(lineitem);
                         entry.purchase_order(po);
-                        lineitem.purchase_order(po);
                         addInvoiceEntry(entry);
                     }
                 );
@@ -376,50 +354,21 @@ function addInvoiceEntry(entry) {
         entryTemplate = entryTbody.removeChild(dojo.byId('acq-invoice-entry-template'));
     }
 
-    if(dojo.query('[lineitem=' + entry.lineitem().id() +']', entryTbody)[0])
+    if(dojo.query('[lineitem=' + entry.lineitem() +']', entryTbody)[0])
         // Is it ever valid to have multiple entries for 1 lineitem in a single invoice?
         return;
 
     var row = entryTemplate.cloneNode(true);
-    row.setAttribute('lineitem', entry.lineitem().id());
-    var lineitem = entry.lineitem();
-
-    var idents = [];
-    if(liMarcAttr(lineitem, 'isbn')) idents.push(liMarcAttr(lineitem, 'isbn'));
-    if(liMarcAttr(lineitem, 'upc')) idents.push(liMarcAttr(lineitem, 'upc'));
-    if(liMarcAttr(lineitem, 'issn')) idents.push(liMarcAttr(lineitem, 'issn'));
-
-    var lids = lineitem.lineitem_details();
-    var numOrdered = lids.length;
-    var numReceived = lids.filter(function(lid) { return (lid.recv_time() != null) }).length;
-    var numInvoiced = lids.filter(function(lid) { return !openils.Util.isTrue(lid.fund_debit().encumbrance()) }).length;
-
-    var poName = '';
-    var poId = '';
-    var po = entry.purchase_order();
-    if(po) {
-        poName = po.name();
-        poId = po.id();
-    }
-
-    nodeByName('title_details', row).innerHTML = 
-        dojo.string.substitute(
-            localeStrings.INVOICE_TITLE_DETAILS, [
-                liMarcAttr(lineitem, 'title'),
-                liMarcAttr(lineitem, 'author'),
-                idents.join(','),
-                numOrdered,
-                numReceived,
-                Number(lineitem.estimated_unit_price()).toFixed(2),
-                (Number(lineitem.estimated_unit_price()) * numOrdered).toFixed(2),
-                numInvoiced,
-                lineitem.id(),
-                oilsBasePath,
-                poId,
-                poName
-            ]
-        );
-
+    row.setAttribute('lineitem', entry.lineitem());
+
+    openils.acq.Lineitem.fetchAndRender(
+        entry.lineitem(), {}, 
+        function(li, html) { 
+            entry.lineitem(li);
+            entry.purchase_order(li.purchase_order());
+            nodeByName('title_details', row).innerHTML = html;
+        }
+    );
 
     dojo.forEach(
         ['inv_item_count', 'phys_item_count', 'cost_billed', 'amount_paid'],
@@ -579,11 +528,9 @@ function prorateInvoice(invoice) {
             }
         }
     );
-
 }
 
 
-
 openils.Util.addOnLoad(init);