summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorivan <ivan>2010-10-14 01:14:27 +0000
committerivan <ivan>2010-10-14 01:14:27 +0000
commit7bbe939d139be7edd77378cfc9a2c3ec6287749d (patch)
tree4c43f97b729880ae191cc630d9402b843ca76870
parentf839709351aee1f9488e9a26496adc564aa5b8e5 (diff)
more contact work and preliminary business card upload
-rw-r--r--FS/FS/Mason.pm2
-rw-r--r--FS/FS/Misc.pm36
-rw-r--r--FS/FS/Schema.pm18
-rw-r--r--FS/FS/contact.pm133
-rw-r--r--FS/FS/contact_phone.pm2
-rw-r--r--FS/FS/cust_main.pm2
-rw-r--r--httemplate/edit/process/prospect_main.html7
-rw-r--r--httemplate/edit/prospect_main-ocr.html86
-rw-r--r--httemplate/edit/prospect_main-upload.html7
-rw-r--r--httemplate/edit/prospect_main.html44
-rw-r--r--httemplate/elements/contact.html55
-rw-r--r--httemplate/elements/menu.html4
-rw-r--r--httemplate/view/image.cgi31
13 files changed, 400 insertions, 27 deletions
diff --git a/FS/FS/Mason.pm b/FS/FS/Mason.pm
index 2282bc5..550ea1a 100644
--- a/FS/FS/Mason.pm
+++ b/FS/FS/Mason.pm
@@ -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 );
diff --git a/FS/FS/Misc.pm b/FS/FS/Misc.pm
index 0e8d92b..fe8ac60 100644
--- a/FS/FS/Misc.pm
+++ b/FS/FS/Misc.pm
@@ -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
diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm
index 6a987ce..a4c12aa 100644
--- a/FS/FS/Schema.pm
+++ b/FS/FS/Schema.pm
@@ -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' => [],
diff --git a/FS/FS/contact.pm b/FS/FS/contact.pm
index d3ab411..774aed0 100644
--- a/FS/FS/contact.pm
+++ b/FS/FS/contact.pm
@@ -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.
diff --git a/FS/FS/contact_phone.pm b/FS/FS/contact_phone.pm
index bb9cf03..ad8e8f7 100644
--- a/FS/FS/contact_phone.pm
+++ b/FS/FS/contact_phone.pm
@@ -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;
diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm
index 104a0c4..4e42f8d 100644
--- a/FS/FS/cust_main.pm
+++ b/FS/FS/cust_main.pm
@@ -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 );
diff --git a/httemplate/edit/process/prospect_main.html b/httemplate/edit/process/prospect_main.html
index 34d2642..ca4dfab 100644
--- a/httemplate/edit/process/prospect_main.html
+++ b/httemplate/edit/process/prospect_main.html
@@ -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
index 0000000..41fc4c1
--- /dev/null
+++ b/httemplate/edit/prospect_main-ocr.html
@@ -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
index 0000000..24b1caa
--- /dev/null
+++ b/httemplate/edit/prospect_main-upload.html
@@ -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') %>
diff --git a/httemplate/edit/prospect_main.html b/httemplate/edit/prospect_main.html
index e867907..c260eb8 100644
--- a/httemplate/edit/prospect_main.html
+++ b/httemplate/edit/prospect_main.html
@@ -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 ) = @_;
diff --git a/httemplate/elements/contact.html b/httemplate/elements/contact.html
index a7a33b1..eea3694 100644
--- a/httemplate/elements/contact.html
+++ b/httemplate/elements/contact.html
@@ -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>
diff --git a/httemplate/elements/menu.html b/httemplate/elements/menu.html
index 09b8e74..cc52aae 100644
--- a/httemplate/elements/menu.html
+++ b/httemplate/elements/menu.html
@@ -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
index 0000000..153ec85
--- /dev/null
+++ b/httemplate/view/image.cgi
@@ -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>