more contact work and preliminary business card upload
authorivan <ivan>
Thu, 14 Oct 2010 01:14:27 +0000 (01:14 +0000)
committerivan <ivan>
Thu, 14 Oct 2010 01:14:27 +0000 (01:14 +0000)
13 files changed:
FS/FS/Mason.pm
FS/FS/Misc.pm
FS/FS/Schema.pm
FS/FS/contact.pm
FS/FS/contact_phone.pm
FS/FS/cust_main.pm
httemplate/edit/process/prospect_main.html
httemplate/edit/prospect_main-ocr.html [new file with mode: 0644]
httemplate/edit/prospect_main-upload.html [new file with mode: 0644]
httemplate/edit/prospect_main.html
httemplate/elements/contact.html
httemplate/elements/menu.html
httemplate/view/image.cgi [new file with mode: 0644]

index 2282bc5..550ea1a 100644 (file)
@@ -126,7 +126,7 @@ if ( -e $addl_handler_use_file ) {
   use FS::UI::Web::small_custview qw(small_custview);
   use FS::UI::bytecount;
   use FS::Msgcat qw(gettext geterror);
-  use FS::Misc qw( send_email send_fax
+  use FS::Misc qw( send_email send_fax ocr_image
                    states_hash counties cities state_label
                  );
   use FS::Misc::eps2png qw( eps2png );
index 0e8d92b..fe8ac60 100644 (file)
@@ -20,6 +20,7 @@ use Tie::IxHash;
                  pkg_freqs
                  generate_ps generate_pdf do_print
                  csv_from_fixed
+                 ocr_image
                );
 
 $DEBUG = 0;
@@ -850,6 +851,41 @@ sub csv_from_fixed {
   '';
 }
 
+=item ocr_image IMAGE_SCALAR
+
+Runs OCR on the provided image data and returns a list of text lines.
+
+=cut
+
+sub ocr_image {
+  my $logo_data = shift;
+
+  #XXX use conf dir location from Makefile
+  my $dir = $FS::UID::conf_dir. "/cache.". $FS::UID::datasrc;
+  my $fh = new File::Temp(
+    TEMPLATE => 'bizcard.XXXXXXXX',
+    SUFFIX   => '.png', #XXX assuming, but should handle jpg, gif, etc. too
+    DIR      => $dir,
+    UNLINK   => 0,
+  ) or die "can't open temp file: $!\n";
+
+  my $filename = $fh->filename;
+
+  print $fh $logo_data;
+  close $fh;
+
+  run( [qw(ocroscript recognize), $filename], '>'=>"$filename.hocr" )
+    or die "ocroscript recognize failed\n";
+
+  run( [qw(ocroscript hocr-to-text), "$filename.hocr"], '>pipe'=>\*OUT )
+    or die "ocroscript hocr-to-text failed\n";
+
+  my @lines = split(/\n/, <OUT> );
+
+  foreach (@lines) { s/\.c0m\s*$/.com/; }
+
+  @lines;
+}
 
 =back
 
index 6a987ce..a4c12aa 100644 (file)
@@ -831,8 +831,8 @@ sub tables_hashref {
         'last',      'varchar',     '', $char_d, '', '', 
 #        'middle',    'varchar', 'NULL', $char_d, '', '', 
         'first',     'varchar',     '', $char_d, '', '', 
-        'title',     'varchar',     '', $char_d, '', '', #eg Head Bottle Washer
-        'comment',   'varchar',     '', $char_d, '', '', 
+        'title',     'varchar', 'NULL', $char_d, '', '', #eg Head Bottle Washer
+        'comment',   'varchar', 'NULL', $char_d, '', '', 
         'disabled',     'char', 'NULL',       1, '', '', 
       ],
       'primary_key' => 'contactnum',
@@ -844,13 +844,13 @@ sub tables_hashref {
 
     'contact_phone' => {
       'columns' => [
-        'contactphonenum', 'serial', '', '', '', '',
-        'contactnum',         'int', '', '', '', '',
-        'phonetypenum',       'int', '', '', '', '',
-        'countrycode',    'varchar', '',  3, '', '', 
-        'phonenum',       'varchar', '', 14, '', '', 
-        'extension',      'varchar', '',  7, '', '',
-        #?#'comment',   'varchar',     '', $char_d, '', '', 
+        'contactphonenum', 'serial',     '', '', '', '',
+        'contactnum',         'int',     '', '', '', '',
+        'phonetypenum',       'int',     '', '', '', '',
+        'countrycode',    'varchar',     '',  3, '', '', 
+        'phonenum',       'varchar',     '', 14, '', '', 
+        'extension',      'varchar', 'NULL',  7, '', '',
+        #?#'comment',        'varchar',     '', $char_d, '', '', 
       ],
       'primary_key' => 'contactphonenum',
       'unique'      => [],
index d3ab411..774aed0 100644 (file)
@@ -2,10 +2,12 @@ package FS::contact;
 
 use strict;
 use base qw( FS::Record );
-use FS::Record qw( qsearch qsearchs );
+use FS::Record qw( qsearch qsearchs dbh );
 use FS::prospect_main;
 use FS::cust_main;
 use FS::cust_location;
+use FS::contact_phone;
+use FS::contact_email;
 
 =head1 NAME
 
@@ -96,7 +98,59 @@ otherwise returns false.
 
 =cut
 
-# the insert method can be inherited from FS::Record
+sub insert {
+  my $self = shift;
+
+  local $SIG{INT} = 'IGNORE';
+  local $SIG{QUIT} = 'IGNORE';
+  local $SIG{TERM} = 'IGNORE';
+  local $SIG{TSTP} = 'IGNORE';
+  local $SIG{PIPE} = 'IGNORE';
+
+  my $oldAutoCommit = $FS::UID::AutoCommit;
+  local $FS::UID::AutoCommit = 0;
+  my $dbh = dbh;
+
+  my $error = $self->SUPER::insert;
+  if ( $error ) {
+    $dbh->rollback if $oldAutoCommit;
+    return $error;
+  }
+
+  foreach my $pf ( grep { /^phonetypenum(\d+)$/ && $self->get($_) =~ /\S/ }
+                        keys %{ $self->hashref } ) {
+    $pf =~ /^phonetypenum(\d+)$/ or die "wtf (daily, the)";
+    my $phonetypenum = $1;
+
+    my $contact_phone = new FS::contact_phone {
+      'contactnum' => $self->contactnum,
+      'phonetypenum' => $phonetypenum,
+      _parse_phonestring( $self->get($pf) ),
+    };
+    $error = $contact_phone->insert;
+    if ( $error ) {
+      $dbh->rollback if $oldAutoCommit;
+      return $error;
+    }
+  }
+
+  if ( $self->get('emailaddress') =~ /\S/ ) {
+    my $contact_email = new FS::contact_email {
+      'contactnum'   => $self->contactnum,
+      'emailaddress' => $self->get('emailaddress'),
+    };
+    $error = $contact_email->insert;
+    if ( $error ) {
+      $dbh->rollback if $oldAutoCommit;
+      return $error;
+    }
+  }
+
+  $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+  '';
+
+}
 
 =item delete
 
@@ -106,6 +160,8 @@ Delete this record from the database.
 
 # the delete method can be inherited from FS::Record
 
+# XXX delete contact_phone, contact_email
+
 =item replace OLD_RECORD
 
 Replaces the OLD_RECORD with this one in the database.  If there is an error,
@@ -113,7 +169,76 @@ returns the error, otherwise returns false.
 
 =cut
 
-# the replace method can be inherited from FS::Record
+sub replace {
+  my $self = shift;
+
+  local $SIG{INT} = 'IGNORE';
+  local $SIG{QUIT} = 'IGNORE';
+  local $SIG{TERM} = 'IGNORE';
+  local $SIG{TSTP} = 'IGNORE';
+  local $SIG{PIPE} = 'IGNORE';
+
+  my $oldAutoCommit = $FS::UID::AutoCommit;
+  local $FS::UID::AutoCommit = 0;
+  my $dbh = dbh;
+
+  my $error = $self->SUPER::replace(@_);
+  if ( $error ) {
+    $dbh->rollback if $oldAutoCommit;
+    return $error;
+  }
+
+  foreach my $pf ( grep { /^phonetypenum(\d+)$/ && $self->get($_) }
+                        keys %{ $self->hashref } ) {
+    $pf =~ /^phonetypenum(\d+)$/ or die "wtf (daily, the)";
+    my $phonetypenum = $1;
+
+    my %cp = ( 'contactnum'   => $self->contactnum,
+               'phonetypenum' => $phonetypenum,
+             );
+    my $contact_phone = qsearchs('contact_phone', \%cp)
+                        || new FS::contact_phone   \%cp;
+
+    my %cpd = _parse_phonestring( $self->get($pf) );
+    $contact_phone->set( $_ => $cpd{$_} ) foreach keys %cpd;
+
+    my $method = $contact_phone->contactphonenum ? 'replace' : 'insert';
+
+    $error = $contact_phone->$method;
+    if ( $error ) {
+      $dbh->rollback if $oldAutoCommit;
+      return $error;
+    }
+  }
+
+  $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+  '';
+
+}
+
+#i probably belong in contact_phone.pm
+sub _parse_phonestring {
+  my $value = shift;
+
+  my($countrycode, $extension) = ('1', '');
+
+  #countrycode
+  if ( $value =~ s/^\s*\+\s*(\d+)// ) {
+    $countrycode = $1;
+  } else {
+    $value =~ s/^\s*1//;
+  }
+  #extension
+  if ( $value =~ s/\s*(ext|x)\s*(\d+)\s*$//i ) {
+     $extension = $2;
+  }
+
+  ( 'countrycode' => $countrycode,
+    'phonenum'    => $value,
+    'extension'   => $extension,
+  );
+}
 
 =item check
 
@@ -165,8 +290,6 @@ sub line {
 
 =head1 BUGS
 
-The author forgot to customize this manpage.
-
 =head1 SEE ALSO
 
 L<FS::Record>, schema.html from the base documentation.
index bb9cf03..ad8e8f7 100644 (file)
@@ -120,7 +120,7 @@ sub check {
     || $self->ut_number('phonetypenum')
     || $self->ut_text('countrycode')
     || $self->ut_text('phonenum')
-    || $self->ut_text('extension')
+    || $self->ut_textn('extension')
   ;
   return $error if $error;
 
index 104a0c4..4e42f8d 100644 (file)
@@ -26,7 +26,7 @@ use Tie::IxHash;
 use Digest::MD5 qw(md5_base64);
 use Date::Format;
 #use Date::Manip;
-use File::Temp qw( tempfile );
+use File::Temp; #qw( tempfile );
 use Business::CreditCard 0.28;
 use Locale::Country;
 use FS::UID qw( getotaker dbh driver_name );
index 34d2642..ca4dfab 100644 (file)
@@ -4,7 +4,7 @@
      'agent_virt'     => 1,
      'process_o2m' => {
        'table'  => 'contact',
-       'fields' => [qw( first last title comment )],
+       'fields' => \@contact_fields,
      },
      'redirect' => popurl(3). 'view/prospect_main.html?',
    )
@@ -31,4 +31,9 @@ my $args_callback = sub {
 
 };
 
+my @contact_fields = qw( first last title comment emailaddress );
+foreach my $phone_type ( qsearch({table=>'phone_type', order_by=>'weight'}) ) {
+  push @contact_fields, 'phonetypenum'.$phone_type->phonetypenum;
+}
+
 </%init>
diff --git a/httemplate/edit/prospect_main-ocr.html b/httemplate/edit/prospect_main-ocr.html
new file mode 100644 (file)
index 0000000..41fc4c1
--- /dev/null
@@ -0,0 +1,86 @@
+<%  include("/elements/header.html", 'Upload business card' ) %>
+
+% if ( $error ) { 
+  <FONT SIZE="+1" COLOR="#ff0000">Error: <% $error %></FONT>
+  <BR><BR>
+% } else {
+
+    <FORM ACTION="prospect_main.html" METHOD="POST">
+    <INPUT TYPE="hidden" NAME="session" VALUE="<% $session %>">
+
+    <TABLE>
+
+%   my $num = 0;
+%   foreach my $line ( @lines ) { 
+      <TR>
+        <TD>
+          <INPUT TYPE="hidden" NAME="val<%$num%>" VALUE="<% $line |h %>">
+          <SELECT NAME="sel<%$num%>">
+            <OPTION VALUE="">
+            <OPTION VALUE="name">Name
+            <OPTION VALUE="contactnum0_title">Title
+            <OPTION VALUE="company">Company
+            <OPTION VALUE="contactnum0_emailaddress">Email
+            <OPTION VALUE="address1">Address (1)
+            <OPTION VALUE="address2">Address (2)
+            <OPTION VALUE="city_state_zip">City, State, Zip
+%           my @phone_types = qsearch({table=>'phone_type',order_by=>'weight'});
+%           foreach my $phone_type ( @phone_types ) {
+%             next if $phone_type->typename eq 'Home';
+              <OPTION VALUE="contactnum0_phonetypenum<% $phone_type->phonetypenum %>"><% $phone_type->typename |h %> phone
+%           }
+            <OPTION VALUE="contactnum0_comment">Comment
+          </SELECT>
+        </TD>
+        <TD><% $line %></TD>
+
+%       unless ( $num++) {
+
+          <TD ROWSPAN="9999"><IMG SRC="<%$p%>view/image.cgi?type=png;prefname=bizcard<%$session%>" WIDTH=604 HEIGHT=328></IMG></TD>
+
+%       }
+
+      </TR>
+%   }
+
+    </TABLE>
+
+    <BR>
+    <INPUT TYPE="submit" VALUE="Create prospect">
+
+% }
+<% include('/elements/footer.html') %>
+<%init>
+
+my $fh = $cgi->upload('card');
+
+my $error = '';
+my @lines = ();
+my $session = '';
+if ( defined $fh ) {
+
+  local $/;
+  my $logo_data = <$fh>;
+
+  $session = int(rand(4294967296)); #XXX
+  my $pref = new FS::access_user_pref({
+    'usernum'    => $FS::CurrentUser::CurrentUser->usernum,
+    'prefname'   => "bizcard$session",
+    'prefvalue'  => encode_base64($logo_data),
+    'expiration' => time + 3600, #1h?  1m?
+  });
+  my $pref_error = $pref->insert;
+  if ( $pref_error ) {
+    die "FATAL: couldn't set preview cookie: $pref_error\n";
+  }
+
+  @lines = eval { ocr_image($logo_data); };
+  $error = $@ if $error;
+
+} else {
+
+  $error = 'No file uploaded';
+
+}
+
+</%init>
diff --git a/httemplate/edit/prospect_main-upload.html b/httemplate/edit/prospect_main-upload.html
new file mode 100644 (file)
index 0000000..24b1caa
--- /dev/null
@@ -0,0 +1,7 @@
+<%  include("/elements/header.html", 'Upload business card' ) %>
+
+  <FORM ACTION="prospect_main-ocr.html" METHOD="POST" ENCTYPE="multipart/form-data">
+  <INPUT TYPE="file" NAME="card">
+  <BR><INPUT TYPE="submit" NAME="submit" VALUE="Upload">
+
+<% include('/elements/footer.html') %>
index e867907..c260eb8 100644 (file)
@@ -5,6 +5,7 @@
                             'agentnum'    => 'Agent',
                             'company'     => 'Company',
                             'contactnum'  => 'Contact',
+                            'locationnum' => '&nbsp;',
                           },
      'fields'          => [
        { 'field'       => 'agentnum',
@@ -34,6 +35,7 @@
          'empty_label' => 'No address',
        },
      ],
+     'new_callback'    => $new_callback,
      'edit_callback'   => $edit_callback,
      'error_callbacck' => $error_callback,
      'agent_virt'      => 1,
@@ -62,6 +64,48 @@ if ( $cgi->param('error') ) {
 
 }
 
+my $new_callback = sub {
+  my( $cgi, $prospect_main, $fields_listref, $opt_hashref ) = @_;
+
+  if ( $cgi->param('session') =~ /^(\w+)$/ ) {
+    my $session = $1;
+
+    #add a link to the image.cgi for this card
+    $opt_hashref->{'html_bottom'} .=
+      qq(<BR><IMG SRC="${p}view/image.cgi?type=png;prefname=bizcard$session" ).
+      ' WIDTH=604 HEIGHT=328><BR>';
+
+    #fill in the incoming params: name, address1/address2, city_state_zip
+    foreach my $param ( grep /^sel\d+$/, $cgi->param ) {
+      $param =~ /^sel(\d+)$/ or die 'again, wtf (daily)';
+      my $num = $1;
+      my $field = $cgi->param($param);
+      my $value = $cgi->param("val$num");
+      $cgi->param($field => $value);
+    }
+
+    if ( $cgi->param('company') ) {
+      $prospect_main->company( $cgi->param('company') );
+    }
+
+    if ( $cgi->param('name') =~ /^(.*\S+)\s+(\w+)\s*$/ ) {
+      $cgi->param('contactnum0_first' => $1);
+      $cgi->param('contactnum0_last'  => $2);
+    }
+
+    if ( grep $cgi->param($_), qw( address1 address2 city_state_zip ) ) {
+      $cgi->param('locationnum', -1);
+      if ( $cgi->param('city_state_zip') =~ /^(\s*)([\w\s]+)[\., ]+(\w{2})[, ]+(\d{5}(-\d{4})?)/ ) {
+         $cgi->param('city'  => $2);
+         $cgi->param('state' => $3);
+         $cgi->param('zip'   => $4);
+      }
+    }
+
+  }
+
+};
+
 my $edit_callback = sub {
   #my( $cgi, $prospect_main, $fields_listref, $opt_hashref ) = @_;
   my( $cgi, $prospect_main ) = @_;
index a7a33b1..eea3694 100644 (file)
@@ -5,12 +5,38 @@
   <TABLE>
     <TR>
 %     foreach my $field ( @fields ) {
+%
+%       my $value = '';
+%       if ( $field =~ /^phonetypenum(\d+)$/ ) {
+%         my $contact_phone = qsearchs('contact_phone', {
+%           'contactnum'   => $curr_value,
+%           'phonetypenum' => $1,
+%         });
+%         if ( $contact_phone ) {
+%           $value = $contact_phone->phonenum;
+%           $value .= 'x'.$contact_phone->extension
+%             if $contact_phone->extension;
+%           $value = '+'. $contact_phone->countrycode. " $value"
+%             if $contact_phone->countrycode
+%             && $contact_phone->countrycode ne '1';
+%         }
+%       } elsif ( $field eq 'emailaddress' ) {
+%         #XXX multiple not yet supported
+%         my $contact_email = qsearchs('contact_email', {
+%           'contactnum' => $curr_value,
+%         });
+%         $value = $contact_email->emailaddress if $contact_email;
+%       } else {
+%         $value = $contact->get($field);
+%       }
+
         <TD>
-          <INPUT TYPE = "text"
-                 NAME = "<%$name%>_<%$field%>"
-                 ID   = "<%$id%>_<%$field%>"
+          <INPUT TYPE  = "text"
+                 NAME  = "<%$name%>_<%$field%>"
+                 ID    = "<%$id%>_<%$field%>"
+                 SIZE  = "<% $size{$field} || 15 %>"
                  VALUE = "<% scalar($cgi->param($name."_$field"))
-                             || $contact->get($field) |h %>"
+                             || $value |h %>"
                  <% $onchange %>
           ><BR>
           <FONT SIZE="-1"><% $label{$field} %></FONT>
@@ -45,12 +71,25 @@ if ( $curr_value ) {
   $contact = new FS::contact {};
 }
 
+my %size = ( 'title' => 12 );
+
 tie my %label, 'Tie::IxHash',
-  'first'  => 'First name',
-  'last'    => 'Last name',
-  'title'   => 'Title/Position',
-  'comment' => 'Comment',
+  'first'        => 'First name',
+  'last'         => 'Last name',
+  'title'        => 'Title/Position',
+  'emailaddress' => 'Email',
 ;
+
+my $first = 0;
+foreach my $phone_type ( qsearch({table=>'phone_type', order_by=>'weight'}) ) {
+  next if $phone_type->typename eq 'Home';
+  my $f = 'phonetypenum'.$phone_type->phonetypenum;
+  $label{$f} = $phone_type->typename. ' phone';
+  $size{$f} = $first++ ? 11 : 15;
+}
+
+$label{'comment'} = 'Comment';
+
 my @fields = keys %label;
 
 </%init>
index 09b8e74..cc52aae 100644 (file)
@@ -364,11 +364,13 @@ $tools_menu{'Process payment batches'} = [ $fsurl.'search/pay_batch.cgi?magic=_d
   if ( $conf->exists('batch-enable') || $conf->config('batch-enable_payby') )
      && $curuser->access_right('Process batches');
 $tools_menu{'Process invoice batches'} = [ $fsurl.'search/bill_batch.cgi' ] 
-  if ( $conf->exists('invoice_print_pdf') );
+  if $conf->exists('invoice_print_pdf');
 $tools_menu{'Job Queue'} =  [ $fsurl.'search/queue.html', 'View pending job queue' ]
   if $curuser->access_right('Job queue');
 $tools_menu{'Ticketing'} = [ \%tools_ticketing, 'Ticketing tools' ]
   if $conf->config('ticket_system');
+$tools_menu{'Business card scan'} = [ $fsurl.'edit/prospect_main-upload.html' ]
+  if $curuser->access_right('New prospect');
 $tools_menu{'Time Queue'} =  [ $fsurl.'search/report_timeworked.html', 'View pending support time' ]
   if $curuser->access_right('Time queue');
 $tools_menu{'Attachments'} = [ $fsurl.'browse/cust_attachment.html', 'View customer attachments' ]
diff --git a/httemplate/view/image.cgi b/httemplate/view/image.cgi
new file mode 100644 (file)
index 0000000..153ec85
--- /dev/null
@@ -0,0 +1,31 @@
+<% $data %>\
+<%init>
+
+#die "access denied"
+#  unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my $conf = new FS::Conf;
+
+my $type;
+if ( $cgi->param('type') eq 'png' ) {
+  $type = 'png';
+} elsif ( $cgi->param('type') eq 'eps' ) {
+  $type = 'eps';
+} else {
+  die "unknown image type ". $cgi->param('type');
+}
+
+my $data;
+if ( $cgi->param('prefname') =~ /^(\w+)$/ ) {
+
+  my $prefname = $1;
+  my $curuser = $FS::CurrentUser::CurrentUser;
+  $data = decode_base64( $curuser->option("$prefname") );
+
+} else {
+  die "no preview_session specified";
+}
+
+http_header('Content-Type' => 'image/png' );
+
+</%init>