LP#1175740 Acq: If deleting line items with copies, remove fund debits and maybe... user/csharp/lp1175740-acq-li-delete-debits-and-copies
authorLebbeous Fogle-Weekley <lebbeous@esilibrary.com>
Tue, 11 Jun 2013 21:28:17 +0000 (17:28 -0400)
committerChris Sharp <csharp@georgialibraries.org>
Sun, 25 Sep 2016 00:27:06 +0000 (20:27 -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 af491b6..abcd5d4 100644 (file)
@@ -230,6 +230,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'}
     }
@@ -243,11 +244,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',
@@ -256,13 +305,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;
 
@@ -295,9 +345,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 40546c8..ec5316d 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 bc73c72..4b545a1 100644 (file)
@@ -60,3 +60,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 e3588ae..107719a 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 1f02110..43d3826 100644 (file)
@@ -3421,7 +3421,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;
 
@@ -3446,7 +3446,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;
             }
@@ -3454,12 +3457,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);
+                    }
                 }
             }
         );