Acq: Indicate funds at stop/warning balance level with color and warnings
authorsenator <senator@dcc99617-32d9-48b4-a31d-7c20da2025e4>
Wed, 31 Mar 2010 19:38:02 +0000 (19:38 +0000)
committersenator <senator@dcc99617-32d9-48b4-a31d-7c20da2025e4>
Wed, 31 Mar 2010 19:38:02 +0000 (19:38 +0000)
Specifically, in the lineitem detail (copy) interface,
where there are fund dropdowns.

git-svn-id: svn://svn.open-ils.org/ILS/trunk@16071 dcc99617-32d9-48b4-a31d-7c20da2025e4

Open-ILS/src/perlmods/OpenILS/Application/Acq/Order.pm
Open-ILS/web/js/dojo/openils/acq/nls/acq.js
Open-ILS/web/js/ui/default/acq/common/li_table.js

index 220c65e..c0283c4 100644 (file)
@@ -698,20 +698,54 @@ sub create_lineitem_detail_debit {
 }
 
 
-sub fund_exceeds_balance_stop_percent {
-    return fund_exceeds_balance_percent(
-        @_, "balance_stop_percent", "ACQ_FUND_EXCEEDS_STOP_PERCENT"
-    );
-}
+__PACKAGE__->register_method(
+       "method" => "fund_exceeds_balance_percent_api",
+       "api_name" => "open-ils.acq.fund.check_balance_percentages",
+       "signature" => {
+        "desc" => q/Determine whether a given fund exceeds its defined
+            "balance stop and warning percentages"/,
+        "params" => [
+            {"desc" => "Authentication token", "type" => "string"},
+            {"desc" => "Fund ID", "type" => "number"},
+            {"desc" => "Theoretical debit amount (optional)",
+                "type" => "number"}
+        ],
+        "return" => {"desc" => q/An array of two values, for stop and warning,
+            in that order: 1 if fund exceeds that balance percentage, else 0/}
+    }
+);
 
-sub fund_exceeds_balance_warning_percent {
-    return fund_exceeds_balance_percent(
-        @_, "balance_warning_percent", "ACQ_FUND_EXCEEDS_WARN_PERCENT"
-    );
+sub fund_exceeds_balance_percent_api {
+    my ($self, $conn, $auth, $fund_id, $debit_amount) = @_;
+
+    $debit_amount ||= 0;
+
+    my $e = new_editor("authtoken" => $auth);
+    return $e->die_event unless $e->checkauth;
+
+    my $fund = $e->retrieve_acq_fund($fund_id) or return $e->die_event;
+    return $e->die_event unless $e->allowed("VIEW_FUND", $fund->org);
+
+    my $result = [
+        fund_exceeds_balance_percent($fund, $debit_amount, $e, "stop"),
+        fund_exceeds_balance_percent($fund, $debit_amount, $e, "warning")
+    ];
+
+    $e->disconnect;
+    return $result;
 }
 
 sub fund_exceeds_balance_percent {
-    my ($fund, $debit_amount, $e, $method_name, $event_name) = @_;
+    my ($fund, $debit_amount, $e, $which) = @_;
+
+    my ($method_name, $event_name) = @{{
+        "warning" => [
+            "balance_warning_percent", "ACQ_FUND_EXCEEDS_WARN_PERCENT"
+        ],
+        "stop" => [
+            "balance_stop_percent", "ACQ_FUND_EXCEEDS_STOP_PERCENT"
+        ]
+    }->{$which}};
 
     if ($fund->$method_name) {
         my $balance =
@@ -752,10 +786,12 @@ sub create_fund_debit {
     my $fund = $mgr->editor->retrieve_acq_fund($args{fund}) or return 0;
 
     return 0 if
-        fund_exceeds_balance_stop_percent($fund, $args{"amount"}, $mgr->editor);
+        fund_exceeds_balance_percent(
+            $fund, $args{"amount"}, $mgr->editor, "stop"
+        );
     return 0 if
-        $dry_run and fund_exceeds_balance_warning_percent(
-            $fund, $args{"amount"}, $mgr->editor
+        $dry_run and fund_exceeds_balance_percent(
+            $fund, $args{"amount"}, $mgr->editor, "warning"
         );
 
     my $debit = Fieldmapper::acq::fund_debit->new;
index 56ba178..e88bdc8 100644 (file)
@@ -53,5 +53,7 @@
     'PO_COULD_ACTIVATE' : "Yes.",
     'PO_WARNING_NO_BLOCK_ACTIVATION': "Yes; fund ${0} (${1}) would be encumbered beyond its warning level.",
     'PO_STOP_BLOCKS_ACTIVATION': "No; fund ${0} (${1}) would be encumbered beyond its stop level.",
-    'PO_FUND_WARNING_CONFIRM': "Are you sure? Did you see the warning about over-encumbering a fund?"
+    'PO_FUND_WARNING_CONFIRM': "Are you sure? Did you see the warning about over-encumbering a fund?",
+    'CONFIRM_FUNDS_AT_STOP': "One or more of the selected funds has a balance below its stop level.\nYou may not be able to activate purchase orders incorporating these copies.\nContinue?",
+    'CONFIRM_FUNDS_AT_WARNING': "One or more of the selected funds has a balance below its warning level.\nContinue?",
 }
index 7f49c95..121bdb4 100644 (file)
@@ -22,7 +22,9 @@ var localeStrings = dojo.i18n.getLocalization('openils.acq', 'acq');
 const XUL_OPAC_WRAPPER = 'chrome://open_ils_staff_client/content/cat/opac.xul';
 var li_exportable_attrs = ["issn", "isbn", "upc"];
 
-var fundLabelFormat = ['${0} (${1})', 'code', 'year'];
+var fundLabelFormat = [
+    '<span class="fund_${0}">${1} (${2})</span>', 'id', 'code', 'year'
+];
 var fundSearchFormat = ['${0} (${1})', 'code', 'year'];
 
 function nodeByName(name, context) {
@@ -32,6 +34,10 @@ function nodeByName(name, context) {
 
 var liDetailBatchFields = ['fund', 'owning_lib', 'location', 'collection_code', 'circ_modifier', 'cn_label'];
 var liDetailFields = liDetailBatchFields.concat(['barcode', 'note']);
+var fundStyles = {
+    "stop": "color: #c00; font-weight: bold;",
+    "warning": "color: #c93;"
+};
 
 function AcqLiTable() {
 
@@ -40,6 +46,8 @@ function AcqLiTable() {
     this.plCache = {};
     this.poCache = {};
     this.relCache = {};
+    this.haveFundClass = {}
+    this.fundBalanceState = {};
     this.realDfaCache = {};
     this.virtDfaCounts = {};
     this.virtDfaId = -1;
@@ -1131,14 +1139,27 @@ function AcqLiTable() {
                         searchFilter : (field == 'fund') ? {"active": "t"} : null,
                         parentNode : dojo.query('[name='+field+']', row)[0],
                         orgLimitPerms : ['CREATE_PICKLIST'],
-                        dijitArgs : {required:false},
+                        dijitArgs : {
+                            "required": false,
+                            "labelType": (field == "fund") ? "html" : null
+                        },
+                        noCache: (field == "fund"),
                         forceSync : true
                     });
                     widget.build(
                         function(w, ww) {
+                            if (field == "fund" && w.store)
+                                self._ensureCSSFundClasses(w.store);
                             self.copyBatchWidgets[field] = w;
                         }
                     );
+                    if (field == "fund") {
+                        dojo.connect(
+                            widget.widget, "onChange", function(val) {
+                                self._updateFundSelectorStyle(widget, val);
+                            }
+                        );
+                    }
                 }
             }
         );
@@ -1234,8 +1255,9 @@ function AcqLiTable() {
                     fmField : field,
                     labelFormat : (field == 'fund') ? fundLabelFormat : null,
                     searchFormat : (field == 'fund') ? fundSearchFormat : null,
+                    dijitArgs: {"labelType": (field == 'fund') ? "html" : null},
                     searchFilter : searchFilter,
-                    noCache: true,
+                    noCache: (field == "fund"),
                     fmClass : 'acqlid',
                     parentNode : dojo.query('[name='+field+']', row)[0],
                     orgLimitPerms : ['CREATE_PICKLIST', 'CREATE_PURCHASE_ORDER'],
@@ -1244,13 +1266,18 @@ function AcqLiTable() {
                 widget.build(
                     // make sure we capture the value from any async widgets
                     function(w, ww) { 
+                        if (field == "fund" && w.store)
+                            self._ensureCSSFundClasses(w.store);
                         copy[field](ww.getFormattedValue()) 
                         self.copyWidgetCache[copy.id()][field] = w;
                     }
                 );
                 dojo.connect(widget.widget, 'onChange', 
                     function(val) { 
-                        if(copy.isnew() || val != copy[field]()) {
+                        if (field == "fund")
+                            self._updateFundSelectorStyle(widget, val);
+
+                        if (copy.isnew() || val != copy[field]()) {
                             // prevent setting ischanged() automatically on widget load for existing copies
                             copy[field](widget.getFormattedValue()) 
                             copy.ischanged(true);
@@ -1263,6 +1290,55 @@ function AcqLiTable() {
         this.updateLidState(copy, row);
     };
 
+    this._ensureCSSFundClass = function(id) {
+        if (!this.fundStyleSheet) {
+            dojo.create(
+                "style", {"type": "text/css"},
+                document.getElementsByTagName("head")[0], "last"
+            );
+            this.fundStyleSheet = document.styleSheets[
+                document.styleSheets.length - 1
+            ];
+        }
+
+        var cn = "fund_" + id;
+        if (!this.haveFundClass[cn]) {
+            fieldmapper.standardRequest(
+                ["open-ils.acq", "open-ils.acq.fund.check_balance_percentages"],
+                {
+                    "params": [openils.User.authtoken, id],
+                    "async": true,
+                    "oncomplete": function(r) {
+                        r = openils.Util.readResponse(r);
+                        self.fundBalanceState[id] = r;
+                        var style = "";
+                        if (r[0] /* stop */)
+                            style = fundStyles.stop;
+                        else if (r[1] /* warning */)
+                            style = fundStyles.warning;
+                        self.fundStyleSheet.insertRule(
+                            "." + cn + " { " + style + " }",
+                            self.fundStyleSheet.cssRules.length
+                        );
+                        self.haveFundClass[cn] = true;
+                    }
+                }
+            );
+        }
+    };
+
+    this._ensureCSSFundClasses = function(store) {
+        store.fetch({
+            "query": {"id": "*"},
+            "onItem": function(o) { self._ensureCSSFundClass(o.id[0]); }
+        });
+    };
+
+    this._updateFundSelectorStyle = function(widget, fund_id) {
+        openils.Util.removeCSSClass(widget.widget.domNode, /fund_\d+/);
+        openils.Util.addCSSClass(widget.widget.domNode, "fund_" + fund_id);
+    };
+
     this.updateLidState = function(copy, row) {
         if (typeof(row) == "undefined") {
             row = dojo.query(
@@ -1444,6 +1520,28 @@ function AcqLiTable() {
         return L;
     }
 
+    this.confirmBreachedCopyFunds = function(copies) {
+        var stop = 0, warning = 0;
+        copies.forEach(
+            function(o) {
+                if (o.fund()) {
+                    var state = self.fundBalanceState[o.fund()];
+                    if (state[0] /* stop */)
+                        stop++;
+                    else if (state[1] /* warning */)
+                        warning++;
+                }
+            }
+        );
+
+        if (stop) {
+            return confirm(localeStrings.CONFIRM_FUNDS_AT_STOP);
+        } else if (warning) {
+            return confirm(localeStrings.CONFIRM_FUNDS_AT_WARNING);
+        }
+        return true;
+    };
+
     this.saveCopyChanges = function(liId) {
         var self = this;
         var copies = [];
@@ -1459,14 +1557,17 @@ function AcqLiTable() {
             }
         }
 
-        if (typeof(this._copy_count_cb) == "function") {
-            this._copy_count_cb(liId, total);
-        }
 
         dojo.byId('acq-lit-copy-count-label-' + liId).innerHTML = total;
 
 
         if (copies.length > 0) {
+            if (!this.confirmBreachedCopyFunds(copies))
+                return;
+
+            if (typeof(this._copy_count_cb) == "function")
+                this._copy_count_cb(liId, total);
+
             openils.Util.show("acq-lit-update-copies-progress");
             fieldmapper.standardRequest(
                 ['open-ils.acq', 'open-ils.acq.lineitem_detail.cud.batch'],