#34636: Add service address Lat and Long to Advanced customer report
[freeside.git] / FS / FS / UI / Web.pm
index 91d2034..28ba869 100644 (file)
@@ -6,7 +6,7 @@ use Exporter;
 use Carp qw( confess );
 use HTML::Entities;
 use FS::Conf;
 use Carp qw( confess );
 use HTML::Entities;
 use FS::Conf;
-use FS::Misc::DateTime qw( parse_datetime );
+use FS::Misc::DateTime qw( parse_datetime day_end );
 use FS::Record qw(dbdef);
 use FS::cust_main;  # are sql_balance and sql_date_balance in the right module?
 
 use FS::Record qw(dbdef);
 use FS::cust_main;  # are sql_balance and sql_date_balance in the right module?
 
@@ -32,16 +32,16 @@ sub parse_beginning_ending {
   my $beginning = 0;
   if ( $cgi->param($prefix.'begin') =~ /^(\d+)$/ ) {
     $beginning = $1;
   my $beginning = 0;
   if ( $cgi->param($prefix.'begin') =~ /^(\d+)$/ ) {
     $beginning = $1;
-  } elsif ( $cgi->param($prefix.'beginning') =~ /^([ 0-9\-\/]{1,64})$/ ) {
+  } elsif ( $cgi->param($prefix.'beginning') =~ /^([ 0-9\-\/\:]{1,64})$/ ) {
     $beginning = parse_datetime($1) || 0;
   }
 
   my $ending = 4294967295; #2^32-1
   if ( $cgi->param($prefix.'end') =~ /^(\d+)$/ ) {
     $ending = $1 - 1;
     $beginning = parse_datetime($1) || 0;
   }
 
   my $ending = 4294967295; #2^32-1
   if ( $cgi->param($prefix.'end') =~ /^(\d+)$/ ) {
     $ending = $1 - 1;
-  } elsif ( $cgi->param($prefix.'ending') =~ /^([ 0-9\-\/]{1,64})$/ ) {
-    #probably need an option to turn off the + 86399
-    $ending = parse_datetime($1) + 86399;
+  } elsif ( $cgi->param($prefix.'ending') =~ /^([ 0-9\-\/\:]{1,64})$/ ) {
+    $ending = parse_datetime($1);
+    $ending = day_end($ending) unless $ending =~ /:/;
   }
 
   ( $beginning, $ending );
   }
 
   ( $beginning, $ending );
@@ -113,16 +113,16 @@ sub svc_url {
     if $DEBUG;
   if ( $opt{m}->interp->comp_exists("/$opt{action}/$svcdb.cgi") ) {
     $url = "$svcdb.cgi?";
     if $DEBUG;
   if ( $opt{m}->interp->comp_exists("/$opt{action}/$svcdb.cgi") ) {
     $url = "$svcdb.cgi?";
+  } elsif ( $opt{m}->interp->comp_exists("/$opt{action}/$svcdb.html") ) {
+    $url = "$svcdb.html?";
   } else {
   } else {
-
     my $generic = $opt{action} eq 'search' ? 'cust_svc' : 'svc_Common';
 
     $url = "$generic.html?svcdb=$svcdb;";
     $url .= 'svcnum=' if $query =~ /^\d+(;|$)/ or $query eq '';
   }
 
     my $generic = $opt{action} eq 'search' ? 'cust_svc' : 'svc_Common';
 
     $url = "$generic.html?svcdb=$svcdb;";
     $url .= 'svcnum=' if $query =~ /^\d+(;|$)/ or $query eq '';
   }
 
-  import FS::CGI 'rooturl'; #WTF!  why is this necessary
-  my $return = rooturl(). "$opt{action}/$url$query";
+  my $return = FS::CGI::rooturl(). "$opt{action}/$url$query";
 
   $return = qq!<A HREF="$return">! if $opt{ahref};
 
 
   $return = qq!<A HREF="$return">! if $opt{ahref};
 
@@ -170,7 +170,8 @@ sub svc_export_links {
 }
 
 sub parse_lt_gt {
 }
 
 sub parse_lt_gt {
-  my($cgi, $field) = @_;
+  my($cgi, $field) = (shift, shift);
+  my $table = ( @_ && length($_[0]) ) ? shift.'.' : '';
 
   my @search = ();
 
 
   my @search = ();
 
@@ -188,7 +189,7 @@ sub parse_lt_gt {
 
       my $num = $1;
       $num =~ s/[\,\s]+//g;
 
       my $num = $1;
       $num =~ s/[\,\s]+//g;
-      my $search = "$field $op{$op} $num";
+      my $search = "$table$field $op{$op} $num";
       push @search, $search;
 
       warn "found ${field}_$op field; adding search element $search\n"
       push @search, $search;
 
       warn "found ${field}_$op field; adding search element $search\n"
@@ -205,6 +206,7 @@ sub parse_lt_gt {
 # cust_main report subroutines
 ###
 
 # cust_main report subroutines
 ###
 
+=over 4
 
 =item cust_header [ CUST_FIELDS_VALUE ]
 
 
 =item cust_header [ CUST_FIELDS_VALUE ]
 
@@ -229,39 +231,45 @@ sub cust_header {
     'Cust#'                    => 'custnum',
     'Name'                     => 'contact',
     'Company'                  => 'company',
     'Cust#'                    => 'custnum',
     'Name'                     => 'contact',
     'Company'                  => 'company',
+
+    # obsolete but might still be referenced in configuration
     '(bill) Customer'          => 'name',
     '(service) Customer'       => 'ship_name',
     '(bill) Name'              => 'contact',
     '(service) Name'           => 'ship_contact',
     '(bill) Company'           => 'company',
     '(service) Company'        => 'ship_company',
     '(bill) Customer'          => 'name',
     '(service) Customer'       => 'ship_name',
     '(bill) Name'              => 'contact',
     '(service) Name'           => 'ship_contact',
     '(bill) Company'           => 'company',
     '(service) Company'        => 'ship_company',
+    '(bill) Day phone'         => 'daytime',
+    '(bill) Night phone'       => 'night',
+    '(bill) Fax number'        => 'fax',
+    'Customer'                 => 'name',
     'Address 1'                => 'bill_address1',
     'Address 2'                => 'bill_address2',
     'City'                     => 'bill_city',
     'State'                    => 'bill_state',
     'Zip'                      => 'bill_zip',
     'Address 1'                => 'bill_address1',
     'Address 2'                => 'bill_address2',
     'City'                     => 'bill_city',
     'State'                    => 'bill_state',
     'Zip'                      => 'bill_zip',
-    'Country'                  => 'country_full',
+    'Country'                  => 'bill_country_full',
     'Day phone'                => 'daytime', # XXX should use msgcat, but how?
     'Night phone'              => 'night',   # XXX should use msgcat, but how?
     'Day phone'                => 'daytime', # XXX should use msgcat, but how?
     'Night phone'              => 'night',   # XXX should use msgcat, but how?
+    'Mobile phone'             => 'mobile',  # XXX should use msgcat, but how?
     'Fax number'               => 'fax',
     '(bill) Address 1'         => 'bill_address1',
     '(bill) Address 2'         => 'bill_address2',
     '(bill) City'              => 'bill_city',
     '(bill) State'             => 'bill_state',
     '(bill) Zip'               => 'bill_zip',
     'Fax number'               => 'fax',
     '(bill) Address 1'         => 'bill_address1',
     '(bill) Address 2'         => 'bill_address2',
     '(bill) City'              => 'bill_city',
     '(bill) State'             => 'bill_state',
     '(bill) Zip'               => 'bill_zip',
-    '(bill) Country'           => 'country_full',
-    '(bill) Day phone'         => 'daytime', # XXX should use msgcat, but how?
-    '(bill) Night phone'       => 'night',   # XXX should use msgcat, but how?
-    '(bill) Fax number'        => 'fax',
+    '(bill) Country'           => 'bill_country_full',
+    '(bill) Latitude'          => 'bill_latitude',
+    '(bill) Longitude'         => 'bill_longitude',
     '(service) Address 1'      => 'ship_address1',
     '(service) Address 2'      => 'ship_address2',
     '(service) City'           => 'ship_city',
     '(service) State'          => 'ship_state',
     '(service) Zip'            => 'ship_zip',
     '(service) Country'        => 'ship_country_full',
     '(service) Address 1'      => 'ship_address1',
     '(service) Address 2'      => 'ship_address2',
     '(service) City'           => 'ship_city',
     '(service) State'          => 'ship_state',
     '(service) Zip'            => 'ship_zip',
     '(service) Country'        => 'ship_country_full',
-    '(service) Day phone'      => 'ship_daytime', # XXX should use msgcat, how?
-    '(service) Night phone'    => 'ship_night',   # XXX should use msgcat, how?
-    '(service) Fax number'     => 'ship_fax',
+    '(service) Latitude'       => 'ship_latitude',
+    '(service) Longitude'      => 'ship_longitude',
     'Invoicing email(s)'       => 'invoicing_list_emailonly_scalar',
     'Payment Type'             => 'payby',
     'Current Balance'          => 'current_balance',
     'Invoicing email(s)'       => 'invoicing_list_emailonly_scalar',
     'Payment Type'             => 'payby',
     'Current Balance'          => 'current_balance',
@@ -322,6 +330,14 @@ sub cust_header {
   @cust_header;
 }
 
   @cust_header;
 }
 
+sub cust_sort_fields {
+  cust_header(@_) if( @_ or !@cust_fields );
+  #inefficientish, but tiny lists and only run once per page
+
+  map { $_ eq 'custnum' ? 'custnum' : '' } @cust_fields;
+
+}
+
 =item cust_sql_fields [ CUST_FIELDS_VALUE ]
 
 Returns a list of fields for the SELECT portion of an SQL query.
 =item cust_sql_fields [ CUST_FIELDS_VALUE ]
 
 Returns a list of fields for the SELECT portion of an SQL query.
@@ -337,19 +353,26 @@ sub cust_sql_fields {
   my @fields = qw( last first company );
 #  push @fields, map "ship_$_", @fields;
 
   my @fields = qw( last first company );
 #  push @fields, map "ship_$_", @fields;
 
-  cust_header(@_);
+  cust_header(@_) if( @_ or !@cust_fields );
   #inefficientish, but tiny lists and only run once per page
 
   my @location_fields;
   #inefficientish, but tiny lists and only run once per page
 
   my @location_fields;
-  foreach my $field (qw( address1 address2 city state zip )) {
+  foreach my $field (qw( address1 address2 city state zip latitude longitude )) {
     foreach my $pre ('bill_','ship_') {
       if ( grep { $_ eq $pre.$field } @cust_fields ) {
         push @location_fields, $pre.'location.'.$field.' AS '.$pre.$field;
       }
     }
   }
     foreach my $pre ('bill_','ship_') {
       if ( grep { $_ eq $pre.$field } @cust_fields ) {
         push @location_fields, $pre.'location.'.$field.' AS '.$pre.$field;
       }
     }
   }
-  
-  push @fields, 'payby' if grep { $_ eq 'payby'} @cust_fields;
+  foreach my $pre ('bill_','ship_') {
+    if ( grep { $_ eq $pre.'country_full' } @cust_fields ) {
+      push @location_fields, $pre.'locationnum';
+    }
+  }
+
+  foreach my $field (qw(daytime night mobile fax payby)) {
+    push @fields, $field if (grep { $_ eq $field } @cust_fields);
+  }
   push @fields, 'agent_custid';
 
   my @extra_fields = ();
   push @fields, 'agent_custid';
 
   my @extra_fields = ();
@@ -378,6 +401,9 @@ Otherwise, this function will assume the field is named "custnum".  If the
 argument isn't present at all, the join will just say "USING (custnum)", 
 which might work.
 
 argument isn't present at all, the join will just say "USING (custnum)", 
 which might work.
 
+As a special case, if TABLE is 'cust_main', only the joins to cust_location
+will be returned.
+
 LOCATION_TABLE is an optional table name to use for joining ship_location,
 in case your query also includes package information and you want the 
 "service address" columns to reflect package addresses.
 LOCATION_TABLE is an optional table name to use for joining ship_location,
 in case your query also includes package information and you want the 
 "service address" columns to reflect package addresses.
@@ -392,11 +418,12 @@ sub join_cust_main {
   ($location_table, $locationnum) = split(/\./, $location_table);
   $locationnum ||= 'locationnum';
 
   ($location_table, $locationnum) = split(/\./, $location_table);
   $locationnum ||= 'locationnum';
 
-  my $sql = ' LEFT JOIN cust_main ';
+  my $sql = '';
   if ( $cust_table ) {
   if ( $cust_table ) {
-    $sql .= "ON (cust_main.custnum = $cust_table.$custnum)";
+    $sql = " LEFT JOIN cust_main ON (cust_main.custnum = $cust_table.$custnum)"
+      unless $cust_table eq 'cust_main';
   } else {
   } else {
-    $sql .= "USING (custnum)";
+    $sql = " LEFT JOIN cust_main USING (custnum)";
   }
 
   if ( !@cust_fields or grep /^bill_/, @cust_fields ) {
   }
 
   if ( !@cust_fields or grep /^bill_/, @cust_fields ) {
@@ -466,25 +493,29 @@ element.
 
 sub cust_fields_subs {
   my $unlinked_warn = 0;
 
 sub cust_fields_subs {
   my $unlinked_warn = 0;
+
   return map { 
     my $f = $_;
   return map { 
     my $f = $_;
-    if( $unlinked_warn++ ) {
+    if ( $unlinked_warn++ ) {
+
       sub {
         my $record = shift;
       sub {
         my $record = shift;
-        if( $record->custnum ) {
-          $record->$f(@_);
-        }
-        else {
+        if ( $record->custnum ) {
+          encode_entities( $record->$f(@_) );
+        } else {
           '(unlinked)'
         };
           '(unlinked)'
         };
-      }
-    } 
-    else {
+      };
+
+    } else {
+
       sub {
         my $record = shift;
       sub {
         my $record = shift;
-        $record->$f(@_) if $record->custnum;
-      }
+        $record->custnum ? encode_entities( $record->$f(@_) ) : '';
+      };
+
     }
     }
+
   } @cust_fields;
 }
 
   } @cust_fields;
 }
 
@@ -549,6 +580,19 @@ sub cust_aligns {
   }
 }
 
   }
 }
 
+=item cust_links
+
+Returns an array of links to view/cust_main.cgi, for use with cust_fields.
+
+=cut
+
+sub cust_links {
+  my $link = [ FS::CGI::rooturl().'view/cust_main.cgi?', 'custnum' ];
+
+  return map { $_ eq 'cust_status_label' ? '' : $link }
+    @cust_fields;
+}
+
 =item is_mobile
 
 Utility function to determine if the client is a mobile browser.
 =item is_mobile
 
 Utility function to determine if the client is a mobile browser.
@@ -562,7 +606,11 @@ sub is_mobile {
   }
   return 0;
 }
   }
   return 0;
 }
-    
+
+=back
+
+=cut
+
 ###
 # begin JSRPC code...
 ###
 ###
 # begin JSRPC code...
 ###
@@ -574,7 +622,7 @@ use vars qw($DEBUG);
 use Carp;
 use Storable qw(nfreeze);
 use MIME::Base64;
 use Carp;
 use Storable qw(nfreeze);
 use MIME::Base64;
-use JSON;
+use JSON::XS;
 use FS::UID qw(getotaker);
 use FS::Record qw(qsearchs);
 use FS::queue;
 use FS::UID qw(getotaker);
 use FS::Record qw(qsearchs);
 use FS::queue;
@@ -671,6 +719,10 @@ sub start_job {
   
   #warn 'froze string of size '. length(nfreeze(\%param)). " for job args\n"
   #  if $DEBUG;
   
   #warn 'froze string of size '. length(nfreeze(\%param)). " for job args\n"
   #  if $DEBUG;
+  #
+  #  XXX FS::queue::insert knows how to do this.
+  #  not changing it here because that requires changing it everywhere else,
+  #  too, but we should eventually fix it
 
   my $error = $job->insert( '_JOB', encode_base64(nfreeze(\%param)) );
 
 
   my $error = $job->insert( '_JOB', encode_base64(nfreeze(\%param)) );
 
@@ -719,10 +771,7 @@ sub job_status {
     @return = ( 'error', $job ? $job->statustext : $jobnum );
   }
 
     @return = ( 'error', $job ? $job->statustext : $jobnum );
   }
 
-  #to_json(\@return);  #waiting on deb 5.0 for new JSON.pm?
-  #silence the warning though
-  my $to_json = JSON->can('to_json') || JSON->can('objToJson');
-  &$to_json(\@return);
+  encode_json \@return;
 
 }
 
 
 }