[freeside-commits] branch new_standardization created. c40f5c44646f53c43e2fed784d19c3cecdd7f8c7

Mark Wells mark at 420.am
Mon Nov 2 11:58:17 PST 2015


The branch, new_standardization has been created
        at  c40f5c44646f53c43e2fed784d19c3cecdd7f8c7 (commit)

- Log -----------------------------------------------------------------
commit c40f5c44646f53c43e2fed784d19c3cecdd7f8c7
Author: Mark Wells <mark at freeside.biz>
Date:   Fri Oct 30 16:07:37 2015 -0700

    address standardization, part 2

diff --git a/httemplate/edit/cust_main.cgi b/httemplate/edit/cust_main.cgi
index 2593201..ee8d1b1 100755
--- a/httemplate/edit/cust_main.cgi
+++ b/httemplate/edit/cust_main.cgi
@@ -107,12 +107,14 @@ function samechanged(what) {
   }
 }
 
-% if ( ! $has_ship_address ) {
-  $('#ship_location').hide();
-% }
-
 $().ready( function() {
-  window.bill_location = new Location($('fieldset#bill_location'));
+  window.bill_location = new Location($('fieldset#bill_location'), 'bill_');
+  window.ship_location = new Location($('fieldset#ship_location'), 'ship_');
+
+  if ($('#same').prop('checked')) {
+    $('#ship_location').hide();
+  }
+
 });
 
 </SCRIPT>
diff --git a/httemplate/edit/cust_main/bottomfixup.js b/httemplate/edit/cust_main/bottomfixup.js
index 97816aa..48da4e3 100644
--- a/httemplate/edit/cust_main/bottomfixup.js
+++ b/httemplate/edit/cust_main/bottomfixup.js
@@ -5,7 +5,8 @@ my $conf = new FS::Conf;
 my $company_latitude  = $conf->config('company_latitude');
 my $company_longitude = $conf->config('company_longitude');
 
-my @fixups = ('standardize_locations');
+my @fixups = ();
+# 'standardize_locations');
 
 push @fixups, 'confirm_censustract_bill', 'confirm_censustract_ship'
     if $conf->exists('cust_main-require_censustract');
diff --git a/httemplate/elements/location.html b/httemplate/elements/location.html
index d4c0f14..7055545 100644
--- a/httemplate/elements/location.html
+++ b/httemplate/elements/location.html
@@ -271,9 +271,10 @@ Example:
 % }
 %# Placeholders
 <& hidden.html, field => $pre.'cachenum', value => '' &>
-<& hidden.html, field => $pre.'addr_clean', value => '' &>
+<& hidden.html, field => $pre.'addr_clean', value => $object->get('addr_clean') &>
 
 <SCRIPT TYPE="text/javascript">
+// XXX some of this should go away after address standardization changes...
 <&| /elements/onload.js &>
   var clear_coords_ids = [
     '<%$pre%>latitude',
@@ -328,11 +329,16 @@ Example:
 
 </&>
 
-function Location(fieldset) {
+% if (! $m->notes('location_js') ) {
+%   $m->notes('location_js', 1);
+
+function Location(fieldset, prefix) {
   if ( typeof fieldset == 'String' ) {
     fieldset = $('#' + fieldset);
   }
   this.fieldset = $(fieldset);
+  this.prefix = prefix || '';
+
   var errorbox = document.createElement('DIV');
   errorbox.className = 'error';
   fieldset.append(errorbox); // after the <table>
@@ -343,8 +349,12 @@ function Location(fieldset) {
   });
   this.errorbox = $(errorbox); // so we can find it
 
-  var img_tick = $('<IMG SRC="http://localhost/freeside/images/tick.png">');
-  var img_wait = $('<IMG SRC="http://localhost/freeside/images/wait-orange.gif">');
+  var img_tick = $('<IMG SRC="<% $fsurl %>images/tick.png">');
+  var img_wait = $('<IMG SRC="<% $fsurl %>images/wait-orange.gif">');
+
+  if ( $('#' + prefix + 'addr_clean').prop('value') ) {
+    $(errorbox).append(img_tick);
+  }
 
   // get/set the serialized (URL parameter string) contents of the form fields
   this.value = function(newvalue) {
@@ -365,51 +375,63 @@ function Location(fieldset) {
     return this.fieldset.serialize();
   };
 
-  // send a standardization request and do something with the result
+  // send a standardization request and push the result into this.value()
   this.standardize = function(callback) {
     this.errorbox.empty();
     this.errorbox.append(img_wait);
     $.ajax({
       type: 'POST',
       url: '<% $fsurl %>misc/address_standardize.cgi',
-      success: callback,
+      success: this.value.bind(this),
       data: this.value()
     });
   };
 
-  // check if required fields are filled, and if so, standardize
-  var standardize_if_ready = function() {
-    var loc = this;
-    var ready = true;
-    var required_fields = this.fieldset.find(':data(required)');
-    for ( var i = 0; ready && i < required_fields.length; i++ ) {
-      if ( required_fields[i].prop('value').length == 0 ) {
-        ready = false;
-      }
-    }
+  var location_change_timer;
 
-    if ( ready ) {
-      // pass the "value" method, prebound to the location object
-      this.standardize( this.value.bind(loc) );
-    }
-  };
+  // check if required fields are filled, and if so, wait 2 seconds, then
+  // call "standardize"
+  var standardize_if_ready = function( ev ) {
+    // pull the location object back out
+    var loc = ev.data;
 
-  // event handler; the Location object is passed in event.data
-  var location_change_timer;
-  var location_changed = function( ev ) {
     if ( location_change_timer ) {
+      console.log("reset timer...");
       window.clearTimeout(location_change_timer);
     }
-    location_change_timer = window.setTimeout(
-      standardize_if_ready.bind(ev.data),
-      2000
-    );
+
+    // require address1 and either (zip) or (city and state)
+    // before trying to standardize
+    var ready =
+         $('#' + loc.prefix + 'address1').val()
+      && (
+           (    $('#' + loc.prefix + 'city').val()
+             && $('#' + loc.prefix + 'state').val()
+           )
+           || $('#' + loc.prefix + 'zip').val()
+         )
+    ;
+
+    if ( ready ) {
+      console.log("start timer...");
+      location_change_timer = window.setTimeout( loc.standardize.bind(loc),
+                                                 2000 );
+    }
   };
 
-  fieldset.find('input').on('change', this, location_changed);
-  fieldset.find('select').on('change', this, location_changed);
+  // event handler; the Location object is passed in event.data
+  var location_changed = standardize_if_ready;
+
+  // bind only to the fields that are used in standardization
+  // (so that it's possible to manually edit coords, etc.)
+  var onchange_fields = 
+    [ 'address1', 'address2', 'city', 'state', 'zip', 'country' ];
+  for ( var i = 0; i < onchange_fields.length; i++ ) {
+    $('#' + prefix + onchange_fields[i]).on('change', this, location_changed);
+  }
 }
 
+% }
 </SCRIPT>
 
 <%init>

commit f2057c70368bbb0acbc098baabc14e44d8e57d56
Author: Mark Wells <mark at freeside.biz>
Date:   Fri Oct 30 11:24:30 2015 -0700

    fix missing import, #14092

diff --git a/FS/FS/part_pkg/discount_Mixin.pm b/FS/FS/part_pkg/discount_Mixin.pm
index dcca343..4c86753 100644
--- a/FS/FS/part_pkg/discount_Mixin.pm
+++ b/FS/FS/part_pkg/discount_Mixin.pm
@@ -4,6 +4,7 @@ use strict;
 use vars qw( %info );
 use Time::Local qw( timelocal );
 use List::Util  qw( min );
+use FS::Record qw( qsearchs );
 use FS::cust_pkg;
 use FS::cust_bill_pkg_discount;
 

commit ebf0fbf1e78dda4cba6ce0aa1420233375f3a0ee
Merge: b2787f7 b8fc5d2
Author: Mark Wells <mark at freeside.biz>
Date:   Fri Oct 30 11:24:47 2015 -0700

    Merge branch 'master' of git.freeside.biz:/home/git/freeside


commit b2787f77f842a16df069227e74a2da54b8b36efb
Author: Mark Wells <mark at freeside.biz>
Date:   Thu Oct 29 13:49:45 2015 -0700

    address standardization UI, part 1

diff --git a/FS/FS/Misc/Geo.pm b/FS/FS/Misc/Geo.pm
index 1aa5939..293748c 100644
--- a/FS/FS/Misc/Geo.pm
+++ b/FS/FS/Misc/Geo.pm
@@ -342,7 +342,7 @@ sub standardize_uscensus {
     die "Geocoding did not find a matching address.\n";
   } else {
     warn Dumper($result) if $DEBUG;
-    die $result->error_message;
+    die $result->error_message."\n";
   }
 }
 
diff --git a/httemplate/docs/license.html b/httemplate/docs/license.html
index 7e5bb1e..71643fc 100644
--- a/httemplate/docs/license.html
+++ b/httemplate/docs/license.html
@@ -130,6 +130,8 @@ and other contributors, licensed under the terms of the MIT license.
 Contains the Spectrum No Hassle jQuery Colorpicker by Brian Grinstead, licensed
 under the terms of the MIT license.
 
+<P>
+Contains <a href="https://github.com/kflorence/jquery-deserialize/">jQuery.deserialize</a> by Kyle Florence, licensed under the terms of the MIT license.
 
 <!-- artwork -->
 
diff --git a/httemplate/edit/cust_main.cgi b/httemplate/edit/cust_main.cgi
index effe84b..2593201 100755
--- a/httemplate/edit/cust_main.cgi
+++ b/httemplate/edit/cust_main.cgi
@@ -43,6 +43,7 @@
   <TD>
 %#; padding-right:2px; vertical-align:top">
     <FONT CLASS="fsinnerbox-title"><% mt('Billing address') |h %></FONT>
+    <FIELDSET ID="bill_location" CLASS="location">
     <TABLE CLASS="fsinnerbox" WIDTH="100%">
     <& cust_main/before_bill_location.html, $cust_main &>
     <& /elements/location.html,
@@ -54,6 +55,7 @@
     &>
     <& cust_main/after_bill_location.html, $cust_main &>
     </TABLE>
+    </FIELDSET>
   </TD>
 </TR>
 <TR><TD STYLE="height:14px"></TD></TR>
@@ -68,7 +70,7 @@
            VALUE="Y"
            <% $has_ship_address ? '' : 'CHECKED' %>
     ><% mt('same as billing address') |h %>
-    <DIV ID="div_ship_location">
+    <FIELDSET ID="ship_location" CLASS="location">
       <TABLE WIDTH="100%" CLASS="fsinnerbox">
       <& cust_main/before_ship_location.html, $cust_main &>
       <& /elements/location.html,
@@ -91,7 +93,7 @@
         </TR>
 % }
       </TABLE>
-    </DIV>
+    </FIELDSET>
   </TD>
 </TR></TABLE>
 
@@ -99,16 +101,20 @@
 
 function samechanged(what) {
   if ( what.checked ) {
-    $('#div_ship_location').slideUp();
+    $('#ship_location').slideUp();
   } else {
-    $('#div_ship_location').slideDown();
+    $('#ship_location').slideDown();
   }
 }
 
 % if ( ! $has_ship_address ) {
-  $('#div_ship_location').hide();
+  $('#ship_location').hide();
 % }
 
+$().ready( function() {
+  window.bill_location = new Location($('fieldset#bill_location'));
+});
+
 </SCRIPT>
 
 <& cust_main/contacts_new.html, 'cust_main'=>$cust_main, &>
diff --git a/httemplate/elements/freeside.css b/httemplate/elements/freeside.css
index dbd27cb..5eb8f72 100644
--- a/httemplate/elements/freeside.css
+++ b/httemplate/elements/freeside.css
@@ -335,3 +335,11 @@ div.package-marker-change_from {
   border-left: solid #bbffbb 30px;
   display: inline-block;
 }
+
+/* elements/location.html and co. */
+fieldset.location {
+  padding: 0px;
+  margin: 0px;
+  border: none;
+}
+
diff --git a/httemplate/elements/jquery.deserialize.min.js b/httemplate/elements/jquery.deserialize.min.js
new file mode 100644
index 0000000..7054ea4
--- /dev/null
+++ b/httemplate/elements/jquery.deserialize.min.js
@@ -0,0 +1,8 @@
+/**
+ * @author Kyle Florence <kyle[dot]florence[at]gmail[dot]com>
+ * @website https://github.com/kflorence/jquery-deserialize/
+ * @version 1.2.1
+ *
+ * Dual licensed under the MIT and GPLv2 licenses.
+ */
+(function(i,b){var f=Array.prototype.push,a=/^(?:radio|checkbox)$/i,e=/\+/g,d=/^(?:option|select-one|select-multiple)$/i,g=/^(?:button|color|date|datetime|datetime-local|email|hidden|month|number|password|range|reset|search|submit|tel|text|textarea|time|url|week)$/i;function c(j){return j.map(function(){return this.elements?i.makeArray(this.elements):this}).filter(":input").get()}function h(j){var k,l={};i.each(j,function(n,m){k=l[m.name];l[m.name]=k===b?m:(i.isArray(k)?k.concat(m):[k,m])});return l}i.fn.deserialize=function(A,l){var y,n,q=c(this),t=[];if(!A||!q.length){return this}if(i.isArray(A)){t=A}else{if(i.isPlainObject(A)){var B,w;for(B in A){i.isArray(w=A[B])?f.apply(t,i.map(w,function(j){return{name:B,value:j}})):f.call(t,{name:B,value:w})}}else{if(typeof A==="string"){var v;A=A.split("&");for(y=0,n=A.length;y<n;y++){v=A[y].split("=");f.call(t,{name:decodeURIComponent(v[0]),value:decodeURIComponent(v[1].replace(e,"%20"))})}}}}if(!(n=t.length)){return this}var u,k,x,z,C,o,m,w,p=i.noop,s=i.noop,r={};l=l||{};q=h(q);if(i.isFunction(l)){s=l}else{p=i.isFunction(l.change)?l.change:p;s=i.isFunction(l.complete)?l.complete:s}for(y=0;y<n;y++){u=t[y];C=u.name;w=u.value;if(!(k=q[C])){continue}m=(z=k.length)?k[0]:k;m=(m.type||m.nodeName).toLowerCase();o=null;if(g.test(m)){if(z){x=r[C];k=k[r[C]=(x==b)?0:++x]}p.call(k,(k.value=w))}else{if(a.test(m)){o="checked"}else{if(d.test(m)){o="selected"}}}if(o){if(!z){k=[k];z=1}for(x=0;x<z;x++){u=k[x];if(u.value==w){p.call(u,(u[o]=true)&&w)}}}}s.call(this);return this}})(jQuery);
diff --git a/httemplate/elements/location.html b/httemplate/elements/location.html
index b5f0a96..d4c0f14 100644
--- a/httemplate/elements/location.html
+++ b/httemplate/elements/location.html
@@ -19,6 +19,9 @@ Example:
 
 </%doc>
 
+<SCRIPT SRC="<% $fsurl %>elements/jquery.deserialize.min.js"></SCRIPT>
+<SCRIPT SRC="<% $fsurl %>elements/polyfill.js"></SCRIPT>
+
 % if ( $opt{'alt_format'} ) {
 
 <TR>
@@ -324,6 +327,89 @@ Example:
   }
 
 </&>
+
+function Location(fieldset) {
+  if ( typeof fieldset == 'String' ) {
+    fieldset = $('#' + fieldset);
+  }
+  this.fieldset = $(fieldset);
+  var errorbox = document.createElement('DIV');
+  errorbox.className = 'error';
+  fieldset.append(errorbox); // after the <table>
+  $(errorbox).position({
+    my: 'left',
+    at: 'right+20px',
+    of: fieldset
+  });
+  this.errorbox = $(errorbox); // so we can find it
+
+  var img_tick = $('<IMG SRC="http://localhost/freeside/images/tick.png">');
+  var img_wait = $('<IMG SRC="http://localhost/freeside/images/wait-orange.gif">');
+
+  // get/set the serialized (URL parameter string) contents of the form fields
+  this.value = function(newvalue) {
+    if (newvalue) {
+      try {
+        this.fieldset.deserialize(newvalue);
+        this.errorbox.empty();
+        if ( newvalue['error'] ) {
+          this.errorbox.text(newvalue['error']);
+        } else {
+          this.errorbox.append(img_tick);
+        }
+      } catch(err) {
+        console.log("Couldn't parse returned data:\n" + newvalue);
+        // show an error also
+      }
+    }
+    return this.fieldset.serialize();
+  };
+
+  // send a standardization request and do something with the result
+  this.standardize = function(callback) {
+    this.errorbox.empty();
+    this.errorbox.append(img_wait);
+    $.ajax({
+      type: 'POST',
+      url: '<% $fsurl %>misc/address_standardize.cgi',
+      success: callback,
+      data: this.value()
+    });
+  };
+
+  // check if required fields are filled, and if so, standardize
+  var standardize_if_ready = function() {
+    var loc = this;
+    var ready = true;
+    var required_fields = this.fieldset.find(':data(required)');
+    for ( var i = 0; ready && i < required_fields.length; i++ ) {
+      if ( required_fields[i].prop('value').length == 0 ) {
+        ready = false;
+      }
+    }
+
+    if ( ready ) {
+      // pass the "value" method, prebound to the location object
+      this.standardize( this.value.bind(loc) );
+    }
+  };
+
+  // event handler; the Location object is passed in event.data
+  var location_change_timer;
+  var location_changed = function( ev ) {
+    if ( location_change_timer ) {
+      window.clearTimeout(location_change_timer);
+    }
+    location_change_timer = window.setTimeout(
+      standardize_if_ready.bind(ev.data),
+      2000
+    );
+  };
+
+  fieldset.find('input').on('change', this, location_changed);
+  fieldset.find('select').on('change', this, location_changed);
+}
+
 </SCRIPT>
 
 <%init>
diff --git a/httemplate/elements/polyfill.js b/httemplate/elements/polyfill.js
new file mode 100644
index 0000000..5e08a99
--- /dev/null
+++ b/httemplate/elements/polyfill.js
@@ -0,0 +1,30 @@
+// Function.bind(), not supported in IE8
+// polyfill from Mozilla Developer Network
+
+if (!Function.prototype.bind) {
+  Function.prototype.bind = function(oThis) {
+    if (typeof this !== 'function') {
+      // closest thing possible to the ECMAScript 5
+      // internal IsCallable function
+      throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
+    }
+
+    var aArgs   = Array.prototype.slice.call(arguments, 1),
+        fToBind = this,
+        fNOP    = function() {},
+        fBound  = function() {
+          return fToBind.apply(this instanceof fNOP
+                 ? this
+                 : oThis,
+                 aArgs.concat(Array.prototype.slice.call(arguments)));
+        };
+
+    if (this.prototype) {
+      // native functions don't have a prototype
+      fNOP.prototype = this.prototype; 
+    }
+    fBound.prototype = new fNOP();
+
+    return fBound;
+  };
+}
diff --git a/httemplate/misc/address_standardize.cgi b/httemplate/misc/address_standardize.cgi
new file mode 100644
index 0000000..d9ba550
--- /dev/null
+++ b/httemplate/misc/address_standardize.cgi
@@ -0,0 +1,51 @@
+<% encode_json($return) %>\
+<%init>
+
+local $SIG{__DIE__}; #disable Mason error trap
+
+my $DEBUG = 0;
+
+my $conf = new FS::Conf;
+
+# figure out the prefix
+my $pre;
+foreach my $name ($cgi->param) {
+  if ($name =~ /^(\w*)address1$/) {
+    $pre = $1;
+    last;
+  }
+}
+die "no address1 field in location" if !defined($pre);
+
+# gather relevant fields
+my %old = ( map { $_ => scalar($cgi->param($pre . $_)) }
+  qw( company address1 address2 city state zip country )
+);
+
+my $cache = eval { FS::GeocodeCache->standardize(\%old) };
+$cache->set_coord;
+# don't do set_censustract here, though censustract may be set by now
+
+# give the fields their prefixed names back
+# except always name the error string 'error'
+my $error = delete($cache->{'error'}) || '';
+my %new = (
+  'changed' => 0,
+  'error' => $error,
+  map { $pre.$_, $cache->get($_) } keys %$cache
+);
+
+foreach ( qw(address1 address2 city state zip country) ) {
+  if ( $new{$pre.$_} ne $old{$pre.$_} ) {
+    $new{changed} = 1;
+    last;
+  }
+}
+
+# refold this to make it acceptable to jquery
+#my $return = [ map { { name => $_, value => $new{$_} } } keys %new ];
+my $return = \%new;
+warn "result:\n".encode_json($return) if $DEBUG;
+
+$r->content_type('application/json');
+</%init>

commit 07b346c23e24d7fb368935b964c091421517dbc0
Author: Mark Wells <mark at freeside.biz>
Date:   Wed Oct 28 11:59:55 2015 -0700

    fix census bureau conf setting on 4.x, #32250

diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm
index 990f2a3..6b7dbe0 100644
--- a/FS/FS/Conf.pm
+++ b/FS/FS/Conf.pm
@@ -4282,7 +4282,6 @@ and customer address. Include units.',
     'section'     => 'invoicing',
     'description' => 'Instead of showing payments (and credits) applied to the invoice, show those received since the previous invoice date.',
     'type'        => 'checkbox',
-                       'uscensus' => 'U.S. Census Bureau',
   },
 
   {
@@ -4316,6 +4315,7 @@ and customer address. Include units.',
     'type'        => 'select',
     'select_hash' => [ '' => '', 
                        'usps'     => 'U.S. Postal Service',
+                       'uscensus' => 'U.S. Census Bureau',
                        'tomtom'   => 'TomTom',
                        'melissa'  => 'Melissa WebSmart',
                      ],

-----------------------------------------------------------------------




More information about the freeside-commits mailing list