my $conj = $is_and ? "-and" : "-or";
my $outer_clause = {};
- foreach my $class (qw/acqpo acqpl acqinv jub/) {
+ foreach my $class (qw/acqpo acqpl acqinv jub acqlid acqlisum acqlisumi/) {
next if not exists $terms->{$class};
$outer_clause->{$conj} = [] unless $outer_clause->{$conj};
} elsif ($between and could_be_range($v)) {
$term_clause = {$k => {"between" => $v}};
} elsif (check_1d_max($v)) {
- $v = castdate($v, $gte, $lte) if $castdate;
+ if ($castdate) {
+ $v = castdate($v, $gte, $lte) if $castdate;
+ } elsif ($gte or $lte) {
+ my $op = $gte ? '>=' : '<=';
+ $v = {$op => $v};
+ }
$term_clause = {$k => $v};
} else {
next;
} else {
$graft_map{$class} = $query->{from}{$core}{$class} ||= {};
$graft_map{$class}{type} = $join_type;
+
+ # without this, the SQL attempts to join on
+ # jub.order_summary, which is a virtual field.
+ $graft_map{$class}{field} = 'lineitem'
+ if $class eq 'acqlisum' or $class eq 'acqlisumi';
}
}
$hint => [{"column" => "id", "transform" => "distinct"}]
};
+ my $attr_from_filter;
if ($options->{"order_by"}) {
# What's the point of this block? When using ORDER BY in conjuction
# with SELECT DISTINCT, the fields present in ORDER BY have to also
q/order_by clause must be of the long form, like:
"order_by": [{"class": "foo", "field": "bar", "direction": "asc"}]/
);
+
} else {
+
+ # we can't combine distinct(id) with another select column,
+ # since the non-distinct column may arbitrarily (via hash keys)
+ # sort to the front of the final SQL, which PG will complain about.
+ $select_clause = { $hint => ["id"] };
$select_clause->{$class} ||= [];
- push @{$select_clause->{$class}}, $field;
+ push @{$select_clause->{$class}},
+ {column => $field, transform => 'first', aggregate => 1};
+
+ # when sorting by LI attr values, we have to limit
+ # to a specific type of attr value to sort on.
+ if ($class eq 'acqlia') {
+ $attr_from_filter = {
+ "fkey" => "id",
+ "filter" => {
+ "attr_type" => "lineitem_marc_attr_definition",
+ "attr_name" => $options->{"order_by_attr"} || "title"
+ },
+ "type" => "left",
+ "field" =>"lineitem"
+ };
+ }
}
}
return new OpenILS::Event("BAD_PARAMS", "desc" => "No usable terms");
}
+
+ # if ordering by acqlia, insert the from clause
+ # filter to limit to one type of attr.
+ if ($attr_from_filter) {
+ $query->{from}->{jub} = {} unless $query->{from}->{jub};
+ $query->{from}->{jub}->{acqlia} = $attr_from_filter;
+ }
+
my $results = $e->json_query($query) or return $e->die_event;
my @id_list = map { $_->{"id"} } (grep { $_->{"id"} } @$results);
[% WRAPPER 'base.tt2' %]
[% ctx.page_title = 'Invoicing' %]
-<script type="text/javascript">var invoiceId = '[% ctx.page_args.0 %]';</script>
<div dojoType="dijit.layout.ContentPane" style="height:100%">
<div dojoType="dijit.layout.ContentPane" layoutAlign="client" class='oils-header-panel'>
<div> Invoice </div>
- <div id="acq-view-invoice-receive" class="hidden"><button id="acq-view-invoice-receive-link">Receive Items</button></div>
+ <div id="acq-view-invoice-receive" class="hidden">
+ <button id="acq-view-invoice-receive-link">Receive Items</button>
+ </div>
</div>
<div dojoType="dijit.layout.ContentPane" layoutAlign="client">
- <div id='acq-view-invoice-div'></div>
- </div>
+ <style>
+ #acq-invoice-num-summary-table td {
+ padding-right: 8px;
+ padding-top: 5px;
+ padding-bottom: 5px;
+ border-bottom: 2px solid #888;
+ }
+ #acq-invoice-num-summary-table td[id] {
+ font-weight:bold;
+ }
+ </style>
+ <table id='acq-invoice-num-summary-table'>
+ <tr><td>[% l("Lineitems: " ) %]</td><td id='acq-invoice-summary-count'>0</td></tr>
+ <tr>
+ <td>[% l("Expected Cost: " ) %]</td>
+ <td id='acq-invoice-summary-cost'>0.00</td>
+ </tr>
+ </table>
+ <br/>
+ <div>
<div dojoType="dijit.layout.ContentPane" layoutAlign="client">
- <table class='oils-acq-invoice-table'>
- <thead/>
- <tbody id='acq-invoice-entry-header' class='hidden'>
- <tr>
- <td colspan='0'>
- <h3>Bibliographic Items</h3>
- </td>
- </tr>
- </tbody>
- <!-- acq.invoice_entry -->
- <thead id='acq-invoice-entry-thead' class='hidden'>
- <tr>
- <th colspan='2'>Title Details</th>
- <th class='acq-invoice-center-col'># Invoiced / # Paid</th>
- <th class='acq-invoice-center-col'>Billed</th>
- <th class='acq-invoice-paid-col'>Paid</th>
- <th class='acq-invoice-center-col hide-complete'>Detach</th>
- </tr>
- </thead>
- <tbody id='acq-invoice-entry-tbody' class='hidden'>
- <tr id='acq-invoice-entry-template' class='acq-invoice-row'>
- <td colspan='2'>
- <div name='title_details'></div>
- <div name='note'></div>
- </td>
- <td class='acq-invoice-center-col'>
- <span name='inv_item_count'></span> / <span name='phys_item_count'></span>
- </td>
- <td class='acq-invoice-billed-col'><div name='cost_billed'></div></td>
- <td class='acq-invoice-paid-col'><div name='amount_paid'></div></td>
- <td class='acq-invoice-center-col hide-complete'><a href='javascript:void(0);' name='detach'>Detach</a></td>
- </tr>
- </tbody>
- <tbody>
- <tr>
- <td style='margin-top:15px;' colspan='0'>
- <h3>Direct Charges, Taxes, Fees, etc.</h3>
- </td>
- </tr>
- </tbody>
- <!-- acq.invoice_item -->
- <thead>
- <tr>
- <th>Charge Type</th>
- <th class='acq-invoice-center-col'>Fund</th>
- <th>Title/Description</th>
- <th class='acq-invoice-center-col'>Billed</th>
- <th class='acq-invoice-paid-col'>Paid</th>
- <th class='acq-invoice-center-col hide-complete'>Delete</th>
- </tr>
- </thead>
- <tbody id='acq-invoice-item-tbody'>
- <tr id='acq-invoice-item-template' class='acq-invoice-row acq-invoice-item-row'>
- <td><div name='inv_item_type'></div></td>
- <td class='acq-invoice-center-col'><div name='fund'></div></td>
- <td><div name='title'></div></td>
- <td class='acq-invoice-center-col acq-invoice-billed-col'><div name='cost_billed'></div></td>
- <td class='acq-invoice-paid-col'><div name='amount_paid'></div></td>
- <td class='acq-invoice-center-col hide-complete'><a href='javascript:void(0);' name='delete'>Delete</a></td>
- </tr>
- </tbody>
- <tbody class='hide-complete'>
- <tr>
- <td colspan='0'>
- <a href='javascript:void(0);' id='acq-invoice-new-item'>Add Charge...</a>
- </td>
- </tr>
- </tbody>
- <tbody>
- <tr>
- <td style='margin-top:15px;' colspan='0'>
- <h3> </h3>
- </td>
- </tr>
- </tbody>
- <thead>
- <tr>
- <th colspan='3'/>
- <th class='acq-invoice-center-col acq-invoice-billed-col'>Total</th>
- <th class='acq-invoice-paid-col'>Total</th>
- <th class='acq-invoice-center-col acq-invoice-balance-col'>Balance</th>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td colspan='3' style='text-align:right;'>
- <button jsId='invoiceSaveButton' class='hide-complete'
- dojoType='dijit.form.Button' onclick='saveChanges();'>Save</button>
- <button jsId='invoiceProrateButton' class='hide-complete'
- dojoType='dijit.form.Button' onclick='saveChanges(true);'>Save & Prorate</button>
- <button jsId='invoiceCloseButton' class='hide-complete'
- dojoType='dijit.form.Button' onclick='saveChanges(false, true);'>Save & Close</button>
- <span class='hidden' id='acq-invoice-reopen-button-wrapper'>
- <button jsId='invoiceReopenButton'
- dojoType='dijit.form.Button' onclick='saveChanges(false, false, true);'>Reopen Invoice</button>
- </span>
- </td>
- <td class='acq-invoice-center-col'><div jsId='totalInvoicedBox' dojoType='dijit.form.CurrencyTextBox' style='width:9em;'></div></td>
- <td class='acq-invoice-paid-col'><div jsId='totalPaidBox' dojoType='dijit.form.CurrencyTextBox' style='width:9em;'></div></td>
- <td class='acq-invoice-center-col'><div jsId='balanceOwedBox' dojoType='dijit.form.CurrencyTextBox' style='width:9em;'></div></td>
- </tr>
- </tbody>
- </table>
+ <div id='acq-invoice-summary'>
+ <button id='acq-invoice-summary-toggle-off'>[% l('Hide Details') %]</button>
+ <div id='acq-view-invoice-div'></div>
+ </div>
+ <div id='acq-invoice-summary-small'>
+ <button id='acq-invoice-summary-toggle-on'>[% l('Show Details') %]</button>
+ <span style='font-weight:bold; font-size:120%' id='acq-invoice-summary-name'></span>
+ <br/>
+ <br/>
+ </div>
</div>
+
+ <div dojoType="dijit.layout.ContentPane" layoutAlign="client">
+
+
+ <div dojoType="dijit.layout.TabContainer" style="width: 96%; height: 100%;">
+ <div dojoType="dijit.layout.ContentPane"
+ class='oils-acq-detail-content-pane' title="Invoice" selected='true' style='height:600px'>
+
+ <script type='dojo/connect' event='onShow'>
+ // the table is left at display=none on subsequent tab views
+ dojo.byId('oils-acq-invoice-table').style.display = 'table'
+ </script>
+
+ <table id='oils-acq-invoice-table' class='oils-acq-invoice-table'>
+ <thead/>
+ <tbody id='acq-invoice-entry-header' class='hidden'>
+ <tr>
+ <td colspan='0'>
+ <h3>
+ [% l('Bibliographic Items') %]
+ </h3>
+ </td>
+ </tr>
+ </tbody>
+ <!-- acq.invoice_entry -->
+ <thead id='acq-invoice-entry-thead' class='hidden'>
+ <tr>
+ <th colspan='2'>Title Details</th>
+ <th class='acq-invoice-center-col'># Invoiced / # Paid</th>
+ <th class='acq-invoice-center-col'>Billed</th>
+ <th class='acq-invoice-paid-per-copy-col'>Per Copy</th>
+ <th class='acq-invoice-paid-col'>Paid</th>
+ <th class='acq-invoice-center-col hide-complete'>Detach</th>
+ </tr>
+ </thead>
+ <tbody id='acq-invoice-entry-tbody' class='hidden'>
+ <tr id='acq-invoice-entry-template' class='acq-invoice-row'>
+ <td colspan='2'>
+ <div name='title_details'></div>
+ <div name='note'></div>
+ </td>
+ <td class='acq-invoice-center-col'>
+ <span name='inv_item_count'></span> / <span name='phys_item_count'></span>
+ </td>
+ <td class='acq-invoice-billed-col'><div name='cost_billed'></div></td>
+ <td><div name='amount_paid_per_copy'>0.00</div></td>
+ <td class='acq-invoice-paid-col'><div name='amount_paid'></div></td>
+ <td class='acq-invoice-center-col hide-complete'><a href='javascript:void(0);' name='detach'>Detach</a></td>
+ </tr>
+ </tbody>
+ <tbody>
+ <tr>
+ <td style='margin-top:15px;' colspan='0'>
+ <h3>Direct Charges, Taxes, Fees, etc.</h3>
+ </td>
+ </tr>
+ </tbody>
+ <!-- acq.invoice_item -->
+ <thead>
+ <tr>
+ <th>Charge Type</th>
+ <th class='acq-invoice-center-col'>Fund</th>
+ <th>Title/Description</th>
+ <th class='acq-invoice-center-col'>Billed</th>
+ <th/>
+ <th class='acq-invoice-paid-col'>Paid</th>
+ <th class='acq-invoice-center-col hide-complete'>Delete</th>
+ </tr>
+ </thead>
+ <tbody id='acq-invoice-item-tbody'>
+ <tr id='acq-invoice-item-template' class='acq-invoice-row acq-invoice-item-row'>
+ <td><div name='inv_item_type'></div></td>
+ <td class='acq-invoice-center-col'><div name='fund'></div></td>
+ <td><div name='title'></div></td>
+ <td class='acq-invoice-center-col acq-invoice-billed-col'><div name='cost_billed'></div></td>
+ <td/>
+ <td class='acq-invoice-paid-col'><div name='amount_paid'></div></td>
+ <td class='acq-invoice-center-col hide-complete'><a href='javascript:void(0);' name='delete'>Delete</a></td>
+ </tr>
+ </tbody>
+ <tbody class='hide-complete'>
+ <tr>
+ <td colspan='0'>
+ <a href='javascript:void(0);' id='acq-invoice-new-item'>Add Charge...</a>
+ </td>
+ </tr>
+ </tbody>
+ <tbody>
+ <tr>
+ <td style='margin-top:15px;' colspan='0'>
+ <h3> </h3>
+ </td>
+ </tr>
+ </tbody>
+ <thead>
+ <tr>
+ <th colspan='3'/>
+ <th class='acq-invoice-center-col acq-invoice-billed-col'>Total</th>
+ <th/>
+ <th class='acq-invoice-paid-col'>Total</th>
+ <th class='acq-invoice-center-col acq-invoice-balance-col'>Balance</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td colspan='3' style='text-align:right;'>
+ <button jsId='invoiceSaveButton' class='hide-complete'
+ dojoType='dijit.form.Button' onclick='saveChanges();'>[% l('Save') %]</button>
+ <button jsId='invoiceSaveButton' class='hide-complete'
+ dojoType='dijit.form.Button' onclick='saveChanges({clear:true});'>[% l('Save & Clear') %]</button>
+ <button jsId='invoiceProrateButton' class='hide-complete'
+ dojoType='dijit.form.Button' onclick='saveChanges({prorate:true});'>[% l('Prorate') %]</button>
+ <button jsId='invoiceCloseButton' class='hide-complete'
+ dojoType='dijit.form.Button' onclick='saveChanges({close:true});'>[% l('Close') %]</button>
+ <span class='hidden' id='acq-invoice-reopen-button-wrapper'>
+ <button jsId='invoiceReopenButton'
+ dojoType='dijit.form.Button' onclick='saveChanges({reopen:true});'>[% l('Reopen') %]</button>
+ </span>
+ </td>
+ <td class='acq-invoice-center-col'><div id='acq-total-invoiced-box'></div></td>
+ <td/>
+ <td class='acq-invoice-paid-col'><div id='acq-total-paid-box'></div></td>
+ <td class='acq-invoice-center-col'><div id='acq-total-balance-box'></div></td>
+ </tr>
+ </tbody>
+ </table>
+ </div> <!-- tab 1 -->
+
+ <div dojoType="dijit.layout.ContentPane"
+ class='oils-acq-detail-content-pane' title="Search">
+
+ <script type='dojo/connect' event='onShow'>
+ // hide summary info when opening the search tab
+ dojo.byId('acq-invoice-summary-toggle-off').onclick();
+ renderUnifiedSearch();
+ </script>
+
+
+ <!-- slim, inline unified search UI -->
+ <div id='oils-acq-invoice-search' _class='hidden'>
+
+ <div id="acq-unified-form">
+ <div>
+ <label for="acq-unified-result-type">[% l('Search for') %]</label>
+ <select id="acq-unified-result-type" disabled='disabled'>
+ <option value="lineitem">line items</option>
+ </select>
+ <label for="acq-unified-conjunction">matching</label>
+ <select id="acq-unified-conjunction">
+ <option value="and">all</option>
+ <option value="or">any</option>
+ </select>
+ <label for="acq-unified-conjunction">
+ of the following terms:
+ </label>
+ </div>
+ <div id="acq-unified-terms">
+ <table id="acq-unified-terms-table">
+ <tbody id="acq-unified-terms-tbody">
+ <tr id="acq-unified-terms-row-tmpl"
+ class="acq-unified-terms-row">
+ <td name="selector"
+ class="acq-unified-terms-selector"></td>
+ <td name="match"
+ class="acq-unified-terms-match">
+ <select>
+ <option value="">is</option>
+ <option value="__not">is NOT</option>
+ <option value="__fuzzy" disabled="disabled">
+ contains
+ </option>
+ <option value="__not,__fuzzy"
+ disabled="disabled">
+ does NOT contain
+ </option>
+ <option value="__lte" disabled="disabled">
+ is on or BEFORE
+ </option>
+ <option value="__gte" disabled="disabled">
+ is on or AFTER
+ </option>
+ <option value="__in" disabled="disabled">
+ matches a term from a file
+ </option>
+ </select>
+ </td>
+ <td name="widget"
+ class="acq-unified-terms-widget"></td>
+ <td name="remove"
+ class="acq-unified-terms-remove"></td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ <div id="acq-unified-add-term">
+ <button onclick="termManager.addRow()">Add Search Term</button>
+ </div>
+ <table width='100%'><tr>
+ <td align='left'>
+ <button onclick="performSearch(0)">[% l('Search') %]</button>
+ <button onclick='addSelectedToInvoice()'>
+ [% l('Add Selected Items to Invoice') %]
+ </button>
+ <span id='acq-inv-search-prev'>
+ <a href='javascript:performSearch(-1)'>[% l('Previous') %]</a>
+ </span>
+ <span>
+ <a href='javascript:performSearch(1)'>[% l('Next') %]</a>
+ </span>
+ </td>
+ <td align='right'>
+ <input type='checkbox' id='acq-invoice-search-sort-title'/>
+ [% l('Sort by title') %]
+ <span style='padding-left:8px;'>
+ <input type='checkbox' id='acq-invoice-search-limit-invoiceable' checked='checked'/>
+ [% l('Limit to Invoiceable Items') %]
+ </td>
+ </tr></table>
+ </div> <!-- end search form -->
+ <div id='acq-unified-results-no_results'>
+ <b>[% l('No Results') %]</b>
+ </div>
+ <div id='acq-unified-results-lineitem'>
+ <style>
+ #acq-invoice-search-results-tbody { width: 100%; }
+ #acq-invoice-search-results-tbody td {
+ padding: 5px;
+ border-bottom: 1px solid #888;
+ }
+ .search-resutls-select-td {
+ padding-right: 8px; border-right: 2px solid #888;
+ }
+ .search-results-content-td {
+ padding-left: 8px; border-left: 2px solid #888;
+ }
+ .search-results-already-invoiced {
+ background-color: #E99;
+ }
+ </style>
+ <table>
+ <tbody id='acq-invoice-search-results-tbody'>
+ <tr id='acq-invoice-search-results-tr'>
+ <td class='search-resutls-select-td'>
+ <input type='checkbox' name='search-results-checkbox'/>
+ </td>
+ <td class='search-results-content-td'>
+ <div name='search-results-content-div'>
+ <img src='[% ctx.media_prefix %]/opac/images/progressbar_green.gif'/>
+ </div>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ </div>
+ </div> <!-- tab 2 -->
+ </div> <!-- end tabcontainer -->
+ </div> <!-- end contentpane -->
+
</div>
<div dojoType='openils.widget.ProgressDialog' jsId='progressDialog'></div>
<div jsId='extraItemsDialog' dojoType="dijit.Dialog" title="Extra Items">
<button dojoType='dijit.form.Button' jsId='extraCopiesGo'>Add New Items</button>
</div>
</div>
+<script type="text/javascript">
+ var invoiceId = '[% ctx.page_args.0 %]';
+ window.unifiedSearchExternalMode = true;
+</script>
+<script type="text/javascript" src="[% ctx.media_prefix %]/js/ui/default/acq/common/base64.js"></script>
+<script type="text/javascript" src="[% ctx.media_prefix %]/js/ui/default/acq/search/unified.js"></script>
<script type="text/javascript" src='[% ctx.media_prefix %]/js/ui/default/acq/invoice/common.js'> </script>
<script type="text/javascript" src='[% ctx.media_prefix %]/js/ui/default/acq/invoice/view.js'> </script>
[% END %]
.acq-inoice-item-extra-info { padding-left: 10px; }
.acq-inoice-item-info { font-weight: bold; }
.acq-invoice-row td { border-bottom: 1px solid #e0e0e0; }
-.acq-invoice-invalid-amount input { color: red; font-weight: bold; }
+.acq-invoice-invalid-amount { color: red; font-weight: bold; }
.acq-link-invoice-dialog td,.acq-link-invoice-dialog th {padding-top: 10px;}
.acq-invoice-paid-col {background : #E0E0E0; text-align: center;}
.acq-invoice-center-col { text-align: center; }
function drawInvoicePane(parentNode, inv, args) {
args = args || {};
+ var pane;
var override = {};
if(!inv) {
override = {
recv_date : {widgetValue : dojo.date.stamp.toISOString(new Date())},
- receiver : {widgetValue : openils.User.user.ws_ou()},
+ //receiver : {widgetValue : openils.User.user.ws_ou()},
recv_method : {widgetValue : 'PPR'}
};
}
dojo.mixin(override, {
- provider : { dijitArgs : { store_options : { base_filter : { active :"t" } } } },
+ provider : {
+ dijitArgs : {
+ store_options : { base_filter : { active :"t" } },
+ onChange : function(val) {
+ pane.setFieldValue('shipper', val);
+ }
+ }
+ },
shipper : { dijitArgs : { store_options : { base_filter : { active :"t" } } } }
});
override[field] = {widgetValue : args[field]};
}
- var pane = new openils.widget.EditPane({
+ // push the name of the invoice into the name display field after update
+ override.inv_ident = dojo.mixin(
+ override.inv_ident,
+ {dijitArgs : {onChange :
+ function(newVal) {
+ if (dojo.byId('acq-invoice-summary-name'))
+ dojo.byId('acq-invoice-summary-name').innerHTML = newVal;
+ }
+ }}
+ );
+
+
+ pane = new openils.widget.EditPane({
fmObject : inv,
paneStackCount : 2,
fmClass : 'acqinv',
dojo.require('dojo.date.locale');
dojo.require('dojo.date.stamp');
+dojo.require('dojo.cookie');
dojo.require('dijit.form.CheckBox');
+dojo.require('dijit.form.Button');
dojo.require('dijit.form.CurrencyTextBox');
dojo.require('dijit.form.NumberTextBox');
dojo.require('openils.User');
var extraCopiesFund;
var widgetRegistry = {acqie : {}, acqii : {}};
var focusLineitem;
+var searchInitDone = false;
+var termManager;
+var resultManager;
function nodeByName(name, context) {
return dojo.query('[name='+name+']', context)[0];
focusLineitem = new openils.CGI().param('focus_li');
+ totalInvoicedBox = dojo.byId('acq-total-invoiced-box');
+ totalPaidBox = dojo.byId('acq-total-paid-box');
+ balanceOwedBox = dojo.byId('acq-total-balance-box');
+
itemTypes = pcrud.retrieveAll('aiit');
+ dojo.byId('acq-invoice-summary-toggle-off').onclick = function() {
+ openils.Util.hide(dojo.byId('acq-invoice-summary'));
+ openils.Util.show(dojo.byId('acq-invoice-summary-small'));
+ };
+
+ dojo.byId('acq-invoice-summary-toggle-on').onclick = function() {
+ openils.Util.show(dojo.byId('acq-invoice-summary'));
+ openils.Util.hide(dojo.byId('acq-invoice-summary-small'));
+ }
+
if(cgi.param('create')) {
renderInvoice();
+ // show summary info by default for new invoices
+ dojo.byId('acq-invoice-summary-toggle-on').onclick();
+
} else {
+ dojo.byId('acq-invoice-summary-toggle-off').onclick();
fieldmapper.standardRequest(
['open-ils.acq', 'open-ils.acq.invoice.retrieve.authoritative'],
{
);
}
+ // display items and entries in ID order
+ // which effectively equates to add order.
+ function idsort(a, b) { return a.id() < b.id() ? -1 : 1 }
+
if(invoice) {
dojo.forEach(
- invoice.items(),
+ invoice.items().sort(idsort),
function(item) {
addInvoiceItem(item);
}
);
dojo.forEach(
- invoice.entries(),
+ invoice.entries().sort(idsort),
function(entry) {
addInvoiceEntry(entry);
}
if(attachPo.length) doAttachPo(0);
}
-function doAttachLi() {
+function doAttachLi(skipInit) {
//var invoiceArgs = {provider : lineitem.provider(), shipper : lineitem.provider()};
- if(cgi.param('create')) {
+ if(cgi.param('create') && !skipInit) {
// use the first LI in the list to determine the default provider
fieldmapper.standardRequest(
);
}
+function performSearch(pageDir) {
+ clearSearchResTable();
+ var searchObject = termManager.buildSearchObject();
+ dojo.cookie('invs', base64Encode(searchObject));
+ dojo.cookie('invc', dojo.byId("acq-unified-conjunction").getValue());
+
+ if (pageDir == 0) { // new search
+ resultsLoader.displayOffset = 0;
+ } else {
+ resultsLoader.displayOffset += pageDir * resultsLoader.displayLimit;
+ }
+
+ if (resultsLoader.displayOffset == 0) {
+ openils.Util.hide('acq-inv-search-prev');
+ } else {
+ openils.Util.show('acq-inv-search-prev', 'inline');
+ }
+
+ if (dojo.byId('acq-invoice-search-limit-invoiceable').checked) {
+ if (!searchObject.jub)
+ searchObject.jub = [];
+
+ // exclude lineitems that are "cancelled" (sidebar: 'Mericans spell it 'canceled')
+ searchObject.jub.push({state : 'cancelled', '__not' : true});
+
+ // exclude lineitems already linked to this invoice
+ if (invoice && invoice.id() > 0) {
+ if (!searchObject.acqinv)
+ searchObject.acqinv = [];
+ searchObject.acqinv.push({id : invoice.id(), '__not' : true});
+ }
+
+ // limit to lineitems that have invoiceable copies
+ searchObject.acqlisumi = [{item_count : 1, '_gte' : true}];
+
+ // limit to provider if a provider is selected
+ var provider = invoicePane.getFieldValue('provider');
+ if (provider) {
+ if (!searchObject.jub.filter(function(i) { return i.provider != null }).length)
+ searchObject.jub.push({provider : provider});
+ }
+ }
+
+ if (dojo.byId('acq-invoice-search-sort-title').checked) {
+ uriManager.order_by =
+ [ {"class": "acqlia", "field":"attr_value", "transform":"first"} ];
+ }
+
+ resultsLoader.lastSearch = searchObject;
+ resultManager.go(searchObject)
+ console.log('Lineitem Search: ' + js2JSON(searchObject));
+ focusLastSearchInput();
+}
+
+
+function renderUnifiedSearch() {
+
+ if (!searchInitDone) {
+
+ searchInitDone = true;
+ termManager = new TermManager();
+ resultManager = new ResultManager();
+ resultsLoader = new searchResultsLoader();
+ uriManager = new URIManager();
+
+ // define custom lineitem result handler
+ resultManager.result_types = {
+ "lineitem": {
+ "search_options": { "id_list": true },
+ "revealer": function() { },
+ "finisher": function() {
+ resultsLoader.batch_length = resultManager.count_results;
+ },
+ "adder": function(li) {
+ resultsLoader.addLineitem(li);
+ },
+ "interface": resultsLoader
+ },
+ "no_results": {
+ "revealer": function() { }
+ }
+ };
+
+ var searchObject = dojo.cookie('invs');
+ console.log('loaded ' + searchObject);
+ if (searchObject) {
+ // if there is a search object cookie, populate the search form
+ termManager.reflect(base64Decode(searchObject));
+ dojo.byId("acq-unified-conjunction").setValue(dojo.cookie('invc'));
+ } else {
+ console.log('adding row');
+ termManager.addRow();
+ }
+ }
+
+ dojo.addClass(dojo.byId('oils-acq-invoice-table'), 'hidden');
+ dojo.removeClass(dojo.byId('oils-acq-invoice-search'), 'hidden');
+ focusLastSearchInput();
+}
+
+function focusLastSearchInput() {
+ // TODO: see about making this better and moving it into search/unified.js
+ var wnodes = dojo.query('[name=widget]');
+ var inputNode = wnodes.item(wnodes.length - 1).firstChild;
+ if (inputNode) {
+ try {
+ inputNode.select();
+ } catch(E) {
+ inputNode.focus();
+ }
+ }
+}
+
+var resultsTbody, resultsRow;
+function searchResultsLoader() {
+ this.displayOffset = 0;
+ this.displayLimit = 10;
+
+ if (!resultsTbody) {
+ resultsTbody = dojo.byId('acq-invoice-search-results-tbody');
+ resultsRow = resultsTbody.removeChild(dojo.byId('acq-invoice-search-results-tr'));
+ }
+
+ this.addLineitem = function(li_id) {
+ console.log('Adding search result lineitem ' + li_id);
+ var row = resultsRow.cloneNode(true);
+ resultsTbody.appendChild(row);
+ var checkbox = dojo.query('[name=search-results-checkbox]', row)[0];
+ checkbox.setAttribute('lineitem', li_id);
+
+ // this lineitem is already part of the invoice
+ if (dojo.query('[entry_lineitem_row=' + li_id + ']')[0]) {
+ checkbox.disabled = true;
+ dojo.addClass(checkbox.parentNode, 'search-results-already-invoiced');
+ }
+
+ openils.acq.Lineitem.fetchAndRender(
+ li_id, {},
+ function(li, html) {
+ dojo.query('[name=search-results-content-div]', row)[0].innerHTML = html;
+ }
+ );
+ }
+}
+
+function addSelectedToInvoice() {
+ var inputs = dojo.query('[name=search-results-checkbox]');
+ attachLi = [];
+ dojo.forEach(inputs,
+ function(checkbox) {
+ if (checkbox.checked) {
+ attachLi.push(checkbox.getAttribute('lineitem'));
+ checkbox.disabled = true;
+ checkbox.checked = false;
+ dojo.addClass(checkbox.parentNode, 'search-results-already-invoiced');
+ }
+ }
+ );
+ doAttachLi(true);
+}
+
+function clearSearchResTable() {
+ while (resultsTbody.childNodes[0])
+ resultsTbody.removeChild(resultsTbody.childNodes[0]);
+}
+
function updateTotalCost() {
var totalCost = 0;
for(var id in widgetRegistry.acqie)
if(!widgetRegistry.acqie[id]._object.isdeleted())
totalCost += Number(widgetRegistry.acqie[id].cost_billed.getFormattedValue());
- totalInvoicedBox.attr('value', totalCost);
+ totalInvoicedBox.innerHTML = totalCost.toFixed(2);
totalPaid = 0;
for(var id in widgetRegistry.acqii)
for(var id in widgetRegistry.acqie)
if(!widgetRegistry.acqie[id]._object.isdeleted())
totalPaid += Number(widgetRegistry.acqie[id].amount_paid.getFormattedValue());
- totalPaidBox.attr('value', totalPaid);
+ totalPaidBox.innerHTML = totalPaid.toFixed(2);
var buttonsDisabled = false;
if(totalPaid > totalCost || totalPaid < 0) {
- openils.Util.addCSSClass(totalPaidBox.domNode, 'acq-invoice-invalid-amount');
+ openils.Util.addCSSClass(totalPaidBox, 'acq-invoice-invalid-amount');
invoiceSaveButton.attr('disabled', true);
invoiceProrateButton.attr('disabled', true);
buttonsDisabled = true;
} else {
- openils.Util.removeCSSClass(totalPaidBox.domNode, 'acq-invoice-invalid-amount');
+ openils.Util.removeCSSClass(totalPaidBox, 'acq-invoice-invalid-amount');
invoiceSaveButton.attr('disabled', false);
invoiceProrateButton.attr('disabled', false);
}
if(totalCost < 0) {
- openils.Util.addCSSClass(totalInvoicedBox.domNode, 'acq-invoice-invalid-amount');
+ openils.Util.addCSSClass(totalInvoicedBox, 'acq-invoice-invalid-amount');
invoiceSaveButton.attr('disabled', true);
invoiceProrateButton.attr('disabled', true);
} else {
- openils.Util.removeCSSClass(totalInvoicedBox.domNode, 'acq-invoice-invalid-amount');
+ openils.Util.removeCSSClass(totalInvoicedBox, 'acq-invoice-invalid-amount');
if(!buttonsDisabled) {
invoiceSaveButton.attr('disabled', false);
invoiceProrateButton.attr('disabled', false);
invoiceCloseButton.attr('disabled', true);
}
- balanceOwedBox.attr('value', (totalCost - totalPaid));
+ balanceOwedBox.innerHTML = (totalCost - totalPaid).toFixed(2);
+
+ updateExpectedCost();
}
} else if(field == 'cost_billed' || field == 'amount_paid') {
args = {required : true, style : 'width: 8em'};
}
+
registerWidget(
item,
field,
}
+// expected cost is totalCostInvoiced + totalCostNotYetInvoiced
+function updateExpectedCost() {
+
+ var cost = Number(totalInvoicedBox.innerHTML || 0);
+
+ // for any LI's that are not yet billed (i.e. filled in)
+ // use the total expected cost for that lineitem.
+ for(var id in widgetRegistry.acqie) {
+ var entry = widgetRegistry.acqie[id]._object;
+ if(!entry.isdeleted()) {
+ if (Number(widgetRegistry.acqie[id].cost_billed.getFormattedValue()) == 0) {
+ var li = entry.lineitem();
+ cost +=
+ Number(li.order_summary().estimated_amount()) -
+ Number(li.order_summary().paid_amount());
+ }
+ }
+ }
+
+ dojo.byId('acq-invoice-summary-cost').innerHTML = cost.toFixed(2);
+}
+
+var invoicEntryWidgets = {};
function addInvoiceEntry(entry) {
+ console.log('Adding new entry for lineitem ' + entry.lineitem());
openils.Util.removeCSSClass(dojo.byId('acq-invoice-entry-header'), 'hidden');
openils.Util.removeCSSClass(dojo.byId('acq-invoice-entry-thead'), 'hidden');
openils.Util.removeCSSClass(dojo.byId('acq-invoice-entry-tbody'), 'hidden');
+ dojo.byId('acq-invoice-summary-count').innerHTML =
+ Number(dojo.byId('acq-invoice-summary-count').innerHTML) + 1;
+
entryTbody = dojo.byId('acq-invoice-entry-tbody');
if(entryTemplate == null) {
entryTemplate = entryTbody.removeChild(dojo.byId('acq-invoice-entry-template'));
var row = entryTemplate.cloneNode(true);
row.setAttribute('lineitem', entry.lineitem());
+ row.setAttribute('entry_lineitem_row', entry.lineitem());
openils.acq.Lineitem.fetchAndRender(
entry.lineitem(), {},
updateReceiveLink(li);
+ // set some default values if otherwise unset
+ if (!invoicePane.getFieldValue('receiver')) {
+ invoicePane.setFieldValue('receiver', li.purchase_order().ordering_agency());
+ }
+ if (!invoicePane.getFieldValue('provider')) {
+ invoicePane.setFieldValue('provider', li.purchase_order().provider());
+ }
+
dojo.forEach(
['inv_item_count', 'phys_item_count', 'cost_billed', 'amount_paid'],
function(field) {
parentNode : nodeByName(field, row)
}),
function(w) {
+
if(field == 'phys_item_count') {
dojo.connect(w, 'onChange',
function() {
}
}
)
- }
- }
+ } // if
+
+ if(field == 'inv_item_count' || field == 'cost_billed') {
+ setPerCopyPrice(row, entry);
+ // update the per-copy count as invoice count and cost billed change
+ dojo.connect(w, 'onChange', function() { setPerCopyPrice(row, entry) } );
+ }
+
+ } // func
);
}
);
+ updateTotalCost();
if (focusLineitem == li.id())
focusLi();
}
}
entryTbody.appendChild(row);
- updateTotalCost();
+}
+
+function setPerCopyPrice(row, entry) {
+ var inv_w = widgetRegistry.acqie[entry.id()].inv_item_count;
+ var bill_w = widgetRegistry.acqie[entry.id()].cost_billed;
+
+ if (inv_w && bill_w) {
+ var invoiced = Number(inv_w.getFormattedValue());
+ var billed = Number(bill_w.getFormattedValue());
+ console.log(invoiced + ' : ' + billed);
+ if (invoiced > 0) {
+ nodeByName('amount_paid_per_copy', row).innerHTML = (billed / invoiced).toFixed(2);
+ } else {
+ nodeByName('amount_paid_per_copy', row).innerHTML = '0.00';
+ }
+ }
}
function liMarcAttr(lineitem, name) {
return (attr) ? attr.attr_value() : '';
}
-function saveChanges(doProrate, doClose, doReopen) {
- createExtraCopies(
- function() {
- saveChangesPartTwo(doProrate, doClose, doReopen);
- }
- );
+function saveChanges(args) {
+ args = args || {};
+ createExtraCopies(function() { saveChangesPartTwo(args); });
}
// Define a helper function to 'unflesh' sub-objects from an fmclass object.
});
}
-function saveChangesPartTwo(doProrate, doClose, doReopen) {
-
+function saveChangesPartTwo(args) {
+ args = args || {};
- if(doReopen) {
+ if(args.reopen) {
invoice.complete('f');
} else {
return;
}
- if(doClose)
+ if(args.close)
invoice.complete('t');
progressDialog.hide();
var invoice = openils.Util.readResponse(r);
if(invoice) {
- if(doProrate)
+ if(args.prorate)
return prorateInvoice(invoice);
- location.href = oilsBasePath + '/acq/invoice/view/' + invoice.id();
+ if (args.clear) {
+ location.href = oilsBasePath + '/acq/invoice/view?create=1';
+ } else {
+ location.href = oilsBasePath + '/acq/invoice/view/' + invoice.id();
+ }
}
}
}
dojo.require("openils.widget.AutoWidget");
dojo.require("openils.widget.XULTermLoader");
dojo.require("openils.PermaCrud");
+dojo.require('dijit.layout.TabContainer');
if (!localeStrings) { /* we can do this because javascript doesn't have block
scope */
};
this.terms = {};
- ["jub", "acqpl", "acqpo", "acqinv"].forEach(
+ ["jub", "acqpl", "acqpo", "acqinv", "acqlid"].forEach(
function(hint) {
var o = {};
o.__label = fieldmapper.IDL.fmclasses[hint].label;
this.plCache = {};
this.invCache = {};
- this.result_types = {
- "lineitem": {
- "search_options": {
- "flesh_attrs": true,
- "flesh_cancel_reason": true,
- "flesh_notes": true
- },
- "revealer": function() {
- self.liPager.show();
- progressDialog.show(true);
- },
- "finisher": function() {
- self.liPager.batch_length = self.count_results;
- self.liPager.relabelControls();
- self.liPager.enableControls(true);
- progressDialog.hide();
- },
- "adder": function(li) {
- self.liPager.liTable.addLineitem(li);
- },
- "interface": self.liPager
- },
- "purchase_order": {
- "search_options": {
- "no_flesh_cancel_reason": true
- },
- "revealer": function() {
- self.poGrid.resetStore();
- self.poGrid.showLoadProgressIndicator();
- self.poCache = {};
- },
- "finisher": function() {
- self.poGrid.hideLoadProgressIndicator();
- },
- "adder": function(po) {
- self.poCache[po.id()] = po;
- self.poGrid.store.newItem(acqpo.toStoreItem(po));
- },
- "interface": self.poGrid
- },
- "picklist": {
- "search_options": {
- "flesh_lineitem_count": true,
- "flesh_owner": true
- },
- "revealer": function() {
- self.plGrid.resetStore();
- self.plGrid.showLoadProgressIndicator();
- self.plCache = {};
- },
- "finisher": function() {
- self.plGrid.hideLoadProgressIndicator();
- },
- "adder": function(pl) {
- self.plCache[pl.id()] = pl;
- self.plGrid.store.newItem(acqpl.toStoreItem(pl));
- },
- "interface": self.plGrid
- },
- "invoice": {
- "search_options": {
- "no_flesh_misc": true
+ if (window.unifiedSearchExternalMode) {
+
+ // external user will define result types and handlers
+
+ } else {
+
+ this.result_types = {
+ "lineitem": {
+ "search_options": {
+ "flesh_attrs": true,
+ "flesh_cancel_reason": true,
+ "flesh_notes": true
+ },
+ "revealer": function() {
+ self.liPager.show();
+ progressDialog.show(true);
+ },
+ "finisher": function() {
+ self.liPager.batch_length = self.count_results;
+ self.liPager.relabelControls();
+ self.liPager.enableControls(true);
+ progressDialog.hide();
+ },
+ "adder": function(li) {
+ self.liPager.liTable.addLineitem(li);
+ },
+ "interface": self.liPager
},
- "finisher": function() {
- self.invGrid.hideLoadProgressIndicator();
+ "purchase_order": {
+ "search_options": {
+ "no_flesh_cancel_reason": true
+ },
+ "revealer": function() {
+ self.poGrid.resetStore();
+ self.poGrid.showLoadProgressIndicator();
+ self.poCache = {};
+ },
+ "finisher": function() {
+ self.poGrid.hideLoadProgressIndicator();
+ },
+ "adder": function(po) {
+ self.poCache[po.id()] = po;
+ self.poGrid.store.newItem(acqpo.toStoreItem(po));
+ },
+ "interface": self.poGrid
},
- "revealer": function() {
- self.invGrid.resetStore();
- self.invCache = {};
+ "picklist": {
+ "search_options": {
+ "flesh_lineitem_count": true,
+ "flesh_owner": true
+ },
+ "revealer": function() {
+ self.plGrid.resetStore();
+ self.plGrid.showLoadProgressIndicator();
+ self.plCache = {};
+ },
+ "finisher": function() {
+ self.plGrid.hideLoadProgressIndicator();
+ },
+ "adder": function(pl) {
+ self.plCache[pl.id()] = pl;
+ self.plGrid.store.newItem(acqpl.toStoreItem(pl));
+ },
+ "interface": self.plGrid
},
- "adder": function(inv) {
- self.invCache[inv.id()] = inv;
- self.invGrid.store.newItem(acqinv.toStoreItem(inv));
+ "invoice": {
+ "search_options": {
+ "no_flesh_misc": true
+ },
+ "finisher": function() {
+ self.invGrid.hideLoadProgressIndicator();
+ },
+ "revealer": function() {
+ self.invGrid.resetStore();
+ self.invCache = {};
+ },
+ "adder": function(inv) {
+ self.invCache[inv.id()] = inv;
+ self.invGrid.store.newItem(acqinv.toStoreItem(inv));
+ },
+ "interface": self.invGrid
},
- "interface": self.invGrid
- },
- "no_results": {
- "revealer": function() { alert(localeStrings.NO_RESULTS); }
+ "no_results": {
+ "revealer": function() { alert(localeStrings.NO_RESULTS); }
+ }
}
};
};
this.go = function(search_object) {
+
+ if (window.unifiedSearchExternalMode) {
+ // assume for now that external mode implies inline results display
+
+ uriManager = uriManager || new URIManager();
+ uriManager.search_object = search_object;
+ uriManager.result_type = dojo.byId("acq-unified-result-type").getValue();
+ uriManager.conjunction = dojo.byId("acq-unified-conjunction").getValue();
+ this.search(uriManager, termManager);
+
+ } else {
+
location.href = oilsBasePath + "/acq/search/unified?" +
"so=" + base64Encode(search_object) +
"&rt=" + dojo.byId("acq-unified-result-type").getValue() +
"&c=" + dojo.byId("acq-unified-conjunction").getValue();
+ }
};
this.search = function(uriManager, termManager) {
/* onload */
openils.Util.addOnLoad(
function() {
+
+ // onload handled by external user
+ if (window.unifiedSearchExternalMode) return;
+
termManager = new TermManager();
+
resultManager = new ResultManager(
new LiTablePager(null, new AcqLiTable()),
dijit.byId("acq-unified-po-grid"),
--- /dev/null
+ACQ Invoice Inline Lineitem Search and Add
+------------------------------------------
+
+The Invoice UI is how composed of two tabs, the main invoice tab and a new Search tab. The search tab consists of a subset of the Acquisitions unified search interface. The goal is to allow users to search for lineitems to invoice. Search results may be added directly to the growing invoice. A number of small usability features are included.
+
+Features
+~~~~~~~~
+
+ * Option (default) to limit searches to invoiceable items.
+ ** These are lineitems that are not cancelled, have at least one invoiceable copy, linked to a PO whose provider matches that of the current invoice, and are not already linked to the current invoice.
+ * Search defaults to last-run search (on workstation).
+ * New Lineitem Detail filter options
+ * Sort searches by lineitem number (default) and title.
+ * There is a new Expected Cost field which includes both the total invoiced cost plus the anticipated cost of lineitems as they are added.
+ * New Price per Copy field
+ * Lineitem count field
+ * Show / Hide Invoice details button. Details are displayed by default, but hidden when the user enters the search tab. From there it remains hidden until manually shown (or a new invoice is opened).
+ * A new "Save & Clear" button which saves the current invoice then clears the invoice display to create a new invoice.
+ * Provider, shipper, and receiver fields are auto-populated from the first-added invoice data (when not already set).
+ * Totals are now read-only, since they are derived from existing data (and are informational only).
+
+