From: senator Date: Mon, 15 Mar 2010 08:52:29 +0000 (+0000) Subject: Acq: cancel POs, lineitems, or individual copies from the PO interface X-Git-Url: https://old-git.evergreen-ils.org/?a=commitdiff_plain;h=98d6155b637fe1082b27fdafb0e879d106acdc38;p=evergreen%2Fmasslnc.git Acq: cancel POs, lineitems, or individual copies from the PO interface Still needs some adjustments: e.g., you can still "receive" lineitems even when all the individual copies are canceled, and things like that. git-svn-id: svn://svn.open-ils.org/ILS/trunk@15844 dcc99617-32d9-48b4-a31d-7c20da2025e4 --- diff --git a/Open-ILS/examples/fm_IDL.xml b/Open-ILS/examples/fm_IDL.xml index e9dc977c31..6045c2a0a9 100644 --- a/Open-ILS/examples/fm_IDL.xml +++ b/Open-ILS/examples/fm_IDL.xml @@ -5406,7 +5406,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA - + diff --git a/Open-ILS/src/extras/ils_events.xml b/Open-ILS/src/extras/ils_events.xml index e0aa42169f..54682419f6 100644 --- a/Open-ILS/src/extras/ils_events.xml +++ b/Open-ILS/src/extras/ils_events.xml @@ -927,8 +927,12 @@ The lineitem has no price - - + + The object is already canceled. + + + The object is not in a cancelable state. + diff --git a/Open-ILS/src/perlmods/OpenILS/Application/Acq/Financials.pm b/Open-ILS/src/perlmods/OpenILS/Application/Acq/Financials.pm index a56d038452..69e1a8e03d 100644 --- a/Open-ILS/src/perlmods/OpenILS/Application/Acq/Financials.pm +++ b/Open-ILS/src/perlmods/OpenILS/Application/Acq/Financials.pm @@ -893,12 +893,17 @@ sub build_price_summary { sub retrieve_purchase_order_impl { my($e, $po_id, $options) = @_; + # let's just always flesh this if it's there. what the hey. + my $flesh = { + "flesh" => 1, "flesh_fields" => {"acqpo" => ["cancel_reason"]} + }; + $options ||= {}; - my $po = $e->retrieve_acq_purchase_order( - $options->{"flesh_notes"} ? [ - $po_id, {"flesh" => 1, "flesh_fields" => {"acqpo" => ["notes"]}} - ] : $po_id - ) or return $e->event; + if ($options->{"flesh_notes"}) { + push @{$flesh->{"flesh_fields"}->{"acqpo"}}, "notes"; + } + my $po = $e->retrieve_acq_purchase_order([$po_id, $flesh]) + or return $e->event; if($$options{flesh_lineitems}) { diff --git a/Open-ILS/src/perlmods/OpenILS/Application/Acq/Lineitem.pm b/Open-ILS/src/perlmods/OpenILS/Application/Acq/Lineitem.pm index d215e8fcc9..f3975707b5 100644 --- a/Open-ILS/src/perlmods/OpenILS/Application/Acq/Lineitem.pm +++ b/Open-ILS/src/perlmods/OpenILS/Application/Acq/Lineitem.pm @@ -101,6 +101,9 @@ sub retrieve_lineitem_impl { push(@{$flesh->{flesh_fields}->{jub}}, 'lineitem_notes'); $flesh->{flesh_fields}->{acqlin} = ['alert_text']; } + if($$options{"flesh_cancel_reason"}) { + push @{$flesh->{flesh_fields}->{jub}}, "cancel_reason"; + } push(@{$flesh->{flesh_fields}->{jub}}, 'attributes') if $$options{flesh_attrs}; } @@ -109,7 +112,7 @@ sub retrieve_lineitem_impl { if($$options{flesh_li_details}) { my $ops = { flesh => 1, - flesh_fields => {acqlid => []} + 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}; diff --git a/Open-ILS/src/perlmods/OpenILS/Application/Acq/Order.pm b/Open-ILS/src/perlmods/OpenILS/Application/Acq/Order.pm index 77caccb4d8..36bf5ef707 100644 --- a/Open-ILS/src/perlmods/OpenILS/Application/Acq/Order.pm +++ b/Open-ILS/src/perlmods/OpenILS/Application/Acq/Order.pm @@ -2120,52 +2120,348 @@ sub split_purchase_order_by_lineitems { } +sub not_cancelable { + my $o = shift; + (ref $o eq "HASH" and $o->{"textcode"} eq "ACQ_NOT_CANCELABLE"); +} + +__PACKAGE__->register_method( + method => "cancel_purchase_order_api", + api_name => "open-ils.acq.purchase_order.cancel", + signature => { + desc => q/Cancels an on-order purchase order/, + params => [ + {desc => "Authentication token", type => "string"}, + {desc => "PO ID to cancel", type => "number"}, + {desc => "Cancel reason ID", type => "number"} + ], + return => {desc => q/Object describing changed POs, LIs and LIDs + on success; Event on error./} + } +); + +sub cancel_purchase_order_api { + my ($self, $conn, $auth, $po_id, $cancel_reason) = @_; + + 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 + ); + + $cancel_reason = $mgr->editor->retrieve_acq_cancel_reason($cancel_reason) or + return new OpenILS::Event( + "BAD_PARAMS", "note" => "Provide cancel reason ID" + ); + + my $result = cancel_purchase_order($mgr, $po_id, $cancel_reason) or + return $e->die_event; + if (not_cancelable($result)) { # event not from CStoreEditor + $e->rollback; + return $result; + } elsif ($result == -1) { + $e->rollback; + return new OpenILS::Event("ACQ_ALREADY_CANCELED"); + } + + $e->commit or return $e->die_event; + + # XXX create purchase order status events? + return $result; +} + +sub cancel_purchase_order { + my ($mgr, $po_id, $cancel_reason) = @_; + + my $po = $mgr->editor->retrieve_acq_purchase_order($po_id) or return 0; + + # XXX is "cancelled" a typo? It's not correct US spelling, anyway. + # Depending on context, this may not warrant an event. + return -1 if $po->state eq "cancelled"; + + # But this always does. + return new OpenILS::Event( + "ACQ_NOT_CANCELABLE", "note" => "purchase_order $po_id" + ) unless ($po->state eq "on-order" or $po->state eq "pending"); + + return 0 unless + $mgr->editor->allowed("CREATE_PURCHASE_ORDER", $po->ordering_agency); + + $po->state("cancelled"); + $po->cancel_reason($cancel_reason); + + my $li_ids = $mgr->editor->search_acq_lineitem( + {"purchase_order" => $po_id}, {"idlist" => 1} + ); + + my $result = {"li" => {}, "lid" => {}}; + foreach my $li_id (@$li_ids) { + my $li_result = cancel_lineitem($mgr, $li_id, $cancel_reason) + or return 0; + + next if $li_result == -1; # already canceled:skip. + return $li_result if not_cancelable($li_result); # not cancelable:stop. + + # Merge in each LI result (there's only going to be + # one per call to cancel_lineitem). + my ($k, $v) = each %{$li_result->{"li"}}; + $result->{"li"}->{$k} = $v; + + # Merge in each LID result (there may be many per call to + # cancel_lineitem). + while (($k, $v) = each %{$li_result->{"lid"}}) { + $result->{"lid"}->{$k} = $v; + } + } + + # TODO who/what/where/how do we indicate this change for electronic orders? + # TODO return changes to encumbered/spent + # TODO maybe cascade up from smaller object to container object if last + # smaller object in the container has been canceled? + + update_purchase_order($mgr, $po) or return 0; + $result->{"po"} = { + $po_id => {"state" => $po->state, "cancel_reason" => $cancel_reason} + }; + return $result; +} + + __PACKAGE__->register_method( - method => 'cancel_lineitem_api', - api_name => 'open-ils.acq.lineitem.cancel', + method => "cancel_lineitem_api", + api_name => "open-ils.acq.lineitem.cancel", signature => { desc => q/Cancels an on-order lineitem/, params => [ - {desc => 'Authentication token', type => 'string'}, - {desc => 'Lineitem ID to cancel', type => 'number'}, - {desc => 'Cancel Cause ID', type => 'number'} + {desc => "Authentication token", type => "string"}, + {desc => "Lineitem ID to cancel", type => "number"}, + {desc => "Cancel reason ID", type => "number"} ], - return => {desc => '1 on success, Event on error'} + return => {desc => q/Object describing changed LIs and LIDs on success; + Event on error./} + } +); + +__PACKAGE__->register_method( + method => "cancel_lineitem_api", + api_name => "open-ils.acq.lineitem.cancel.batch", + signature => { + desc => q/Batched version of open-ils.acq.lineitem.cancel/, + return => {desc => q/Object describing changed LIs and LIDs on success; + Event on error./} } ); sub cancel_lineitem_api { - my($self, $conn, $auth, $li_id, $cancel_cause) = @_; + my ($self, $conn, $auth, $li_id, $cancel_reason) = @_; - my $e = new_editor(xact=>1, authtoken=>$auth); + my $batched = $self->api_name =~ /\.batch/; + + my $e = new_editor("xact" => 1, "authtoken" => $auth); return $e->die_event unless $e->checkauth; - my $mgr = OpenILS::Application::Acq::BatchManager->new(editor => $e, conn => $conn); + my $mgr = new OpenILS::Application::Acq::BatchManager( + "editor" => $e, "conn" => $conn + ); - my $li = $e->retrieve_acq_lineitem([$li_id, - {flesh => 1, flesh_fields => {jub => [q/purchase_order/]}}]); + $cancel_reason = $mgr->editor->retrieve_acq_cancel_reason($cancel_reason) or + return new OpenILS::Event( + "BAD_PARAMS", "note" => "Provide cancel reason ID" + ); + + my ($result, $maybe_event); - unless( $li->purchase_order and ($li->state eq 'on-order' or $li->state eq 'pending-order') ) { + if ($batched) { + $result = {"li" => {}, "lid" => {}}; + foreach my $one_li_id (@$li_id) { + my $one = cancel_lineitem($mgr, $one_li_id, $cancel_reason) or + return $e->die_event; + if (not_cancelable($one)) { + $maybe_event = $one; + } elsif ($result == -1) { + $maybe_event = new OpenILS::Event("ACQ_ALREADY_CANCELED"); + } else { + my ($k, $v); + if ($one->{"li"}) { + while (($k, $v) = each %{$one->{"li"}}) { + $result->{"li"}->{$k} = $v; + } + } + if ($one->{"lid"}) { + while (($k, $v) = each %{$one->{"lid"}}) { + $result->{"lid"}->{$k} = $v; + } + } + } + } + } else { + $result = cancel_lineitem($mgr, $li_id, $cancel_reason) or + return $e->die_event; + + if (not_cancelable($result)) { + $e->rollback; + return $result; + } elsif ($result == -1) { + $e->rollback; + return new OpenILS::Event("ACQ_ALREADY_CANCELED"); + } + } + + if ($batched and not scalar keys %{$result->{"li"}}) { $e->rollback; - return OpenILS::Event->new('BAD_PARAMS') + return $maybe_event; + } else { + $e->commit or return $e->die_event; + # create_lineitem_status_events should handle array li_id ok + create_lineitem_status_events($mgr, $li_id, "aur.cancelled"); + return $result; } +} + +sub cancel_lineitem { + my ($mgr, $li_id, $cancel_reason) = @_; + my $li = $mgr->editor->retrieve_acq_lineitem([ + $li_id, {"flesh" => 1, "flesh_fields" => {"jub" => ["purchase_order"]}} + ]) or return 0; + + return 0 unless $mgr->editor->allowed( + "CREATE_PURCHASE_ORDER", $li->purchase_order->ordering_agency + ); + + # Depending on context, this may not warrant an event. + return -1 if $li->state eq "cancelled"; + + # But this always does. + return new OpenILS::Event( + "ACQ_NOT_CANCELABLE", "note" => "lineitem $li_id" + ) unless ( + $li->purchase_order and ( + $li->state eq "on-order" or $li->state eq "pending-order" + ) + ); + + $li->state("cancelled"); + $li->cancel_reason($cancel_reason); + + my $lid_ids = $mgr->editor->search_acq_lineitem_detail( + {"lineitem" => $li_id}, {"idlist" => 1} + ); + + my $result = {"lid" => {}}; + foreach my $lid_id (@$lid_ids) { + my $lid_result = cancel_lineitem_detail($mgr, $lid_id, $cancel_reason) + or return 0; - return $e->die_event unless - $e->allowed('CREATE_PURCHASE_ORDER', $li->purchase_order->ordering_agency); + next if $lid_result == -1; # already canceled: just skip it. + return $lid_result if not_cancelable($lid_result); # not cxlable: stop. - $li->state('cancelled'); + # Merge in each LID result (there's only going to be one per call to + # cancel_lineitem_detail). + my ($k, $v) = each %{$lid_result->{"lid"}}; + $result->{"lid"}->{$k} = $v; + } # TODO delete the associated fund debits? - # TODO add support for cancel reasons # TODO who/what/where/how do we indicate this change for electronic orders? - update_lineitem($mgr, $li) or return $e->die_event; - $e->commit; + update_lineitem($mgr, $li) or return 0; + $result->{"li"} = { + $li_id => { + "state" => $li->state, + "cancel_reason" => $cancel_reason + } + }; + return $result; +} - $conn->respond_complete($li); - create_lineitem_status_events($mgr, $li_id, 'aur.cancelled'); - return undef; + +__PACKAGE__->register_method( + method => "cancel_lineitem_detail_api", + api_name => "open-ils.acq.lineitem_detail.cancel", + signature => { + desc => q/Cancels an on-order lineitem detail/, + params => [ + {desc => "Authentication token", type => "string"}, + {desc => "Lineitem detail ID to cancel", type => "number"}, + {desc => "Cancel reason ID", type => "number"} + ], + return => {desc => q/Object describing changed LIDs on success; + Event on error./} + } +); + +sub cancel_lineitem_detail_api { + my ($self, $conn, $auth, $lid_id, $cancel_reason) = @_; + + 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 + ); + + $cancel_reason = $mgr->editor->retrieve_acq_cancel_reason($cancel_reason) or + return new OpenILS::Event( + "BAD_PARAMS", "note" => "Provide cancel reason ID" + ); + + my $result = cancel_lineitem_detail($mgr, $lid_id, $cancel_reason) or + return $e->die_event; + + if (not_cancelable($result)) { + $e->rollback; + return $result; + } elsif ($result == -1) { + $e->rollback; + return new OpenILS::Event("ACQ_ALREADY_CANCELED"); + } + + $e->commit or return $e->die_event; + + # XXX create lineitem detail status events? + return $result; } +sub cancel_lineitem_detail { + my ($mgr, $lid_id, $cancel_reason) = @_; + my $lid = $mgr->editor->retrieve_acq_lineitem_detail([ + $lid_id, { + "flesh" => 2, + "flesh_fields" => { + "acqlid" => ["lineitem"], "jub" => ["purchase_order"] + } + } + ]) or return 0; + + # Depending on context, this may not warrant an event. + return -1 if $lid->cancel_reason; + + # But this always does. + return new OpenILS::Event( + "ACQ_NOT_CANCELABLE", "note" => "lineitem_detail $lid_id" + ) unless ( + (not $lid->recv_time) and + $lid->lineitem and + $lid->lineitem->purchase_order and ( + $lid->lineitem->state eq "on-order" or + $lid->lineitem->state eq "pending-order" + ) + ); + + return 0 unless $mgr->editor->allowed( + "CREATE_PURCHASE_ORDER", + $lid->lineitem->purchase_order->ordering_agency + ); + + $lid->cancel_reason($cancel_reason); + + # TODO who/what/where/how do we indicate this change for electronic orders? + + # XXX LIDs don't have either an editor or a edit_time field. Should we + # update these on the LI when we alter an LID? + $mgr->editor->update_acq_lineitem_detail($lid) or return 0; + return {"lid" => {$lid_id => {"cancel_reason" => $cancel_reason}}}; +} + + __PACKAGE__->register_method ( method => 'user_requests', api_name => 'open-ils.acq.user_request.retrieve.by_user_id', diff --git a/Open-ILS/web/js/dojo/openils/acq/nls/acq.js b/Open-ILS/web/js/dojo/openils/acq/nls/acq.js index 7567496f53..fdb5b3958d 100644 --- a/Open-ILS/web/js/dojo/openils/acq/nls/acq.js +++ b/Open-ILS/web/js/dojo/openils/acq/nls/acq.js @@ -27,5 +27,10 @@ 'ITS_YOU': "You", 'JUST_NOW': "Just now", 'EXPLAIN_DFA_MGMT': "Remove record of this distribution formula usage?", - 'VENDOR_PUBLIC': "VENDOR PUBLIC" + 'VENDOR_PUBLIC': "VENDOR PUBLIC", + 'PO_CANCEL_CONFIRM': "Are you SURE you want to cancel this purchase order?", + 'LI_CANCEL_CONFIRM': "Are you SURE you want to cancel this line item?", + 'LID_CANCEL_CONFIRM': "Are you SURE you want to cancel this copy?", + 'CANCEL_REASON': "Cancel reason", + 'CANCEL': "Cancel" } diff --git a/Open-ILS/web/js/ui/default/acq/common/li_table.js b/Open-ILS/web/js/ui/default/acq/common/li_table.js index 13398e65c8..0b3275f432 100644 --- a/Open-ILS/web/js/ui/default/acq/common/li_table.js +++ b/Open-ILS/web/js/ui/default/acq/common/li_table.js @@ -252,16 +252,13 @@ function AcqLiTable() { countNode.innerHTML = count; countNode.id = 'acq-lit-copy-count-label-' + li.id(); - // lineitem state - nodeByName('li_state', row).innerHTML = li.state(); // TODO i18n state labels - // lineitem price var priceInput = dojo.query('[name=price]', row)[0]; priceInput.value = li.estimated_unit_price() || ''; priceInput.onchange = function() { self.updateLiPrice(priceInput, li) }; // show either "mark received" or "unreceive" as appropriate - this.updateLiReceivedness(li, row); + this.updateLiState(li, row); if (!skip_final_placement) { self.tbody.appendChild(row); @@ -287,7 +284,8 @@ function AcqLiTable() { nodeByName("notes_count", row).innerHTML = li.lineitem_notes().length; }; - this.updateLiReceivedness = function(li, row) { + /* XXX NOT related to _updateLiState(). rethink */ + this.updateLiState = function(li, row) { if (typeof(row) == "undefined") row = dojo.query('tr[li="' + li.id() + '"]', "acq-lit-tbody")[0]; @@ -295,6 +293,26 @@ function AcqLiTable() { var unrecv_link = nodeByName("unreceive_link", row); var real_copies_link = nodeByName("real_copies_link", row); var holdings_maintenance_link = nodeByName("holdings_maintenance_link", row); + var state_cell = nodeByName("li_state", row); + + if (li.state() == "cancelled") { + var holds_state = dojo.create( + "span", { + "style": "border-bottom: 1px dashed #000;", + "innerHTML": li.state() + }, state_cell, "only" + ); + new dijit.Tooltip( + { + "label": "" + li.cancel_reason().label() + + "
" + li.cancel_reason().description(), + "connectId": [holds_state] + }, dojo.create("span", null, state_cell, "last") + ); + } else { + state_cell.innerHTML = li.state(); // TODO i18n state labels + } + /* handle row coloring for based on LI state */ openils.Util.removeCSSClass(row, /^oils-acq-li-state-/); @@ -322,11 +340,11 @@ function AcqLiTable() { }; // TODO we should allow editing before receipt, in which case the // test should be "if 1 or more real (acp) copies exist - openils.Util.show(real_copies_link); + openils.Util.show(real_copies_link, "inline"); real_copies_link.onclick = function() { self.showRealCopyEditUI(li); } - openils.Util.show(holdings_maintenance_link); + openils.Util.show(holdings_maintenance_link, "inline"); holdings_maintenance_link.onclick = self.generateMakeRecTab( li.eg_bib_id(), 'copy_browser' ); return; } @@ -583,6 +601,7 @@ function AcqLiTable() { params: [self.authtoken, liId, { flesh_attrs: true, + flesh_cancel_reason: true, flesh_li_details: true, flesh_fund_debit: true }], @@ -1219,10 +1238,10 @@ function AcqLiTable() { } ); - this.updateLidReceivedness(copy, row); + this.updateLidState(copy, row); }; - this.updateLidReceivedness = function(copy, row) { + this.updateLidState = function(copy, row) { if (typeof(row) == "undefined") { row = dojo.query( 'tr[copy_id="' + copy.id() + '"]', this.copyTbody @@ -1233,40 +1252,124 @@ function AcqLiTable() { var recv_link = nodeByName("receive", row); var unrecv_link = nodeByName("unreceive", row); var del_link = nodeByName("delete", row); + var cxl_link = nodeByName("cancel", row); + var cxl_reason_link = nodeByName("cancel_reason", row); - if (this.isPO) { + if (copy.cancel_reason()) { openils.Util.hide(del_link.parentNode); + openils.Util.hide(recv_link); + openils.Util.hide(unrecv_link); + openils.Util.hide(cxl_link); + + /* XXX the following may leak memory in a long lived table: dijits may not get destroyed... not positive. revisit. */ + var holds_reason = dojo.create( + "span", { + "style": "border-bottom: 1px dashed #000;", + "innerHTML": "Cancelled" /* XXX [sic] and i18n */ + }, cxl_reason_link, "only" + ); + new dijit.Tooltip( + { + "label": "" + copy.cancel_reason().label() + + "
" + copy.cancel_reason().description(), + "connectId": [holds_reason] + }, dojo.create("span", null, cxl_reason_link, "last") + ); + openils.Util.show(cxl_reason_link, "inline"); + } else if (this.isPO) { + openils.Util.hide(del_link.parentNode); + openils.Util.hide(cxl_reason_link); - /* Avoid showing (un)receive links for virtual copies */ + /* Avoid showing (un)receive links, cancel links, for virt copies */ if (copy.id() > 0) { if(copy.recv_time()) { + openils.Util.hide(cxl_link); openils.Util.hide(recv_link); - openils.Util.show(unrecv_link); + openils.Util.show(unrecv_link, "inline"); unrecv_link.onclick = function() { if (confirm(localeStrings.UNRECEIVE_LID)) self.issueReceive(copy, /* rollback */ true); }; } else { openils.Util.hide(unrecv_link); - openils.Util.show(recv_link); + openils.Util.show(recv_link, "inline"); + openils.Util.show(cxl_link, "inline"); recv_link.onclick = function() { if (self.checkLiAlerts(copy.lineitem())) self.issueReceive(copy); }; + cxl_link.onclick = function() { + self.cancelLid(copy.id()); + }; } } else { + openils.Util.hide(cxl_link); openils.Util.hide(unrecv_link); openils.Util.hide(recv_link); } } else { openils.Util.hide(unrecv_link); openils.Util.hide(recv_link); + openils.Util.hide(cxl_reason_link); del_link.onclick = function() { self.deleteCopy(row) }; openils.Util.show(del_link.parentNode); } } + this.cancelLid = function(lid_id) { + lidCancelDialog._lid_id = lid_id; + openils.Util.show(lidCancelDialog.domNode.parentNode); + lidCancelDialog.show(); + if (!lidCancelDialog._prepared) { + var widget = new openils.widget.AutoFieldWidget({ + "fmField": "cancel_reason", + "fmClass": "acqlid", + "parentNode": dojo.byId("acq-lit-lid-cancel-reason"), + "orgLimitPerms": ["CREATE_PURCHASE_ORDER"], + "forceSync": true + }); + widget.build( + function(w, ww) { + acqLidCancelButton.onClick = function() { + if (w.attr("value")) { + if (confirm(localeStrings.LID_CANCEL_CONFIRM)) { + self._cancelLid( + lidCancelDialog._lid_id, + w.attr("value") + ); + } + lidCancelDialog.hide(); + } + }; + lidCancelDialog._prepared = true; + } + ); + } + }; + + this._cancelLid = function(lid_id, reason) { + fieldmapper.standardRequest( + ["open-ils.acq", "open-ils.acq.lineitem_detail.cancel"], { + "params": [openils.User.authtoken, lid_id, reason], + "async": true, + "onresponse": function(r) { + if (r = openils.Util.readResponse(r)) { + if (r.lid) { + for (var id in r.lid) { + /* actually this should only iterate once */ + self.copyCache[id].cancel_reason( + r.lid[id].cancel_reason + ); + self.updateLidState(self.copyCache[id]); + } + } + } + } + } + ); + }; + this._confirmAlert = function(li, lin) { return confirm( dojo.string.substitute( @@ -1452,6 +1555,12 @@ function AcqLiTable() { location.href = oilsBasePath + '/acq/picklist/brief_record?po=' + this.isPO; else location.href = oilsBasePath + '/acq/picklist/brief_record?pl=' + this.isPL; + + break; + + case "cancel_lineitems": + this.maybeCancelLineitems(); + break; } } @@ -1472,6 +1581,65 @@ function AcqLiTable() { ); } + this.maybeCancelLineitems = function() { + openils.Util.show("acq-lit-cancel-reason", "inline"); + if (!acqLitCancelLineitemsButton._prepared) { + var widget = new openils.widget.AutoFieldWidget({ + "fmField": "cancel_reason", + "fmClass": "jub", + "parentNode": dojo.byId("acq-lit-cancel-reason-selector"), + "orgLimitPerms": ["CREATE_PURCHASE_ORDER"], + "forceSync": true + }); + widget.build( + function(w, ww) { + acqLitCancelLineitemsButton.onClick = function() { + if (w.attr("value")) { + if (confirm(localeStrings.LI_CANCEL_CONFIRM)) { + self._cancelLineitems(w.attr("value")); + } + openils.Util.hide("acq-lit-cancel-reason"); + } + }; + acqLitCancelLineitemsButton._prepared = true; + } + ); + } + }; + + this._cancelLineitems = function(reason) { + var id_list = this.getSelected().map(function(o) { return o.id(); }); + fieldmapper.standardRequest( + ["open-ils.acq", "open-ils.acq.lineitem.cancel.batch"], { + "params": [openils.User.authtoken, id_list, reason], + "async": true, + "onresponse": function(r) { + if (r = openils.Util.readResponse(r)) { + if (r.li) { + for (var id in r.li) { + self.liCache[id].state(r.li[id].state); + self.liCache[id].cancel_reason( + r.li[id].cancel_reason + ); + self.updateLiState(self.liCache[id]); + } + } + if (r.lid && self.copyCache) { + for (var id in r.lid) { + if (self.copyCache[id]) { + self.copyCache[id].cancel_reason( + r.lid[id].cancel_reason + ); + self.updateLidState(self.copyCache[id]); + } + } + } + } + } + } + ); + }; + this.chooseExportAttr = function() { if (!acqLitExportAttrSelector._li_setup) { var self = this; @@ -1590,8 +1758,8 @@ function AcqLiTable() { "params": [this.authtoken, obj.id()], "onresponse": function(r) { self.handleReceive(openils.Util.readResponse(r)); - }, - "oncomplete": function() { progressDialog.hide(); } + progressDialog.hide(); + } } ); }; @@ -1605,7 +1773,7 @@ function AcqLiTable() { for (var li_id in resp.li) { for (var key in resp.li[li_id]) self.liCache[li_id][key](resp.li[li_id][key]); - self.updateLiReceivedness(self.liCache[li_id]); + self.updateLiState(self.liCache[li_id]); } } if (resp.po) { @@ -1616,7 +1784,7 @@ function AcqLiTable() { for (var lid_id in resp.lid) { for (var key in resp.lid[lid_id]) self.copyCache[lid_id][key](resp.lid[lid_id][key]); - self.updateLidReceivedness(self.copyCache[lid_id]); + self.updateLidState(self.copyCache[lid_id]); } } } diff --git a/Open-ILS/web/js/ui/default/acq/picklist/view.js b/Open-ILS/web/js/ui/default/acq/picklist/view.js index a93cd66f5c..cfd6cb98d1 100644 --- a/Open-ILS/web/js/ui/default/acq/picklist/view.js +++ b/Open-ILS/web/js/ui/default/acq/picklist/view.js @@ -78,7 +78,7 @@ function loadLIs() { ['open-ils.acq', 'open-ils.acq.lineitem.picklist.retrieve'], { async: true, params: [openils.User.authtoken, plId, - {flesh_notes:true, flesh_attrs:true, clear_marc:true, offset:plOffset, limit:plLimit}], + {flesh_notes:true, flesh_cancel_reason:true, flesh_attrs:true, clear_marc:true, offset:plOffset, limit:plLimit}], onresponse: function(r) { var li = openils.Util.readResponse(r); if (li) { /* Not every response is an LI (for some reason) */ diff --git a/Open-ILS/web/js/ui/default/acq/po/view_po.js b/Open-ILS/web/js/ui/default/acq/po/view_po.js index d96d0e266f..60c55886db 100644 --- a/Open-ILS/web/js/ui/default/acq/po/view_po.js +++ b/Open-ILS/web/js/ui/default/acq/po/view_po.js @@ -3,6 +3,7 @@ dojo.require('openils.User'); dojo.require('openils.Util'); dojo.require('openils.PermaCrud'); +var pcrud = new openils.PermaCrud(); var PO = null; var liTable; var poNoteTable; @@ -162,6 +163,82 @@ function updatePoState(po_info) { } } +function cancellationUpdater(r) { + var r = openils.Util.readResponse(r); + if (r) { + if (r.po) updatePoState(r.po); + if (r.li) { + for (var id in r.li) { + liTable.liCache[id].state(r.li[id].state); + liTable.liCache[id].cancel_reason(r.li[id].cancel_reason); + liTable.updateLiState(liTable.liCache[id]); + } + } + if (r.lid && liTable.copyCache) { + for (var id in r.lid) { + if (liTable.copyCache[id]) { + liTable.copyCache[id].cancel_reason( + r.lid[id].cancel_reason + ); + liTable.updateLidState(liTable.copyCache[id]); + } + } + } + } +} + +function makeCancelWidget(node, labelnode) { + openils.Util.hide("acq-po-choose-cancel-reason"); + + if (PO.cancel_reason()) { + labelnode.innerHTML = localeStrings.CANCEL_REASON; + node.innerHTML = PO.cancel_reason().description() + " (" + + PO.cancel_reason().label() + ")"; + } else if (["on-order", "pending"].indexOf(PO.state()) == -1) { + dojo.destroy(this.oldTip); + labelnode.innerHTML = ""; + node.innerHTML = ""; + } else { + dojo.destroy(this.oldTip); + labelnode.innerHTML = localeStrings.CANCEL; + node.innerHTML = ""; + if (!acqPoCancelReasonSubmit._prepared) { + var widget = new openils.widget.AutoFieldWidget({ + "fmField": "cancel_reason", + "fmClass": "acqpo", + "parentNode": dojo.byId("acq-po-cancel-reason"), + "orgLimitPerms": ["CREATE_PURCHASE_ORDER"], + "forceSync": true + }); + widget.build( + function(w, ww) { + acqPoCancelReasonSubmit.onClick = function() { + if (w.attr("value")) { + if (confirm(localeStrings.PO_CANCEL_CONFIRM)) { + fieldmapper.standardRequest( + ["open-ils.acq", + "open-ils.acq.purchase_order.cancel"], + { + "params": [ + openils.User.authtoken, + PO.id(), + w.attr("value") + ], + "async": true, + "oncomplete": cancellationUpdater + } + ); + } + } + }; + acqPoCancelReasonSubmit._prepared = true; + } + ); + } + openils.Util.show("acq-po-choose-cancel-reason", "inline"); + } +} + function renderPo() { dojo.byId("acq-po-view-id").innerHTML = PO.id(); dojo.byId("acq-po-view-name").innerHTML = PO.name(); @@ -169,6 +246,10 @@ function renderPo() { dojo.byId("acq-po-view-total-enc").innerHTML = PO.amount_encumbered(); dojo.byId("acq-po-view-total-spent").innerHTML = PO.amount_spent(); dojo.byId("acq-po-view-state").innerHTML = PO.state(); // TODO i18n + makeCancelWidget( + dojo.byId("acq-po-view-cancel-reason"), + dojo.byId("acq-po-cancel-label") + ); dojo.byId("acq-po-view-notes").innerHTML = PO.notes().length; if(PO.state() == "pending") { @@ -208,7 +289,7 @@ function init() { fieldmapper.standardRequest( ['open-ils.acq', 'open-ils.acq.lineitem.search'], { async: true, - params: [openils.User.authtoken, {purchase_order:poId}, {flesh_attrs:true, flesh_notes:true}], +params: [openils.User.authtoken, {purchase_order:poId}, {flesh_attrs:true, flesh_notes:true, flesh_cancel_reason:true}], onresponse: function(r) { liTable.show('list'); liTable.addLineitem(openils.Util.readResponse(r)); @@ -264,7 +345,6 @@ function updatePoName() { var value = prompt('Enter new purchase order name:', PO.name()); // TODO i18n if(!value || value == PO.name()) return; PO.name(value); - var pcrud = new openils.PermaCrud(); pcrud.update(PO, { oncomplete : function(r, cudResults) { var stat = cudResults[0]; diff --git a/Open-ILS/web/opac/locale/en-US/lang.dtd b/Open-ILS/web/opac/locale/en-US/lang.dtd index 9122c68120..0d8c04b11c 100644 --- a/Open-ILS/web/opac/locale/en-US/lang.dtd +++ b/Open-ILS/web/opac/locale/en-US/lang.dtd @@ -688,6 +688,8 @@ + + diff --git a/Open-ILS/web/templates/default/acq/common/li_table.tt2 b/Open-ILS/web/templates/default/acq/common/li_table.tt2 index 563f84c660..554b9fcc12 100644 --- a/Open-ILS/web/templates/default/acq/common/li_table.tt2 +++ b/Open-ILS/web/templates/default/acq/common/li_table.tt2 @@ -22,6 +22,7 @@ + @@ -30,6 +31,10 @@ Export List +