<link field="owning_lib" reltype="has_a" key="id" map="" class="aou"/>
<link field="location" reltype="has_a" key="id" map="" class="acpl"/>
<link field="circ_modifier" reltype="has_a" key="code" map="" class="ccm"/>
- <link field="cancel_reason" reltype="might_have" key="id" map="" class="acqcr"/>
+ <link field="cancel_reason" reltype="has_a" key="id" map="" class="acqcr"/>
</links>
</class>
<desc xml:lang="en-US">The lineitem has no price</desc>
</event>
-
-
+ <event code='10101' textcode='ACQ_ALREADY_CANCELED'>
+ <desc xml:lang="en-US">The object is already canceled.</desc>
+ </event>
+ <event code='10102' textcode='ACQ_NOT_CANCELABLE'>
+ <desc xml:lang="en-US">The object is not in a cancelable state.</desc>
+ </event>
<!-- ================================================================ -->
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}) {
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};
}
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};
}
+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',
'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"
}
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);
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];
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": "<em>" + li.cancel_reason().label() +
+ "</em><br />" + 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-/);
};
// 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;
}
params: [self.authtoken, liId, {
flesh_attrs: true,
+ flesh_cancel_reason: true,
flesh_li_details: true,
flesh_fund_debit: true }],
}
);
- 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
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": "<em>" + copy.cancel_reason().label() +
+ "</em><br />" + 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(
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;
}
}
);
}
+ 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;
"params": [this.authtoken, obj.id()],
"onresponse": function(r) {
self.handleReceive(openils.Util.readResponse(r));
- },
- "oncomplete": function() { progressDialog.hide(); }
+ progressDialog.hide();
+ }
}
);
};
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) {
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]);
}
}
}
['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) */
dojo.require('openils.Util');
dojo.require('openils.PermaCrud');
+var pcrud = new openils.PermaCrud();
var PO = null;
var liTable;
var poNoteTable;
}
}
+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();
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") {
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));
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];
<!ENTITY staff.main.menu.admin.server_admin.acq.accesskey "A">
<!ENTITY staff.main.menu.admin.server_admin.acq.lineitem_alert.label "Line Item Alerts">
<!ENTITY staff.main.menu.admin.server_admin.acq.lineitem_alert.accesskey "L">
+<!ENTITY staff.main.menu.admin.server_admin.acq.cancel_reason.label "Cancel Reasons">
+<!ENTITY staff.main.menu.admin.server_admin.acq.cancel_reason.accesskey "C">
<!ENTITY staff.main.menu.admin.server_admin.booking.label "Booking">
<!ENTITY staff.main.menu.admin.server_admin.booking.accesskey "B">
<option mask='po' value='' disabled='disabled'>----PO----</option>
<option mask='sr|pl' value='create_order'>Create Purchase Order</option>
<option mask='po' value='create_assets'>Load Bibs and Items</option>
+ <option mask='po' value='cancel_lineitems'>Cancel Selected Lineitems</option>
<option mask='po' value='receive_po'>Mark Purchase Order as Received</option>
<option mask='po' value='rollback_receive_po'>Un-Receive Purchase Order</option>
<option mask='po' value='print_po'>Print Purchase Order</option>
<input dojoType="dijit.form.FilteringSelect" id="acq-lit-export-attr" jsId="acqLitExportAttrSelector" labelAttr="description" searchAttr="description" />
<span dojoType="dijit.form.Button" jsId="acqLitExportAttrButton">Export List</span>
</span>
+ <span id="acq-lit-cancel-reason" class="hidden">
+ <span id="acq-lit-cancel-reason-selector"></span>
+ <span dojoType="dijit.form.Button" jsId="acqLitCancelLineitemsButton">Cancel Line Items</span>
+ </span>
</span>
<span id='acq-lit-generic-progress' class='hidden'>
<span dojoType="dijit.ProgressBar" style="width:300px" jsId="litGenericProgress"></span>
</tbody>
</table>
</td>
- <td><a class='hidden' name='real_copies_link' href='javascript:void(0);'>Update Barcodes</a></td>
- <td><a class='hidden' name='holdings_maintenance_link' href='javascript:void(0);'>Holdings Maintenance</a></td>
+ <td><a class='hidden' name='real_copies_link' href='javascript:void(0);'>Update Barcodes</a> <a class='hidden' name='holdings_maintenance_link' href='javascript:void(0);'>Holdings Maintenance</a></td>
<td><a name='receive_link' href='javascript:void(0);'>Mark Received</a><a name='unreceive_link' href='javascript:void(0);'>Un-Receive</a></td>
</td>
<td><a name='copieslink' href='javascript:void(0);'>Copies(<span name='count'>0</span>)</a></td>
<td><div name='cn_label'/></td>
<td><div name='barcode'/></td>
<td><div name='note'/></td>
- <td><a href='javascript:void(0);' name='receive'>Mark Received</a><a href='javascript:void(0);' name='unreceive'>Un-Receive</a></td>
+ <td><a href='javascript:void(0);' name='receive'>Mark Received</a><a href='javascript:void(0);' name='unreceive'>Un-Receive</a> <a href="javascript:void(0);" name='cancel'>Cancel</a><span class="hidden" name='cancel_reason'></span></td>
<td><div name='delete' dojoType='dijit.form.Button' style='color:red;'>X</div></td>
</tr>
</tbody>
</table>
</div>
+ <div class="hidden">
+ <div dojoType="dijit.Dialog" jsId="lidCancelDialog">
+ <label for="acq-lit-lid-cancel-reason">Reason:</label>
+ <span id="acq-lit-lid-cancel-reason"></span>
+ <span dojoType="dijit.form.Button"
+ jsId="acqLidCancelButton">Cancel Copy</span>
+ </div>
+ </div>
+
<div dojoType="dijit.Dialog" jsId='acqLitChangeLiStateDialog'>
<table class='dijitTooltipTable'>
<tr>
<tr><td>Total Spent</td><td>$<span id='acq-po-view-total-spent'/></td></tr>
<tr><td>Status</td><td><span id='acq-po-view-state'/></td></tr>
<tr>
+ <td id="acq-po-cancel-label"></td>
+ <td>
+ <span id="acq-po-view-cancel-reason"></span>
+ <span id="acq-po-choose-cancel-reason" class="hidden">
+ <span dojoType="dijit.form.DropDownButton">
+ <span>Cancel order</span>
+ <span dojoType="dijit.TooltipDialog">
+ <label for="acq-po-cancel-reason">
+ Reason:
+ </label>
+ <span id="acq-po-cancel-reason"></span>
+ <button jsId="acqPoCancelReasonSubmit"
+ dojoType="dijit.form.Button"
+ type="submit">Cancel order</button>
+ </span>
+ </span>
+ </span>
+ </td>
+ </tr>
+ <tr>
<td>Notes</td>
<td>
<a href="javascript:void(0);"
['oncommand'],
function() { open_eg_web_page('conify/global/acq/lineitem_alert'); }
],
+ 'cmd_server_admin_acq_cancel_reason' : [
+ ['oncommand'],
+ function() { open_eg_web_page('conify/global/acq/cancel_reason'); }
+ ],
'cmd_server_admin_z39_source' : [
['oncommand'],
function() { open_eg_web_page('conify/global/config/z3950_source'); }
<command id="cmd_server_admin_copy_status"/>
<command id="cmd_server_admin_marc_code"/>
<command id="cmd_server_admin_billing_type"/>
+ <command id="cmd_server_admin_acq_cancel_reason"/>
<command id="cmd_server_admin_acq_lineitem_alert"/>
<command id="cmd_server_admin_z39_source"/>
<command id="cmd_server_admin_circ_mod"/>
<menu id="main.menu.admin.server.acq" label="&staff.main.menu.admin.server_admin.acq.label;" accesskey="&staff.main.menu.admin.server_admin.acq.accesskey;">
<menupopup id="main.menu.admin.server.acq.popup">
<menuitem label="&staff.main.menu.admin.server_admin.acq.lineitem_alert.label;" accesskey="&staff.main.menu.admin.server_admin.acq.lineitem_alert.accesskey;" command="cmd_server_admin_acq_lineitem_alert"/>
+ <menuitem label="&staff.main.menu.admin.server_admin.acq.cancel_reason.label;" accesskey="&staff.main.menu.admin.server_admin.acq.cancel_reason.accesskey;" command="cmd_server_admin_acq_cancel_reason"/>
</menupopup>
</menu>
<menu id="main.menu.admin.server.booking" label="&staff.main.menu.admin.server_admin.booking.label;" accesskey="&staff.main.menu.admin.server_admin.booking.accesskey;">