Acq: If deleting line items with copies, remove fund debits and maybe copies user/senator/acq-li-delete-debits-and-copies
authorLebbeous Fogle-Weekley <lebbeous@esilibrary.com>
Tue, 11 Jun 2013 21:28:17 +0000 (17:28 -0400)
committerLebbeous Fogle-Weekley <lebbeous@esilibrary.com>
Wed, 12 Jun 2013 21:33:29 +0000 (17:33 -0400)
Depending on workflow, there are times when users might delete Acq line
items that have fund debits and/or real catalog copies attached to them.

We should always delete such fund debits when deleting line items (since
those debits otherwise become invisible and just throw off fund
balances), and we should also delete copies *iff* the staff user
confirms that's what they mean to do, and they have the DELETE_COPY
permission, of course.

This should resolve https://bugs.launchpad.net/evergreen/+bug/1175740

Signed-off-by: Lebbeous Fogle-Weekley <lebbeous@esilibrary.com>
Open-ILS/src/perlmods/lib/OpenILS/Application/Acq/Lineitem.pm
Open-ILS/src/templates/acq/common/li_table.tt2
Open-ILS/web/css/theme/default/acq.css
Open-ILS/web/js/dojo/openils/Util.js
Open-ILS/web/js/ui/default/acq/common/li_table.js

index 35d4648..0a4f0c4 100644 (file)
@@ -195,6 +195,7 @@ __PACKAGE__->register_method(
         params => [
             {desc => 'Authentication token',  type => 'string'},
             {desc => 'lineitem ID to delete', type => 'number'},
+            {desc => 'Even delete associated copies', type => 'boolean'}
         ],
         return => {desc => '1 on success, Event on error'}
     }
@@ -208,11 +209,59 @@ __PACKAGE__->register_method(
         params => [
             {desc => 'Authentication token',  type => 'string'},
             {desc => 'lineitem ID to delete', type => 'number'},
+            {desc => 'Even delete associated copies', type => 'boolean'}
         ],
         return => {desc => '1 on success, Event on error'}
     }
 );
 
+
+# Deletes one fund debit given its ID ($debit_id) *iff* it's an orphan
+# (i.e., it's not related to any of the tables that can point to a fund_debit,
+# not including the lineitem detail with ID $lid_id).
+#
+# Returns nothing on success, event on failure.
+sub delete_orphan_fund_debit {
+    my ($e, $debit_id, $lid_id) = @_;
+
+    # Make sure the debit is orphaned first.
+    my $rows = $e->json_query({
+        select => {acqfdeb => ["id"]},
+        from => {
+            acqfdeb => {
+                acqii => {
+                    type => "left", field => "fund_debit", fkey => "id"
+                },
+                acqpoi => {
+                    type => "left", field => "fund_debit", fkey => "id"
+                },
+                acqlid => {
+                    type => "left", field => "fund_debit", fkey => "id"
+                },
+                acqda => {
+                    type => "left", field => "fund_debit", fkey => "id"
+                }
+            }
+        },
+        where => {
+            "+acqfdeb" => {id => $debit_id},
+            "+acqii" => {id => undef},
+            "+acqpoi" => {id => undef},
+            "+acqda" => {id => undef},
+            "+acqlid" => {id => $lid_id},
+        }
+    }) or return $e->die_event;
+
+    if (@$rows) {
+        $e->delete_acq_fund_debit(
+            $e->retrieve_acq_fund_debit($rows->[0]->{id})
+        ) or return $e->die_event;
+    }
+
+    return; # success
+}
+
+
 __PACKAGE__->register_method(
     method    => 'delete_lineitem',
     api_name  => 'open-ils.acq.picklist.lineitem.delete',
@@ -221,13 +270,14 @@ __PACKAGE__->register_method(
         params => [
             {desc => 'Authentication token',  type => 'string'},
             {desc => 'lineitem ID to delete', type => 'number'},
+            {desc => 'Even delete associated copies', type => 'boolean'}
         ],
         return => {desc => '1 on success, Event on error'}
     }
 );
 
 sub delete_lineitem {
-    my($self, $conn, $auth, $li_id) = @_;
+    my($self, $conn, $auth, $li_id, $even_copies) = @_;
     my $e = new_editor(xact=>1, authtoken=>$auth);
     return $e->die_event unless $e->checkauth;
 
@@ -260,9 +310,38 @@ sub delete_lineitem {
         {lineitem => $li_id}, {idlist=>1});
 
     for my $lid_id (@$lid_ids) {
-        $e->delete_acq_lineitem_detail(
-            $e->retrieve_acq_lineitem_detail($lid_id))
-            or return $e->die_event;
+        # Be careful not to leave orphaned fund debits hanging around when
+        # lineitems are deleted.
+
+        my $lid = $e->retrieve_acq_lineitem_detail($lid_id);
+
+        if ($lid->fund_debit) {
+            my $evt = delete_orphan_fund_debit($e, $lid->fund_debit, $lid->id);
+            return $evt if $evt;
+        }
+
+        if ($lid->eg_copy_id) {
+            my $copies = $e->search_asset_copy([
+                {id => $lid->eg_copy_id, deleted => 'f'},
+                {flesh => 1, flesh_fields  => {"acp" => ["call_number"]}}
+            ]) or return $e->die_event;
+
+            if (@$copies) {
+                my $copy = $copies->[0];
+
+                if (not defined $even_copies) {
+                    $e->rollback;
+                    return new OpenILS::Event("NO_CHANGE", payload => $copy);
+                } elsif ($even_copies) {
+                    $e->allowed("DELETE_COPY", $copy->call_number->owning_lib) or
+                        return $e->die_event;
+
+                    $e->delete_asset_copy($copy) or return $e->die_event;
+                } # else just leave the copy alone
+            }
+        }
+
+        $e->delete_acq_lineitem_detail($lid) or return $e->die_event;
     }
 
     $e->delete_acq_lineitem($li) or return $e->die_event;
index b19e5c3..211dcd5 100644 (file)
     </div>
 
     <div class="hidden">
+        <div jsId="acqConfirmDeleteCopiesDialog" dojoType="dijit.Dialog"
+            title="[% l('Delete attached real copies?') %]">
+            <p>[% l("There are real copies in the catalog associated with line items you're deleting.  Do you wish to delete them all?") %]</p>
+            <table class="oils-acq-confirm-delete-copies">
+                <tr>
+                    <th>
+                        <label for="acq-confirm-delete-copies-yes">[% l('Yes') %]</label></td>
+                    <td><input id="acq-confirm-delete-copies-yes" name="decision" dojoType="dijit.form.RadioButton" type="radio" value="1" /></td>
+                </tr>
+                <tr>
+                    <th><label for="acq-confirm-delete-copies-no">[% l('No') %]</label></td>
+                    <td><input id="acq-confirm-delete-copies-no" dojoType="dijit.form.RadioButton" name="decision" type="radio" value="0" checked="true" /></td>
+                </tr>
+            </table>
+            <div>
+                <span dojoType="dijit.form.Button" type="submit">[% l('Continue') %]</span>
+            </div>
+        </div>
         <div jsId="acqLitLinkInvoiceDialog" dojoType="dijit.Dialog">
             [% INCLUDE "acq/common/inv_dialog.tt2" which = "li" %]
         </div>
index d59e28e..712e752 100644 (file)
@@ -59,3 +59,4 @@
 
 /* entry display */
 #oils-acq-lineitem-marc-block { border: 1px solid #6BA160; }
+.oils-acq-confirm-delete-copies th { font-weight: normal; width: 4em; text-align: left }
index 804d13c..c8099b5 100644 (file)
@@ -163,23 +163,30 @@ if(!dojo._hasResource["openils.Util"]) {
      * false, the response content will be null when an event is encountered.
      * @param isList If true, assume the response will be a list of data and
      * check the 1st item in the list for event-ness instead of the list itself.
+     * @param eventDispatch optional dispatch table indexed by event textcode,
+     *  with methods taking 1) event and 2) raw value as args.
      */
     openils.Util.alertEvent = true;
-    openils.Util.readResponse = function(r, eventOk, isList) {
+    openils.Util.readResponse = function(r, eventOk, isList, eventDispatch) {
         var msg = r.recv();
-        if(msg == null) return msg;
+        if (msg == null) return msg;
         var val = msg.content();
         var testval = val;
-        if(isList && dojo.isArray(val))
+        if (isList && dojo.isArray(val))
             testval = val[0];
-        if(e = openils.Event.parse(testval)) {
-            if(eventOk || e.textcode == 'SUCCESS') return e;
-            console.log(e.toString());
+        if (e = openils.Event.parse(testval)) {
+            if (e.textcode != "NO_SESSION") {
+                if (eventOk || e.textcode == 'SUCCESS') {
+                    return e;
+                } else if (eventDispatch && e.textcode in eventDispatch) {
+                    return eventDispatch[e.textcode](e, val);
+                }
+                console.log(e.toString());
+            } else {
 
             // session timed out.  Stop propagation of requests queued by Util.onload 
             // and launch the XUL login dialog if possible
-            var retryLogin = false;
-            if(e.textcode == 'NO_SESSION') {
+                var retryLogin = false;
                 openils.User.authtoken = null; 
                 if(openils.XUL.isXUL()) {
                     retryLogin = true;
@@ -190,7 +197,7 @@ if(!dojo._hasResource["openils.Util"]) {
                 }
             }
 
-            if(openils.Util.alertEvent && !retryLogin)
+            if (openils.Util.alertEvent && !retryLogin)
                 alert(e);
             return null;
         }
index 7a804da..11e7764 100644 (file)
@@ -504,6 +504,8 @@ function AcqLiTable() {
 
         var result = openils.Util.objectProperties(indices);
 
+        result = result.filter(function(liId) { return typeof self.liCache[liId] != "undefined"; });
+
         if (!id_only)
             result = result.map(function(liId) { return self.liCache[liId]; });
 
@@ -3179,7 +3181,7 @@ function AcqLiTable() {
         );
     }
 
-    this._deleteLiList = function(list, idx) {
+    this._deleteLiList = function(list, idx, even_copies) {
         if(idx == null) idx = 0;
         if(idx >= list.length) return;
 
@@ -3204,7 +3206,10 @@ function AcqLiTable() {
              * confirm dialog for each one.
              */
 
-            if (!confirm(localeStrings.DEL_LI_FROM_PO)) {
+            var preConfirmOnce = self.preConfirmLiDelete;
+            self.preConfirmLiDelete = false;
+
+            if (!preConfirmOnce && !confirm(localeStrings.DEL_LI_FROM_PO)) {
                 self._deleteLiList(list, ++idx); /* move on to next in list */
                 return;
             }
@@ -3212,12 +3217,27 @@ function AcqLiTable() {
 
         fieldmapper.standardRequest(
             ['open-ils.acq',
-             this.isPO ? 'open-ils.acq.purchase_order.lineitem.delete' : 'open-ils.acq.picklist.lineitem.delete'],
+             this.isPO ? 'open-ils.acq.purchase_order.lineitem.delete' :
+                'open-ils.acq.picklist.lineitem.delete'],
             {   async: true,
-                params: [openils.User.authtoken, liId],
+                params: [openils.User.authtoken, liId, even_copies],
                 oncomplete: function(r) {
-                    self.removeLineitem(liId);
-                    self._deleteLiList(list, ++idx);
+                    r = openils.Util.readResponse(
+                        r, false, false, {
+                            "NO_CHANGE": function(e, val) {
+                                acqConfirmDeleteCopiesDialog.execute =
+                                    function(o) {
+                                        self.preConfirmLiDelete = true;
+                                        self._deleteLiList(list, idx, o.decision);
+                                    };
+                                acqConfirmDeleteCopiesDialog.show();
+                            }
+                        }
+                    );
+                    if (r) {    /* worked fine, didn't get NO_CHANGE */
+                        self.removeLineitem(liId);
+                        self._deleteLiList(list, ++idx, even_copies);
+                    }
                 }
             }
         );