<field reporter:label="Fund" name="fund" reporter:datatype="link" />
<field reporter:label="Fund Debit" name="fund_debit" reporter:datatype="link" />
<field reporter:label="Owning Library" name="owning_lib" reporter:datatype="org_unit" />
- <field reporter:label="Shelving Location" name="location" reporter:datatype="link" />
+ <field reporter:label="Copy Location" name="location" reporter:datatype="link" />
<field reporter:label="Circ Modifier" name="circ_modifier" reporter:datatype="link" />
<field reporter:label="Note" name="note" reporter:datatype="text" />
<field reporter:label="Collection Code" name="collection_code" reporter:datatype="text" />
my($mgr, $li_id, $skip_complete_check) = @_;
my $li = $mgr->editor->retrieve_acq_lineitem($li_id) or return 0;
+ return 0 unless $li->state eq 'on-order' or $li->state eq 'cancelled'; # sic
+
my $lid_ids = $mgr->editor->search_acq_lineitem_detail(
{lineitem => $li_id, recv_time => undef}, {idlist => 1});
{flesh => 1, flesh_fields => {jub => ['attributes']}}
]) or return $e->die_event;
+ return $e->die_event(
+ new OpenILS::Event(
+ "BAD_PARAMS", payload => $li,
+ note => "acq.lineitem #" . $li->id .
+ ": purchase_order #" . $li->purchase_order
+ )
+ ) if $li->purchase_order;
+
$li->provider($po->provider);
$li->purchase_order($po->id);
$li->state('pending-order');
__PACKAGE__->register_method(
+ method => 'receive_lineitem_batch_api',
+ api_name => 'open-ils.acq.lineitem.receive.batch',
+ signature => {
+ desc => 'Mark lineitems as received',
+ params => [
+ {desc => 'Authentication token', type => 'string'},
+ {desc => 'lineitem ID list', type => 'array'}
+ ],
+ return => {desc =>
+ q/On success, stream of objects describing changes to LIs and
+ possibly PO; onerror, Event. Any event, even after lots of other
+ objects, should mean general failure of whole batch operation./
+ }
+ }
+);
+
+sub receive_lineitem_batch_api {
+ my ($self, $conn, $auth, $li_idlist) = @_;
+
+ return unless ref $li_idlist eq 'ARRAY' and @$li_idlist;
+
+ my $e = new_editor(xact => 1, authtoken => $auth);
+ return $e->die_event unless $e->checkauth;
+
+ my $mgr = new OpenILS::Application::Acq::BatchManager(
+ editor => $e, conn => $conn
+ );
+
+ for my $li_id (map { int $_ } @$li_idlist) {
+ my $li = $e->retrieve_acq_lineitem([
+ $li_id, {
+ flesh => 1,
+ flesh_fields => { jub => ['purchase_order'] }
+ }
+ ]) or return $e->die_event;
+
+ return $e->die_event unless $e->allowed(
+ 'RECEIVE_PURCHASE_ORDER', $li->purchase_order->ordering_agency
+ );
+
+ receive_lineitem($mgr, $li_id) or return $e->die_event;
+ $mgr->respond;
+ }
+
+ $e->commit or return $e->die_event;
+ $mgr->respond_complete;
+ $mgr->run_post_response_hooks;
+}
+
+__PACKAGE__->register_method(
method => 'rollback_receive_po_api',
api_name => 'open-ils.acq.purchase_order.receive.rollback'
);
$e->commit and return $result or return $e->die_event;
}
+__PACKAGE__->register_method(
+ method => 'rollback_receive_lineitem_batch_api',
+ api_name => 'open-ils.acq.lineitem.receive.rollback.batch',
+ signature => {
+ desc => 'Mark a list of lineitems as Un-received',
+ params => [
+ {desc => 'Authentication token', type => 'string'},
+ {desc => 'lineitem ID list', type => 'array'}
+ ],
+ return => {desc =>
+ q/on success, a stream of objects describing changes to LI and
+ possibly PO; on error, Event. Any event means all previously
+ returned objects indicate changes that didn't really happen./
+ }
+ }
+);
+
+sub rollback_receive_lineitem_batch_api {
+ my ($self, $conn, $auth, $li_idlist) = @_;
+
+ return unless ref $li_idlist eq 'ARRAY' and @$li_idlist;
+
+ my $e = new_editor(xact => 1, authtoken => $auth);
+ return $e->die_event unless $e->checkauth;
+
+ my $mgr = new OpenILS::Application::Acq::BatchManager(
+ editor => $e, conn => $conn
+ );
+
+ for my $li_id (map { int $_ } @$li_idlist) {
+ my $li = $e->retrieve_acq_lineitem([
+ $li_id, {
+ "flesh" => 1,
+ "flesh_fields" => {"jub" => ["purchase_order"]}
+ }
+ ]);
+
+ my $po = $li->purchase_order;
+
+ return $e->die_event unless
+ $e->allowed('RECEIVE_PURCHASE_ORDER', $po->ordering_agency);
+
+ $li = rollback_receive_lineitem($mgr, $li_id) or return $e->die_event;
+
+ my $result = {"li" => {$li->id => {"state" => $li->state}}};
+ if ($po->state eq "received") { # should happen first time, not after
+ $po->state("on-order");
+ $po = update_purchase_order($mgr, $po) or return $e->die_event;
+ }
+ $result->{"po"} = describe_affected_po($e, $po);
+
+ $mgr->respond(%$result);
+ }
+
+ $e->commit or return $e->die_event;
+ $mgr->respond_complete;
+ $mgr->run_post_response_hooks;
+}
+
__PACKAGE__->register_method(
method => 'set_lineitem_price_api',
# Depending on context, this may not warrant an event.
return -1 if $li->state eq "cancelled";
- # But this always does.
+ # But this always does. Note that this used to be looser, but you can
+ # no longer cancel lineitems that lack a PO or that are in "pending-order"
+ # state (you could in the past).
return new OpenILS::Event(
"ACQ_NOT_CANCELABLE", "note" => "lineitem $li_id"
- ) unless (
- (! $li->purchase_order) or (
- $li->purchase_order and (
- $li->state eq "on-order" or $li->state eq "pending-order"
- )
- )
- );
+ ) unless $li->purchase_order and $li->state eq "on-order";
$li->state("cancelled");
$li->cancel_reason($cancel_reason->id);
params => [
{desc => 'Authentication token', type => 'string'},
{desc => 'The purchase order id', type => 'number'},
- {desc => 'The lineitem ID', type => 'number'},
+ {desc => 'The lineitem ID (or an array of them)', type => 'mixed'},
],
return => {desc => 'Streams a total versus completed counts object, event on error'}
}
my $po = $e->retrieve_acq_purchase_order($po_id)
or return $e->die_event;
- my $li = $e->retrieve_acq_lineitem($li_id)
- or return $e->die_event;
-
return $e->die_event unless
$e->allowed('CREATE_PURCHASE_ORDER', $po->ordering_agency);
return {success => 0, po => $po, error => 'bad-po-state'};
}
- unless ($li->state =~ /new|order-ready|pending-order/) {
- $e->rollback;
- return {success => 0, li => $li, error => 'bad-li-state'};
+ my $lis;
+
+ if (ref $li_id eq "ARRAY") {
+ $li_id = [ map { int($_) } @$li_id ];
+ return $e->die_event(new OpenILS::Event("BAD_PARAMS")) unless @$li_id;
+
+ $lis = $e->search_acq_lineitem({id => $li_id})
+ or return $e->die_event;
+ } else {
+ my $li = $e->retrieve_acq_lineitem(int($li_id))
+ or return $e->die_event;
+ $lis = [$li];
+ }
+
+ foreach my $li (@$lis) {
+ if ($li->state !~ /new|order-ready|pending-order/ or
+ $li->purchase_order) {
+ $e->rollback;
+ return {success => 0, li => $li, error => 'bad-li-state'};
+ }
+
+ $li->provider($po->provider);
+ $li->purchase_order($po_id);
+ $li->state('pending-order');
+ update_lineitem($mgr, $li) or return $e->die_event;
}
- $li->provider($po->provider);
- $li->purchase_order($po_id);
- $li->state('pending-order');
- update_lineitem($mgr, $li) or return $e->die_event;
-
$e->commit;
return {success => 1};
}
--- /dev/null
+ <div dojoType="dijit.Dialog" jsId='addToPoDialog'>
+ <script type="dojo/connect" event="execute">
+ var dia = this;
+ var poId = addToPoInput.attr('value');
+ if (!poId) return false;
+
+ var liId = dia._get_li();
+ console.log("adding li " + liId + " to PO " + poId);
+
+ fieldmapper.standardRequest(
+ ['open-ils.acq',
+ 'open-ils.acq.purchase_order.add_lineitem'],
+ { async: true,
+ params: [openils.User.authtoken, poId, liId],
+ oncomplete: function(r) {
+ if ((r = openils.Util.readResponse(r))) {
+ if (r.success) {
+ location.href = oilsBasePath +
+ '/acq/po/view/' + poId;
+ } else {
+ if (r.error == 'bad-po-state') {
+ alert(localeStrings.ADD_LI_TO_PO_BAD_PO_STATE);
+ } else if (r.error == 'bad-li-state') {
+ alert(localeStrings.ADD_LI_TO_PO_BAD_LI_STATE);
+ }
+ }
+ }
+ dia.hide();
+ }
+ }
+ );
+ </script>
+ <script type="dojo/connect" event="onShow">
+ if (!window._already_addToPo_onShow) {
+ var dia = this;
+ openils.Util.registerEnterHandler(
+ addToPoInput.domNode,
+ function() { dia.execute(); }
+ );
+ window._already_addToPo_onShow = true;
+ }
+ </script>
+ <table class='dijitTooltipTable'>
+ <tr>
+ <td><label>[% l('Enter the PO Name: ') %]</label></td>
+ <td>
+ <input jsId="addToPoInput" dojoType="openils.widget.PCrudAutocompleteBox" fmclass="acqpo" searchAttr="name" name="target_po" />
+
+ </td>
+ </tr>
+ <tr>
+ <td colspan='2' align='center'>
+ <span dojoType='dijit.form.Button' type="submit">[% l('Save') %]</span>
+ </td>
+ </tr>
+ </table>
+ </div>
<div id='acq-lit-table-container'>
<div id='acq-lit-table-div' class='hidden'>
- <!-- Lineitem (bib record) list -->
+ <!-- Line Item (bib record) list -->
<table id='acq-lit-table' class='oils-generic-table'>
<thead>
<tr>
<td>
<span>
<select id="acq-lit-li-actions-selector">
+ <!-- mask meanings:
+ pl: selection list
+ po: pending purchase order
+ ao: activated purchase order
+ gs: general search
+ vp: view/place orders
+ fs: MARC federated search
+
+ * for all, otherwise combine with |
+ -->
<option mask='*' value='_'>[% l('--Actions--') %]</option>
- <option mask='sr' value='save_picklist'>[% l('Save Items To Selection List') %]</option>
- <option mask='pl' value='selector_ready'>[% l('Mark Ready for Selector') %]</option>
- <option mask='pl' value='order_ready'>[% l('Mark Ready for Order') %]</option>
- <option mask='*' value='delete_selected'>[% l('Delete Selected Items') %]</option>
- <option mask='*' value='add_brief_record'>[% l('Add Brief Record') %]</option>
- <option mask='*' value='export_attr_list'>[% l('Export Single Attribute List') %]</option>
- <option mask='*' value='batch_apply_funds'>[% l('Apply Funds to Selected Items') %]</option>
- <option mask='po' value='' disabled='disabled'>[% l('----PO----') %]</option>
- <option mask='sr|pl' value='create_order'>[% l('Create Purchase Order') %]</option>
+ <option mask='pl|gs|vp|fs' value='save_picklist'>[% l('Save Items To Selection List') %]</option>
+ <option mask='pl|gs|vp' value='selector_ready'>[% l('Mark Ready for Selector') %]</option>
+ <option mask='pl|gs|vp' value='order_ready'>[% l('Mark Ready for Order') %]</option>
+ <option mask='pl|po|gs|vp' value='delete_selected'>[% l('Delete Selected Items') %]</option>
+ <option mask='pl|po' value='add_brief_record'>[% l('Add Brief Record') %]</option>
+ <option mask='pl|po|ao|gs' value='export_attr_list'>[% l('Export Single Attribute List') %]</option>
+ <option mask='*' value='' disabled='disabled'>[% l('----PO----') %]</option>
+ <option mask='pl|gs|vp|fs' value='create_order'>[% l('Create Purchase Order') %]</option>
+ <option mask='pl|gs|vp|fs' value='add_to_order'>[% l('Add to Purchase Order') %]</option>
+ <option mask='po|ao' value='print_po'>[% l('Print Purchase Order') %]</option>
+ <option mask='po|ao' value='po_history'>[% l('View PO History') %]</option>
<option mask='po' value='create_assets'>[% l('Load Bibs and Items') %]</option>
- <option mask='po' value='cancel_lineitems'>[% l('Cancel Selected Lineitems') %]</option>
- <option mask='po' value='change_claim_policy'>[% l('Change Claim Policy') %]</option>
- <option mask='po' value='receive_po' id='receive_po' disabled='disabled'>[% l('Mark Purchase Order as Received') %]</option>
- <option mask='po' value='rollback_receive_po' id='rollback_receive_po' disabled='disabled'>[% l('Un-Receive Purchase Order') %]</option>
- <option mask='po' value='print_po'>[% l('Print Purchase Order') %]</option>
- <option mask='po' value='po_history'>[% l('View PO History') %]</option>
- <option mask='po' value='batch_create_invoice'>[% l('Create Invoice From Selected Lineitems') %]</option>
- <option mask='po' value='batch_link_invoice'>[% l('Link To Invoice for Selected Lineitems') %]</option>
+ <!-- <option mask='' value='batch_apply_funds'>[% l('Apply Funds to Selected Items') %]</option> XXX moving to batch updater -->
+ <option mask='ao|gs|vp' value='cancel_lineitems'>[% l('Cancel Selected Line Items') %]</option>
+ <option mask='po|ao|gs|vp' value='apply_claim_policy'>[% l('Apply Claim Policy to Selected Line Items') %]</option><!-- can the functionality desired here be covered by the next thing? -->
+ <option mask='ao|gs|vp' value='receive_lineitems' id='receive_lineitems' disabled='disabled'>[% l('Mark Selected Line Items as Received') %]</option>
+ <option mask='ao|gs|vp' value='rollback_receive_lineitems' id='rollback_receive_lineitems' disabled='disabled'>[% l('Un-Receive Selected Line Items') %]</option>
+ <option mask='ao|gs|vp' value='batch_create_invoice'>[% l('Create Invoice From Selected Line Items') %]</option>
+ <option mask='ao|gs|vp' value='batch_link_invoice'>[% l('Link Selected Line Items to Invoice') %]</option>
</select>
<span id="acq-lit-export-attr-holder" class="hidden">
<input dojoType="dijit.form.FilteringSelect" id="acq-lit-export-attr" jsId="acqLitExportAttrSelector" labelAttr="description" searchAttr="description" />
<span name='pl' class='hidden'> | <a title='[% l('Select List') %]' name='pl_link' href='javascript:void(0);'>❖ </a></span>
<span name='po' class='hidden'> | <a title='[% l('Purchase Order') %]' name='po_link' href='javascript:void(0);'>⌘ </a></span>
<span name="show_requests"> | <a title='[% l('Patron Requests') %]' name="show_requests_link" href="javascript:void(0);">[% l('requests') %]</a></span>
+ <span name="invoices_span" class="hidden"> | <a href="javascript:void(0);" name="invoices_link">[% l("view invoice(s)") %] </a></span>
+ <span name="claim_policy" class="hidden"> | [% l("claim policy:") %] <span name="claim_policy_name"></span></span>
<span name='pro' class='hidden'> | <a title='[% l('Provider') %]' name='pro_link' href='javascript:void(0);'>⍟ </a></span>
<span name='queue' class='hidden'> | <a title='[% l('Import Queue') %]' name='queue_link' href='javascript:void(0);'>[% l('➬ queue') %]</a></span>
</td>
<td>
<select name='actions'>
<option name='action_none'>[% l('-- Actions --') %]</option>
- <option name='action_mark_recv' disabled='disabled'>[% l('Mark Received') %]</option>
- <option name='action_mark_unrecv' disabled='disabled'>[% l('Un-Receive') %]</option>
<option name='action_update_barcodes'>[% l('Update Barcodes') %]</option>
<option name='action_holdings_maint'>[% l('Holdings Maint.') %]</option>
- <option name='action_new_invoice' disabled='disabled'>[% l('New Invoice') %]</option>
- <option name='action_link_invoice' disabled='disabled'>[% l('Link to Invoice') %]</option>
- <option name='action_view_invoice' disabled='disabled'>[% l('View Invoice(s)') %]</option>
- <option name='action_view_claim_policy'>[% l('Apply Claim Policy') %]</option>
<option name='action_manage_claims'>[% l('Claims') %]</option>
<option name='action_view_history'>[% l('View History') %]</option>
</select>
</table>
</div>
- <!-- Bib record / Lineitem info table -->
+ <!-- Bib record / Line Item info table -->
[% INCLUDE "acq/common/info.tt2" which = "Lit" %]
- <!-- Lineitem notes table -->
+ <!-- Line Item notes table -->
[% INCLUDE "acq/common/notes.tt2" which = "Lit" %]
<!-- Copies table -->
<tbody style='font-weight:bold;'>
<tr>
<td style='margin-top:30px;'>[% l('Owning Branch') %]</td>
- <td>[% l('Shelving Location') %]</td>
+ <td>[% l('Copy Location') %]</td>
<td>[% l('Collection Code') %]</td>
<td>[% l('Fund') %]</td>
<td>[% l('Circ Modifier') %]</td>
<tbody style='font-weight:bold;'>
<tr>
<td style='margin-top:30px;'>[% l('Owning Branch') %]</td>
- <td>[% l('Shelving Location') %]</td>
+ <td>[% l('Copy Location') %]</td>
<td>[% l('Collection Code') %]</td>
<td>[% l('Fund') %]</td>
<td>[% l('Circ Modifier') %]</td>
<tbody style='font-weight:bold;'>
<tr>
<td style='margin-top:30px;'>[% l('Owning Branch') %]</td>
- <td>[% l('Shelving Location') %]</td>
+ <td>[% l('Copy Location') %]</td>
<td>[% l('Circ Modifier') %]</td>
<td>[% l('Callnumber') %]</td>
<td>[% l('Barcode') %]</td>
<div jsId="acqLitLinkInvoiceDialog" dojoType="dijit.Dialog">
[% INCLUDE "acq/common/inv_dialog.tt2" which = "li" %]
</div>
+ [% INCLUDE "acq/common/add_to_po.tt2" %]
</div>
<td><input id="acq-lit-po-prepay" name="prepayment_required" dojoType="dijit.form.CheckBox"/></td>
</tr>
<tr>
- <td>[% l('All Lineitems') %]</td>
+ <td>[% l('All Line Items') %]</td>
<td><input checked='checked' name='create_from' value='all' dojoType='dijit.form.RadioButton'/></td>
</tr>
<tr>
- <td>[% l('Selected Lineitems') %]</td>
+ <td>[% l('Selected Line Items') %]</td>
<td><input name='create_from' value='selected' dojoType='dijit.form.RadioButton'/></td>
</tr>
<tr>
/><label for="select_all">[% l(' Select All') %]</label>
</th>
<th>[% l('Owning Branch') %]</th>
- <th>[% l('Shelving Location') %]</th>
+ <th>[% l('Copy Location') %]</th>
<th>[% l('Collection Code') %]</th>
<th>[% l('Fund') %]</th>
<th>[% l('Circ Modifier') %]</th>
<thead id='acq-invoice-entry-thead' class='hidden'>
<tr>
<th colspan='2'>[% l('Title Details') %]</th>
- <th class='acq-invoice-center-col'>[% l('# Invoiced / # Paid') %]</th>
+ <th class='acq-invoice-center-col'>[% l('# Invoiced / # Paid') %]</th>
<th class='acq-invoice-center-col'>[% l('Billed') %]</th>
- <th class='acq-invoice-paid-per-copy-col'>[% l('Per Copy') %]</th>
+ <th class='acq-invoice-paid-per-copy-col' style="white-space: normal">[% l('Per Copy') %]</th>
<th class='acq-invoice-paid-col'>[% l('Paid') %]</th>
<th class='acq-invoice-center-col hide-complete'>[% l('Detach') %]</th>
</tr>
</tr>
</tbody>
<!-- acq.invoice_item -->
- <thead>
+ <thead id="acq-invoice-item-thead">
<tr>
<th>[% l('Charge Type') %]</th>
<th class='acq-invoice-center-col'>[% l('Fund') %]</th>
- <th>[% l('Title/Description') %]</th>
+ <th>[% l('Title / Description') %]</th>
<th class='acq-invoice-center-col'>[% l('Billed') %]</th>
<th/>
<th class='acq-invoice-paid-col'>[% l('Paid') %]</th>
</div>
<div class="hidden">
- <div dojoType="dijit.Dialog" jsId='addToPoDialog'>
- <table class='dijitTooltipTable'>
- <tr>
- <td><label>[% l('Enter the PO #: ') %]</label></td>
- <td><input jsId='addToPoInput' dojoType="dijit.form.TextBox" /></td>
- </tr>
- <tr>
- <td colspan='2' align='center'>
- <button dojoType='dijit.form.Button' jsId='addToPoSave' type="submit">[% l('Save') %]</button>
- </td>
- </tr>
- </table>
- </div>
+ [% INCLUDE "acq/common/add_to_po.tt2" %]
</div>
</div>
[% INCLUDE "acq/common/info.tt2" which = "Related" %]
[% INCLUDE "acq/common/li_table.tt2" %]
+<script type="text/javascript" src="[% ctx.media_prefix %]/js/ui/default/acq/common/add_to_po.js"></script>
<script type="text/javascript" src="[% ctx.media_prefix %]/js/ui/default/acq/lineitem/related.js"></script>
[% END %]
<div id="acq-po-item-table-i-am-empty" class="hidden">
<em>[% l('There are no miscellanea attached to this purchase order.') %]</em>
</div>
- <div id="acq-po-item-table-controls">
+ <div id="acq-po-item-table-controls" class="hidden">
<button id="acq-po-item-table-new-charge">[% l('New Charge') %]</button>
<button id="acq-po-item-table-save-new">[% l('Save New Charges') %]</button>
</div>
<th>[% l('Activatable?') %]</th>
<td>
<span id="acq-po-activate-checking"></span>
- <a class="hidden" id="acq-po-activate-link" href="javascript:void(0);" onclick="activatePo()">[% l('Activate Order') %]</a>
+ <span class="hidden" id="acq-po-activate-link"><span dojoType="dijit.form.Button" onClick="activatePo()" jsId="activatePoButton">[% l('Activate Order') %]</span></span>
</td>
</tr>
<tr>
<tr>
<th>[% l('Total Encumbered') %]</th>
<td>[% l('$[_1]', '<span id="acq-po-view-total-enc"></span>') %]</td>
- <th>[% l('Invoicing') %]</th>
+ <th id="acq-po-invoice-label" class="hidden">[% l('Invoicing') %]</th>
<td id="acq-po-invoice-stuff" class="hidden">
<button dojoType="dijit.form.Button"
id="acq-po-view-invoice-link">
<button dojoType='dijit.form.Button' id='acq-po-return-to-invoice-button'>[% l('↖ Return to Invoice') %]</button>
</div>
</td>
- <th>[% l('Allow activation with <br/> zero-copy lineitems') %]</th>
- <td><input type='checkbox' id='acq-po-activate-zero-copies' onclick='checkCouldActivatePo()'/></td>
+ <th class="hidden" id="acq-po-zero-activate-label">[% l('Allow activation with <br/> zero-copy lineitems') %]</th>
+ <td class="hidden" id="acq-po-zero-activate"><input type='checkbox' id='acq-po-activate-zero-copies' onclick='checkCouldActivatePo()'/></td>
</tr>
<tr>
<td colspan='3'>
<tr>
<th></th>
<th>[% l('Owning Library') %]</th>
- <th>[% l('Shelving Location') %]</th>
+ <th>[% l('Copy Location') %]</th>
<th>[% l('Item Count') %]</th>
<th></th>
</tr>
<option value='fund_code'>[% l('Fund Code') %]</option>
<option value='circ_modifier'>[% l('Circ Modifier') %]</option>
<option value='note'>[% l('Note') %]</option>
- <option value='copy_location'>[% l('Shelving Location') %]</option>
+ <option value='copy_location'>[% l('Copy Location') %]</option>
<option value='barcode'>[% l('Barcode') %]</option>
<option value='collection_code'>[% l('Collection Code') %]</option>
</select>
.acq-invoice-paid-col {background : #E0E0E0; text-align: center;}
.acq-invoice-center-col { text-align: center; }
.acq-invoice-money { width: 7em; }
+#acq-invoice-entry-thead th { white-space: nowrap; }
+#acq-invoice-item-thead th { white-space: nowrap; }
.acq-lineitem-summary { font-weight: bold; }
.acq-lineitem-summary-extra { padding-left: 10px; }
{
"CREATE_PO_ASSETS_CONFIRM" : "This will create bibliographic, call number, and copy records for this purchase order in the ILS.\n\nContinue?",
"ROLLBACK_PO_RECEIVE_CONFIRM" : "This will rollback receipt of all copies for this purchase order.\n\nContinue?",
+ "ROLLBACK_LI_RECEIVE_CONFIRM" : "This will rollback receipt of selected line items from this purchase order.\n\nContinue?",
"XUL_RECORD_DETAIL_PAGE" : "Record Details",
"DELETE_LI_COPIES_CONFIRM" : "This will delete the last ${0} copies in the table. Proceed?",
"NO_PO_RESULTS" : "No results",
"COPIES_TO_RECEIVE": "Number of copies to receive: ",
"CREATE_PO_INVALID": "A purchase order must have an ordering agency and a provider.",
"INVOICE_COPY_COUNT_INFO": "Copies received on this invoice: ${0} out of ${1}.",
- "INVOICE_IDENT_COLLIDE": "There is already an invoice in the system with the given combination of 'Vendor Invoice ID' and 'Provider,' which is not allowed."
+ "INVOICE_IDENT_COLLIDE": "There is already an invoice in the system with the given combination of 'Vendor Invoice ID' and 'Provider,' which is not allowed.",
+ "NEW_INVOICE": "New Invoice"
}
var join = (idx == 0) ? '?' : '&';
path += join + "attach_" + self.which + "=" + id;
});
- location.href = path;
+ if (openils.XUL.isXUL()) {
+ openils.XUL.newTabEasy(
+ path,
+ /* tab title */ dojo.string.substitute(
+ localeStrings.INVOICE_NUMBER, [self.inv.inv_ident()]
+ ),
+ null,
+ true /* <browser> wrapper */
+ );
+ } else {
+ location.href = path;
+ }
};
this.which = which;
dojo.require('dijit.form.Textarea');
dojo.require('dijit.Tooltip');
dojo.require('dijit.ProgressBar');
+dojo.require('dojox.timing.doLater');
dojo.require('openils.acq.Lineitem');
dojo.require('openils.acq.PO');
dojo.require('openils.acq.Picklist');
}
};
+ this.enableActionsDropdownOptions = function(mask) {
+ /* 'mask' is probably a minomer the way I'm using it, but it needs to
+ * be one of pl,po,ao,gs,vp, or fs. */
+ dojo.query("option", "acq-lit-li-actions-selector").forEach(
+ function(option) {
+ var opt_mask = dojo.attr(option, "mask");
+
+ /* For each <option> element, an empty or non-existent mask
+ * attribute, a mask attribute of "*", or a mask attribute that
+ * matches this method's argument should result in that
+ * option's being enabled. */
+ dojo.attr(
+ option, "disabled", !(
+ !opt_mask ||
+ opt_mask == "*" ||
+ opt_mask.search(mask) != -1
+ )
+ );
+ }
+ );
+ };
+
/*
* Ensures this.focusLineitem is in view and causes a brief
* border around the lineitem to come to life then fade.
};
this.setClaimPolicyControl = function(li, row) {
- if (!self.claimPolicyPicker) {
- self.claimPolicyPicker = true; /* prevents a race condition */
+ if (!self._claimPolicyPickerLoading) {
+ self._claimPolicyPickerLoading = true;
+
new openils.widget.AutoFieldWidget({
"parentNode": "acq-lit-li-claim-policy",
"fmClass": "acqclp",
"selfReference": true,
"dijitArgs": {"required": true}
- }).build(function(w) { self.claimPolicyPicker = w; });
+ }).build(
+ function(w) { self.claimPolicyPicker = w; }
+ );
}
- if (!row) row = this._findLiRow(li);
-
- var actViewPolicy = nodeByName("action_view_claim_policy", row);
- if (li.claim_policy())
- actViewPolicy.innerHTML = localeStrings.CHANGE_CLAIM_POLICY;
-
- if (!actViewPolicy.onclick) {
- actViewPolicy.onclick = function() {
- if (li.claim_policy())
- self.claimPolicyPicker.attr("value", li.claim_policy());
- liClaimPolicyDialog.show();
- liClaimPolicySave.onClick = function() {
- self.changeClaimPolicy(
- [li], self.claimPolicyPicker.attr("value"),
- function() {
- self.setClaimPolicyControl(li, row);
- self.reconsiderClaimControl(li, row);
- liClaimPolicyDialog.hide();
- }
- );
- }
- };
+ /* dojox.timing.doLater() is the best thing ever. Resource not yet
+ * ready? Just repeat my whole method when it is. */
+ if (dojox.timing.doLater(self.claimPolicyPicker)) {
+ return;
+ } else {
+ if (!row)
+ row = self._findLiRow(li);
+
+ if (li.claim_policy()) {
+ /* This Dojo data dance is necessary to get a whole fieldmapper
+ * object based on a claim policy ID, since we alreay have the
+ * widget thing loaded with all that data, and can thereby
+ * avoid another request to the server. */
+ self.claimPolicyPicker.store.fetchItemByIdentity({
+ "identity": li.claim_policy(),
+ "onItem": function(a) {
+ var policy = (new acqclp()).fromStoreItem(a);
+ var span = nodeByName("claim_policy", row);
+ var inner = nodeByName("claim_policy_name", row);
+
+ openils.Util.show(span, "inline");
+ inner.innerHTML = policy.name();
+ },
+ "onError": function(e) {
+ console.error(e);
+ }
+ });
+ } else {
+ openils.Util.hide(nodeByName("claim_policy", row));
+ nodeByName("claim_policy_name", row).innerHTML = "";
+ }
}
};
this.updateLiState = function(li, row) {
if (!row) row = this._findLiRow(li);
- var actReceive = nodeByName("action_mark_recv", row);
- var actUnRecv = nodeByName("action_mark_unrecv", row);
var actUpdateBarcodes = nodeByName("action_update_barcodes", row);
var actHoldingsMaint = nodeByName("action_holdings_maint", row);
- var actNewInvoice = nodeByName('action_new_invoice', row);
- var actLinkInvoice = nodeByName('action_link_invoice', row);
- var actViewInvoice = nodeByName('action_view_invoice', row);
// always allow access to LI history
nodeByName('action_view_history', row).onclick =
openils.Util.addCSSClass(row, "oils-acq-li-state-" + li.state());
// Expose invoice actions for any lineitem that is linked to a PO
- if( li.purchase_order() ) {
-
- actNewInvoice.disabled = false;
- actLinkInvoice.disabled = false;
- actViewInvoice.disabled = false;
-
- actNewInvoice.onclick = function() {
- location.href = oilsBasePath + '/acq/invoice/view?create=1&attach_li=' + li.id();
- nodeByName("action_none", row).selected = true;
- };
-
- actLinkInvoice.onclick = function() {
- if (!self.invoiceLinkDialogManager) {
- self.invoiceLinkDialogManager =
- new InvoiceLinkDialogManager("li");
- }
- self.invoiceLinkDialogManager.target = li;
- acqLitLinkInvoiceDialog.show();
- nodeByName("action_none", row).selected = true;
- };
-
- actViewInvoice.onclick = function() {
- location.href = oilsBasePath +
- "/acq/search/unified?so=" +
- base64Encode({"jub":[{"id": li.id()}]}) +
- "&rt=invoice";
- nodeByName("action_none", row).selected = true;
+ if (li.purchase_order()) {
+ openils.Util.show(nodeByName("invoices_span", row), "inline");
+ var link = nodeByName("invoices_link", row);
+ link.onclick = function() {
+ openils.XUL.newTabEasy(
+ oilsBasePath + "/acq/search/unified?so=" +
+ base64Encode({"jub":[{"id": li.id()}]}) + "&rt=invoice"
+ );
+ return false;
};
}
return; // all done
case "on-order":
- actReceive.disabled = false;
- actReceive.onclick = function() {
- if (self.checkLiAlerts(li.id()))
- self.issueReceive(li);
- nodeByName("action_none", row).selected = true;
- };
break;
case "received":
- actUnRecv.disabled = false;
- actUnRecv.onclick = function() {
- if (confirm(localeStrings.UNRECEIVE_LI))
- self.issueReceive(li, /* rollback */ true);
- nodeByName("action_none", row).selected = true;
- };
break;
}
this._deleteLiList(self.getSelected());
break;
+ case 'add_to_order':
+ addToPoDialog._get_li = dojo.hitch(
+ this,
+ function() { return this.getSelected(false, null, true); }
+ );
+ addToPoDialog.show();
+ break;
+
case 'create_order':
this._loadPOSelect();
acqLitPoCreateDialog.show();
this.batchLinkInvoice();
break;
- case 'receive_po':
- this.receivePO();
+ case 'receive_lineitems':
+ this.receiveSelectedLineitems();
break;
- case 'rollback_receive_po':
- this.rollbackPoReceive();
+ case 'rollback_receive_lineitems':
+ this.rollbackReceiveLineitems();
break;
case 'create_assets':
this.maybeCancelLineitems();
break;
- case "change_claim_policy":
+ case "apply_claim_policy":
var li_list = this.getSelected();
this.claimPolicyPicker.attr("value", null);
liClaimPolicyDialog.show();
if (!liIds.length) return;
var path = oilsBasePath + '/acq/invoice/view?create=1';
dojo.forEach(liIds, function(li, idx) { path += '&attach_li=' + li });
- location.href = path;
+ if (openils.XUL.isXUL())
+ openils.XUL.newTabEasy(path, localeStrings.NEW_INVOICE, null, true);
+ else
+ location.href = path;
};
this.batchLinkInvoice = function(create) {
acqLitLinkInvoiceDialog.show();
};
- this.receivePO = function() {
- if (!this.isPO) return;
+ this.receiveSelectedLineitems = function() {
+ var li_list = this.getSelected();
+
+ for (var i = 0; i < li_list.length; i++) {
+ var li = li_list[i];
- for (var id in this.liCache) {
- /* assumption: liCache reflects exactly the
- * set of LIs that belong to our PO */
- if (this.liCache[id].state() != "received" &&
- !this.checkLiAlerts(id)) return;
+ if (li.state() != "received" &&
+ !this.checkLiAlerts(li.id())) return;
}
this.show('acq-lit-progress-numbers');
+
var self = this;
fieldmapper.standardRequest(
- ['open-ils.acq', 'open-ils.acq.purchase_order.receive'],
+ ['open-ils.acq', 'open-ils.acq.lineitem.receive.batch'],
{ async: true,
- params: [this.authtoken, this.isPO],
+ params: [
+ this.authtoken,
+ li_list.map(function(li) { return li.id(); })
+ ],
onresponse : function(r) {
var resp = openils.Util.readResponse(r);
self._updateProgressNumbers(resp, true);
},
}
);
- }
+ };
this.issueReceive = function(obj, rollback) {
var part =
}
};
- this.rollbackPoReceive = function() {
- if(!this.isPO) return;
- if(!confirm(localeStrings.ROLLBACK_PO_RECEIVE_CONFIRM)) return;
+ this.rollbackReceiveLineitems = function() {
+ if (!confirm(localeStrings.ROLLBACK_LI_RECEIVE_CONFIRM)) return;
+
this.show('acq-lit-progress-numbers');
var self = this;
+
fieldmapper.standardRequest(
- ['open-ils.acq', 'open-ils.acq.purchase_order.receive.rollback'],
+ ['open-ils.acq', 'open-ils.acq.lineitem.receive.rollback.batch'],
{ async: true,
- params: [this.authtoken, this.isPO],
+ params: [this.authtoken, this.getSelected(false, null, true)],
onresponse : function(r) {
var resp = openils.Util.readResponse(r);
self._updateProgressNumbers(resp, true);
},
}
);
- }
+ };
this._updateProgressNumbers = function(resp, reloadOnComplete, onComplete) {
this.vlAgent.handleResponse(resp,
readOnly : invoice && openils.Util.isTrue(invoice.complete()),
dijitArgs : args,
parentNode : nodeByName(field, row)
- })
- )
+ }),
+ function(w) {
+ if (field == "cost_billed") {
+ dojo.connect(
+ w, "onChange", function(value) {
+ var paid = widgetRegistry.acqii[item.id()].amount_paid.widget;
+ if (value && isNaN(paid.attr("value")))
+ paid.attr("value", value);
+ }
+ );
+ }
+ }
+ );
}
);
} else {
dijitArgs.style = 'width:9em;';
}
- if(entry.isnew() && field == 'phys_item_count') {
+ if (entry.isnew() && (field == 'phys_item_count' || field == 'inv_item_count')) {
// by default, attempt to pay for all non-canceled and as-of-yet-un-invoiced items
var count = Number(li.order_summary().item_count() || 0) -
Number(li.order_summary().cancel_count() || 0) -
)
} // if
+ if (field == "cost_billed") {
+ // hooks applied with dojo.connect to dijit events are additive, so there's no conflict between this and what comes next
+ dojo.connect(
+ w, "onChange", function(value) {
+ var paid = widgetRegistry.acqie[entry.id()].amount_paid.widget;
+ if (value && isNaN(paid.attr("value")))
+ paid.attr("value", value);
+ }
+ );
+ }
if(field == 'inv_item_count' || field == 'cost_billed') {
setPerCopyPrice(row, entry);
// update the per-copy count as invoice count and cost billed change
dojo.require("openils.CGI");
dojo.require("openils.PermaCrud");
dojo.require('openils.BibTemplate');
+dojo.require('openils.widget.PCrudAutocompleteBox');
dojo.require('fieldmapper.OrgUtils');
dojo.requireLocalization('openils.acq', 'acq');
}
liTable = new AcqLiTable();
+ liTable.enableActionsDropdownOptions("vp");
liTable.reset();
liTable._isRelatedViewer = true;
prepareButtons();
fetchRelated();
- dojo.connect(addToPoSave, 'onClick', addToPo)
- openils.Util.registerEnterHandler(addToPoInput.domNode, addToPo);
-}
-
-var _addToPoHappened = false;
-function addToPo(args) {
- var poId = addToPoInput.attr('value');
- if (!poId) return false;
- if (_addToPoHappened) return false;
-
- var liId = liTable.getSelected()[0].id();
- console.log("adding li " + liId + " to PO " + poId);
-
- // hmm, addToPo is invoked twice for some reason...
- _addToPoHappened = true;
- fieldmapper.standardRequest(
- ['open-ils.acq', 'open-ils.acq.purchase_order.add_lineitem'],
- { async : true,
- params : [openils.User.authtoken, poId, liId],
- oncomplete : function(r) {
- var resp = openils.Util.readResponse(r);
- if (resp.success) {
- location.href = oilsBasePath + '/acq/po/view/' + poId;
- } else {
- _addToPoHappened = false;
- if (resp.error == 'bad-po-state') {
- alert(localeStrings.ADD_LI_TO_PO_BAD_PO_STATE);
- } else if (resp.error == 'bad-li-state') {
- alert(localeStrings.ADD_LI_TO_PO_BAD_LI_STATE);
- }
- }
- }
- }
- );
-
- addToPoDialog.hide();
- return false; // prevent form submission
+ /* addToPoDialog now requires this function be defined to tell it what
+ * lineitem IDs to add to the PO. Part of making it reusable in more
+ * places. */
+ addToPoDialog._get_li = function() {
+ return liTable.getSelected()[0].id();
+ };
}
openils.Util.addOnLoad(load);
function drawForm() {
liTable = new AcqLiTable();
+ liTable.enableActionsDropdownOptions("fs");
liTable.skipInitialEligibilityCheck = true;
fieldmapper.standardRequest(
new openils.widget.XULTermLoader(
{"parentNode": "acq-frombib-upload", "parseCSV": true}
).build(function(w) { termLoader = w; });
+
liTable = new AcqLiTable();
+ liTable.enableActionsDropdownOptions("vp");
+
pager = new LiTablePager(fetchRecords, liTable);
openils.Util.show("acq-frombib-begin-holder");
function load() {
liTable = new AcqLiTable();
liTable.isPL = plId;
+ liTable.enableActionsDropdownOptions("pl");
+
fieldmapper.standardRequest(
['open-ils.acq', 'open-ils.acq.picklist.retrieve.authoritative'],
{ async: true,
} else {
if (!metaPO) {
metaPO = new AcqLiTable();
+ metaPo.enableActionsDropdownOptions("po");
/* We need to know the width (in cells) of the template row for
* the LI table, and we don't want to hardcode it here. */
openils.Util.show("acq-po-invoice-stuff", "table-cell");
}
+/* renderPo() is the best place to add tests that depend on PO-state
+ * (or simple ordered-or-not? checks) to enable/disable UI elements
+ * across the whole interface. */
function renderPo() {
var po_state = PO.state();
dojo.byId("acq-po-view-id").innerHTML = PO.id();
if(PO.order_date()) {
openils.Util.show('acq-po-activated-on', 'inline');
+ liTable.enableActionsDropdownOptions("ao"); /* activated */
+
dojo.byId('acq-po-activated-on').innerHTML =
dojo.string.substitute(
localeStrings.PO_ACTIVATED_ON, [
openils.Util.timeStamp(PO.order_date(), {formatLength:'short'})
]
);
- if(po_state == "on-order" || po_state == "cancelled") {
- dojo.removeAttr('receive_po', 'disabled');
- } else if(po_state == "received") {
- dojo.removeAttr('rollback_receive_po', 'disabled');
- }
+ /* These are handled another way now */
+// if (po_state == "on-order" || po_state == "cancelled") {
+// dojo.removeAttr('receive_lineitems', 'disabled');
+// } else if(po_state == "received") {
+// dojo.removeAttr('rollback_receive_lineitems', 'disabled');
+// }
+
+ /* cancel widgets only make sense for activate (ordered) POs */
+ makeCancelWidget(
+ dojo.byId("acq-po-view-cancel-reason"),
+ dojo.byId("acq-po-cancel-label")
+ );
+
+ /* likewise for invoice features */
+ openils.Util.show("acq-po-invoice-label", "table-cell");
+ prepareInvoiceFeatures();
+ } else {
+ /* These things only make sense for not-ordered-yet POs */
+
+ liTable.enableActionsDropdownOptions("po");
+
+ openils.Util.show("acq-po-zero-activate-label", "table-cell");
+ openils.Util.show("acq-po-zero-activate", "table-cell");
+
+ openils.Util.show("acq-po-item-table-controls");
}
makePrepayWidget(
dojo.byId("acq-po-view-prepay"),
openils.Util.isTrue(PO.prepayment_required())
);
- makeCancelWidget(
- dojo.byId("acq-po-view-cancel-reason"),
- dojo.byId("acq-po-cancel-label")
- );
// dojo.byId("acq-po-view-notes").innerHTML = PO.notes().length;
poNoteTable.updatePoNotesCount();
}
);
}
-
- prepareInvoiceFeatures();
}
function checkCouldActivatePo() {
var d = dojo.byId("acq-po-activate-checking");
- var a = dojo.byId("acq-po-activate-link");
+ var a = dojo.byId("acq-po-activate-link"); /* <span> not <a> now, but no diff */
d.innerHTML = localeStrings.PO_CHECKING;
var warnings = [];
var stops = [];
if (!(warnings.length || stops.length || other.length)) {
d.innerHTML = localeStrings.PO_COULD_ACTIVATE;
openils.Util.show(a, "inline");
+ activatePoButton.attr("disabled", false);
} else {
if (other.length) {
/* XXX make the textcode part a tooltip one day */
]
);
openils.Util.show(a, "inline");
+ activatePoButton.attr("disabled", false);
}
}
}
}
function activatePo() {
+ activatePoButton.attr("disabled", true);
+
if (openils.Util.isTrue(PO.prepayment_required())) {
- if (!confirm(localeStrings.PREPAYMENT_REQUIRED_REMINDER))
+ if (!confirm(localeStrings.PREPAYMENT_REQUIRED_REMINDER)) {
+ activatePoButton.attr("disabled", false);
return false;
+ }
}
if (PO._warning_hack) {
- if (!confirm(localeStrings.PO_FUND_WARNING_CONFIRM))
+ if (!confirm(localeStrings.PO_FUND_WARNING_CONFIRM)) {
+ activatePoButton.attr("disabled", false);
return false;
+ }
}
liTable.showAssetCreator(activatePoStage2);
{zero_copy_activate : dojo.byId('acq-po-activate-zero-copies').checked}
],
"onresponse": function(r) {
+ progressDialog.hide();
+ activatePoButton.attr("disabled", false);
want_refresh = Boolean(openils.Util.readResponse(r));
},
"oncomplete": function() {
- progressDialog.hide();
if (want_refresh)
location.href = location.href;
}
termManager = new TermManager();
+ var li_table = new AcqLiTable();
+ li_table.enableActionsDropdownOptions("gs");
+
resultManager = new ResultManager(
- new LiTablePager(null, new AcqLiTable()),
+ new LiTablePager(null, li_table),
dijit.byId("acq-unified-po-grid"),
dijit.byId("acq-unified-pl-grid"),
dijit.byId("acq-unified-inv-grid")
--- /dev/null
+Acquisitions Purchase Order Improvements
+========================================
+
+Feature Summary
+---------------
+
+The following features, which primarily affect the user interface layer,
+improve Acquisitions work flows.
+
+ * Avoid double-activation of POs
+ * Disable invoice and cancel options for pending POs
+ * Disable zero-copy checkbox for activated POs
+ * Disable new charges for activated POs
+ * Replace "Shelving Location" with Copy Location
+
+ * Rearranging "actions" drop-down
+ ** More consitency in actions applying to selected lineitems specifically
+ ** Things moved from the per-lineitem dropdown to the main one when
+ sensible.
+ ** Add to PO dialog added
+ ** You can no longer add lineitems to a PO if they're already on one.
+ ** Actions in dropdown now enabled/disabled differently depending on
+ the interface where it appears (PO vs Selection List vs Acq Search etc.)