summaryrefslogtreecommitdiff
path: root/FS
diff options
context:
space:
mode:
Diffstat (limited to 'FS')
-rw-r--r--FS/FS.pm12
-rw-r--r--FS/FS/AccessRight.pm14
-rw-r--r--FS/FS/Mason.pm2
-rw-r--r--FS/FS/Schema.pm78
-rw-r--r--FS/FS/Setup.pm5
-rw-r--r--FS/FS/Upgrade.pm3
-rw-r--r--FS/FS/contact.pm177
-rw-r--r--FS/FS/contact_email.pm128
-rw-r--r--FS/FS/contact_phone.pm143
-rw-r--r--FS/FS/cust_location.pm19
-rw-r--r--FS/FS/o2m_Common.pm152
-rw-r--r--FS/FS/phone_type.pm137
-rw-r--r--FS/FS/prospect_main.pm292
-rw-r--r--FS/MANIFEST11
-rw-r--r--FS/t/contact.t5
-rw-r--r--FS/t/contact_email.t5
-rw-r--r--FS/t/contact_phone.t5
-rw-r--r--FS/t/phone_type.t5
-rw-r--r--FS/t/prospect_main.t5
19 files changed, 1187 insertions, 11 deletions
diff --git a/FS/FS.pm b/FS/FS.pm
index c6f9eceaf..13dc04f9e 100644
--- a/FS/FS.pm
+++ b/FS/FS.pm
@@ -232,9 +232,19 @@ L<FS::reason> - Reason class
L<FS::cust_pkg_reason> - Package reason class
+L<FS::contact> - Contact class
+
+L<FS::contact_phone> - Contact phone class
+
+L<FS::phone_type> - Phone type class
+
+L<FS::contact_email> - Contact email class
+
+L<FS::prospect_main> - Prospect class
+
L<FS::cust_main> - Customer class
-L<FS::cust_main_location> - Customer location class
+L<FS::cust_location> - Customer location class
L<FS::cust_main_Mixin> - Mixin class for records that contain fields from cust_main
diff --git a/FS/FS/AccessRight.pm b/FS/FS/AccessRight.pm
index 44235b113..8e771a9c0 100644
--- a/FS/FS/AccessRight.pm
+++ b/FS/FS/AccessRight.pm
@@ -85,6 +85,20 @@ assigned to users and/or groups.
#well, this is what we have for now. getting better.
tie my %rights, 'Tie::IxHash',
+
+ ###
+ # contact rights
+ ###
+ 'Contact and Prospect rights' => [
+ 'New prospect',
+ 'View prospect',
+ 'Edit prospect',
+ 'List prospects',
+ 'Edit contact', #!
+ #'New contact',
+ #'View customer contacts',
+ #'List contacts',
+ ],
###
# basic customer rights
diff --git a/FS/FS/Mason.pm b/FS/FS/Mason.pm
index bef005881..cc15cd4d9 100644
--- a/FS/FS/Mason.pm
+++ b/FS/FS/Mason.pm
@@ -224,6 +224,8 @@ if ( -e $addl_handler_use_file ) {
use FS::cust_statement;
use FS::cust_class;
use FS::cust_category;
+ use FS::prospect_main;
+ use FS::contact;
# Sammath Naur
if ( $FS::Mason::addl_handler_use ) {
diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm
index 7c6548a89..a039003c7 100644
--- a/FS/FS/Schema.pm
+++ b/FS/FS/Schema.pm
@@ -800,12 +800,84 @@ sub tables_hashref {
'index' => [],
},
+ #eventually for cust_main too
+ 'contact' => {
+ 'columns' => [
+ 'contactnum', 'serial', '', '', '', '',
+ 'prospectnum', 'int', 'NULL', '', '', '',
+ 'custnum', 'int', 'NULL', '', '', '',
+ 'locationnum', 'int', 'NULL', '', '', '', #not yet
+# 'titlenum', 'int', 'NULL', '', '', '', #eg Mr. Mrs. Dr. Rev.
+ '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, '', '',
+ 'disabled', 'char', 'NULL', 1, '', '',
+ ],
+ 'primary_key' => 'contactnum',
+ 'unique' => [],
+ 'index' => [ [ 'prospectnum' ], [ 'custnum' ], [ 'locationnum' ],
+ [ 'last' ], [ 'first' ],
+ ],
+ },
+
+ 'contact_phone' => {
+ 'columns' => [
+ 'contactphonenum', 'serial', '', '', '', '',
+ 'contactnum', 'int', '', '', '', '',
+ 'phonetypenum', 'int', '', '', '', '',
+ 'countrycode', 'varchar', '', 3, '', '',
+ 'phonenum', 'varchar', '', 14, '', '',
+ 'extension', 'varchar', '', 7, '', '',
+ #?#'comment', 'varchar', '', $char_d, '', '',
+ ],
+ 'primary_key' => 'contactphonenum',
+ 'unique' => [],
+ 'index' => [],
+ },
+
+ 'phone_type' => {
+ 'columns' => [
+ 'phonetypenum', 'serial', '', '', '', '',
+ 'typename', 'varchar', '', $char_d, '', '',
+ 'weight', 'int', '', '', '', '',
+ ],
+ 'primary_key' => 'phonetypenum',
+ 'unique' => [ [ 'typename' ], ],
+ 'index' => [],
+ },
+
+ 'contact_email' => {
+ 'columns' => [
+ 'contactemailnum', 'serial', '', '', '', '',
+ 'contactnum', 'int', '', '', '', '',
+ 'emailaddress', 'varchar', '', $char_d, '', '',
+ ],
+ 'primary_key' => 'contactemailnum',
+ 'unique' => [ [ 'emailaddress' ], ],
+ 'index' => [],
+ },
+
+ 'prospect_main' => {
+ 'columns' => [
+ 'prospectnum', 'serial', '', '', '', '',
+ 'agentnum', 'int', '', '', '', '',
+ 'company', 'varchar', '', $char_d, '', '',
+ #'disabled', 'char', 'NULL', 1, '', '',
+ ],
+ 'primary_key' => 'prospectnum',
+ 'unique' => [],
+ 'index' => [ [ 'company' ], [ 'agentnum' ], ],
+ },
+
#eventually use for billing & ship from cust_main too
#for now, just cust_pkg locations
- 'cust_location' => {
+ 'cust_location' => { #'location' now that its prospects too, but...
'columns' => [
'locationnum', 'serial', '', '', '', '',
- 'custnum', 'int', '', '', '', '',
+ 'prospectnum', 'int', 'NULL', '', '', '',
+ 'custnum', 'int', 'NULL', '', '', '',
'address1', 'varchar', '', $char_d, '', '',
'address2', 'varchar', 'NULL', $char_d, '', '',
'city', 'varchar', '', $char_d, '', '',
@@ -817,7 +889,7 @@ sub tables_hashref {
],
'primary_key' => 'locationnum',
'unique' => [],
- 'index' => [ [ 'custnum' ],
+ 'index' => [ [ 'prospectnum' ], [ 'custnum' ],
[ 'county' ], [ 'state' ], [ 'country' ], [ 'zip' ],
],
},
diff --git a/FS/FS/Setup.pm b/FS/FS/Setup.pm
index edfe912ea..f8c59c5da 100644
--- a/FS/FS/Setup.pm
+++ b/FS/FS/Setup.pm
@@ -348,11 +348,12 @@ sub initial_data {
#not yet....
- #)
-
#usage classes
'usage_class' => [],
+ #phone types
+ 'phone_type' => [],
+
;
\%hash;
diff --git a/FS/FS/Upgrade.pm b/FS/FS/Upgrade.pm
index c39680ef7..8cc5c61b8 100644
--- a/FS/FS/Upgrade.pm
+++ b/FS/FS/Upgrade.pm
@@ -126,6 +126,9 @@ sub upgrade_data {
#usage_classes if we have none
'usage_class' => [],
+ #phone_type if we have none
+ 'phone_type' => [],
+
#fixup access rights
'access_right' => [],
diff --git a/FS/FS/contact.pm b/FS/FS/contact.pm
new file mode 100644
index 000000000..d3ab4118a
--- /dev/null
+++ b/FS/FS/contact.pm
@@ -0,0 +1,177 @@
+package FS::contact;
+
+use strict;
+use base qw( FS::Record );
+use FS::Record qw( qsearch qsearchs );
+use FS::prospect_main;
+use FS::cust_main;
+use FS::cust_location;
+
+=head1 NAME
+
+FS::contact - Object methods for contact records
+
+=head1 SYNOPSIS
+
+ use FS::contact;
+
+ $record = new FS::contact \%hash;
+ $record = new FS::contact { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::contact object represents an example. FS::contact inherits from
+FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item contactnum
+
+primary key
+
+=item prospectnum
+
+prospectnum
+
+=item custnum
+
+custnum
+
+=item locationnum
+
+locationnum
+
+=item last
+
+last
+
+=item first
+
+first
+
+=item title
+
+title
+
+=item comment
+
+comment
+
+=item disabled
+
+disabled
+
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new example. To add the example to the database, see L<"insert">.
+
+Note that this stores the hash reference, not a distinct copy of the hash it
+points to. You can ask the object for a copy with the I<hash> method.
+
+=cut
+
+# the new method can be inherited from FS::Record, if a table method is defined
+
+sub table { 'contact'; }
+
+=item insert
+
+Adds this record to the database. If there is an error, returns the error,
+otherwise returns false.
+
+=cut
+
+# the insert method can be inherited from FS::Record
+
+=item delete
+
+Delete this record from the database.
+
+=cut
+
+# the delete method can be inherited from FS::Record
+
+=item replace OLD_RECORD
+
+Replaces the OLD_RECORD with this one in the database. If there is an error,
+returns the error, otherwise returns false.
+
+=cut
+
+# the replace method can be inherited from FS::Record
+
+=item check
+
+Checks all fields to make sure this is a valid example. If there is
+an error, returns the error, otherwise returns false. Called by the insert
+and replace methods.
+
+=cut
+
+# the check method should currently be supplied - FS::Record contains some
+# data checking routines
+
+sub check {
+ my $self = shift;
+
+ my $error =
+ $self->ut_numbern('contactnum')
+ || $self->ut_foreign_keyn('prospectnum', 'prospect_main', 'prospectnum')
+ || $self->ut_foreign_keyn('custnum', 'cust_main', 'custnum')
+ || $self->ut_foreign_keyn('locationnum', 'cust_location', 'locationnum')
+ || $self->ut_textn('last')
+ || $self->ut_textn('first')
+ || $self->ut_textn('title')
+ || $self->ut_textn('comment')
+ || $self->ut_enum('disabled', [ '', 'Y' ])
+ ;
+ return $error if $error;
+
+ return "No prospect or customer!" unless $self->prospectnum || $self->custnum;
+ return "Prospect and customer!" if $self->prospectnum && $self->custnum;
+
+ return "One of first name, last name, or title must have a value"
+ if ! grep $self->$_(), qw( first last title);
+
+ $self->SUPER::check;
+}
+
+sub line {
+ my $self = shift;
+ my $data = $self->first. ' '. $self->last;
+ $data .= ', '. $self->title
+ if $self->title;
+ $data .= ' ('. $self->comment. ')'
+ if $self->comment;
+ $data;
+}
+
+=back
+
+=head1 BUGS
+
+The author forgot to customize this manpage.
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/contact_email.pm b/FS/FS/contact_email.pm
new file mode 100644
index 000000000..1276d8d68
--- /dev/null
+++ b/FS/FS/contact_email.pm
@@ -0,0 +1,128 @@
+package FS::contact_email;
+
+use strict;
+use base qw( FS::Record );
+use FS::Record qw( qsearch qsearchs );
+
+=head1 NAME
+
+FS::contact_email - Object methods for contact_email records
+
+=head1 SYNOPSIS
+
+ use FS::contact_email;
+
+ $record = new FS::contact_email \%hash;
+ $record = new FS::contact_email { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::contact_email object represents an example. FS::contact_email inherits from
+FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item contactemailnum
+
+primary key
+
+=item contactnum
+
+contactnum
+
+=item emailaddress
+
+emailaddress
+
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new example. To add the example to the database, see L<"insert">.
+
+Note that this stores the hash reference, not a distinct copy of the hash it
+points to. You can ask the object for a copy with the I<hash> method.
+
+=cut
+
+# the new method can be inherited from FS::Record, if a table method is defined
+
+sub table { 'contact_email'; }
+
+=item insert
+
+Adds this record to the database. If there is an error, returns the error,
+otherwise returns false.
+
+=cut
+
+# the insert method can be inherited from FS::Record
+
+=item delete
+
+Delete this record from the database.
+
+=cut
+
+# the delete method can be inherited from FS::Record
+
+=item replace OLD_RECORD
+
+Replaces the OLD_RECORD with this one in the database. If there is an error,
+returns the error, otherwise returns false.
+
+=cut
+
+# the replace method can be inherited from FS::Record
+
+=item check
+
+Checks all fields to make sure this is a valid example. If there is
+an error, returns the error, otherwise returns false. Called by the insert
+and replace methods.
+
+=cut
+
+# the check method should currently be supplied - FS::Record contains some
+# data checking routines
+
+sub check {
+ my $self = shift;
+
+ my $error =
+ $self->ut_numbern('contactemailnum')
+ || $self->ut_number('contactnum')
+ || $self->ut_text('emailaddress')
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=back
+
+=head1 BUGS
+
+The author forgot to customize this manpage.
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/contact_phone.pm b/FS/FS/contact_phone.pm
new file mode 100644
index 000000000..bb9cf034d
--- /dev/null
+++ b/FS/FS/contact_phone.pm
@@ -0,0 +1,143 @@
+package FS::contact_phone;
+
+use strict;
+use base qw( FS::Record );
+use FS::Record qw( qsearch qsearchs );
+
+=head1 NAME
+
+FS::contact_phone - Object methods for contact_phone records
+
+=head1 SYNOPSIS
+
+ use FS::contact_phone;
+
+ $record = new FS::contact_phone \%hash;
+ $record = new FS::contact_phone { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::contact_phone object represents an example. FS::contact_phone inherits from
+FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item contactphonenum
+
+primary key
+
+=item contactnum
+
+contactnum
+
+=item phonetypenum
+
+phonetypenum
+
+=item countrycode
+
+countrycode
+
+=item phonenum
+
+phonenum
+
+=item extension
+
+extension
+
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new example. To add the example to the database, see L<"insert">.
+
+Note that this stores the hash reference, not a distinct copy of the hash it
+points to. You can ask the object for a copy with the I<hash> method.
+
+=cut
+
+# the new method can be inherited from FS::Record, if a table method is defined
+
+sub table { 'contact_phone'; }
+
+=item insert
+
+Adds this record to the database. If there is an error, returns the error,
+otherwise returns false.
+
+=cut
+
+# the insert method can be inherited from FS::Record
+
+=item delete
+
+Delete this record from the database.
+
+=cut
+
+# the delete method can be inherited from FS::Record
+
+=item replace OLD_RECORD
+
+Replaces the OLD_RECORD with this one in the database. If there is an error,
+returns the error, otherwise returns false.
+
+=cut
+
+# the replace method can be inherited from FS::Record
+
+=item check
+
+Checks all fields to make sure this is a valid example. If there is
+an error, returns the error, otherwise returns false. Called by the insert
+and replace methods.
+
+=cut
+
+# the check method should currently be supplied - FS::Record contains some
+# data checking routines
+
+sub check {
+ my $self = shift;
+
+ my $error =
+ $self->ut_numbern('contactphonenum')
+ || $self->ut_number('contactnum')
+ || $self->ut_number('phonetypenum')
+ || $self->ut_text('countrycode')
+ || $self->ut_text('phonenum')
+ || $self->ut_text('extension')
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=back
+
+=head1 BUGS
+
+The author forgot to customize this manpage.
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/cust_location.pm b/FS/FS/cust_location.pm
index 87c6c3eb6..da586f00c 100644
--- a/FS/FS/cust_location.pm
+++ b/FS/FS/cust_location.pm
@@ -4,6 +4,7 @@ use strict;
use base qw( FS::Record );
use Locale::Country;
use FS::Record qw( qsearch ); #qsearchs );
+use FS::prospect_main;
use FS::cust_main;
use FS::cust_main_county;
@@ -119,7 +120,8 @@ sub check {
my $error =
$self->ut_numbern('locationnum')
- || $self->ut_foreign_key('custnum', 'cust_main', 'custnum')
+ || $self->ut_foreign_keyn('prospectnum', 'prospect_main', 'prospectnum')
+ || $self->ut_foreign_keyn('custnum', 'cust_main', 'custnum')
|| $self->ut_text('address1')
|| $self->ut_textn('address2')
|| $self->ut_text('city')
@@ -131,6 +133,9 @@ sub check {
;
return $error if $error;
+ return "No prospect or customer!" unless $self->prospectnum || $self->custnum;
+ return "Prospect and customer!" if $self->prospectnum && $self->custnum;
+
unless ( qsearch('cust_main_county', {
'country' => $self->country,
'state' => '',
@@ -187,8 +192,10 @@ sub location_label {
my $separator = $opt{join_string} || ', ';
my $escape = $opt{escape_function} || sub{ shift };
+ my $ds = $opt{double_space} || ' ';
my $line = '';
- my $cydefault = FS::conf->new->config('countrydefault') || 'US';
+ my $cydefault =
+ $opt{'countrydefault'} || FS::Conf->new->config('countrydefault') || 'US';
my $prefix = '';
my $notfirst = 0;
@@ -202,11 +209,13 @@ sub location_label {
foreach (qw ( city county state zip ) ) {
my $method = "$prefix$_";
if ( $self->$method ) {
- $line .= ' (' if $method eq 'county';
- $line .= ($notfirst ? ' ' : $separator). &$escape($self->$method);
- $line .= ' )' if $method eq 'county';
+ $line .= ($notfirst ? ($method eq 'zip' ? $ds : ' ') : $separator);
+ $line .= '(' if $method eq 'county';
+ $line .= &$escape($self->$method);
+ $line .= ')' if $method eq 'county';
$notfirst++;
}
+ $line .= ',' if $method eq 'county';
}
$line .= $separator. &$escape(code2country($self->country))
if $self->country ne $cydefault;
diff --git a/FS/FS/o2m_Common.pm b/FS/FS/o2m_Common.pm
new file mode 100644
index 000000000..0e03b52ee
--- /dev/null
+++ b/FS/FS/o2m_Common.pm
@@ -0,0 +1,152 @@
+package FS::o2m_Common;
+
+use strict;
+use vars qw( $DEBUG $me );
+use Carp;
+use FS::Schema qw( dbdef );
+use FS::Record qw( qsearch qsearchs dbh );
+
+$DEBUG = 0;
+
+$me = '[FS::o2m_Common]';
+
+=head1 NAME
+
+FS::o2m_Common - Mixin class for tables with a related table
+
+=head1 SYNOPSIS
+
+use FS::o2m_Common;
+
+@ISA = qw( FS::o2m_Common FS::Record );
+
+=head1 DESCRIPTION
+
+FS::o2m_Common is intended as a mixin class for classes which have a
+related table.
+
+=head1 METHODS
+
+=over 4
+
+=item process_o2m OPTION => VALUE, ...
+
+Available options:
+
+table (required) - Table into which the records are inserted.
+
+num_col (optional) - Column in table which links to the primary key of the base table. If not specified, it is assumed this has the same name.
+
+params (required) - Hashref of keys and values, often passed as C<scalar($cgi->Vars)> from a form.
+
+fields (required) - Arrayref of field names for each record in table. Pulled from params as "pkeyNN_field" where pkey is table's primary key and NN is the entry's numeric identifier.
+
+=cut
+
+#a little more false laziness w/m2m_Common.pm than m2_name_Common.pm
+# still, far from the worse of it. at least we're a reuable mixin!
+sub process_o2m {
+ my( $self, %opt ) = @_;
+
+ my $self_pkey = $self->dbdef_table->primary_key;
+ my $link_sourcekey = $opt{'num_col'} || $self_pkey;
+
+ my $hashref = {}; #$opt{'hashref'} || {};
+ $hashref->{$link_sourcekey} = $self->$self_pkey();
+
+ my $table = $self->_load_table($opt{'table'});
+ my $table_pkey = dbdef->table($table)->primary_key;
+
+# my $link_static = $opt{'link_static'} || {};
+
+ warn "$me processing o2m from ". $self->table. ".$link_sourcekey".
+ " to $table\n"
+ if $DEBUG;
+
+ #if ( ref($opt{'params'}) eq 'ARRAY' ) {
+ # $opt{'params'} = { map { $_=>1 } @{$opt{'params'}} };
+ #}
+
+ local $SIG{HUP} = 'IGNORE';
+ 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 @fields = grep { /^$table_pkey\d+$/ }
+ keys %{ $opt{'params'} };
+
+ my %edits = map { $opt{'params'}->{$_} => $_ }
+ grep { $opt{'params'}->{$_} }
+ @fields;
+
+ foreach my $del_obj (
+ grep { ! $edits{$_->$table_pkey()} }
+ qsearch( $table, $hashref )
+ ) {
+ my $error = $del_obj->delete;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
+ foreach my $pkey_value ( keys %edits ) {
+ my $old_obj = qsearchs( $table, { %$hashref, $table_pkey => $pkey_value } ),
+ my $add_param = $edits{$pkey_value};
+ my %hash = ( $table_pkey => $pkey_value,
+ map { $_ => $opt{'params'}->{$add_param."_$_"} }
+ @{ $opt{'fields'} }
+ );
+ #next unless grep { $_ =~ /\S/ } values %hash;
+
+ my $new_obj = "FS::$table"->new( { %$hashref, %hash } );
+ my $error = $new_obj->replace($old_obj);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
+ foreach my $add_param ( grep { ! $opt{'params'}->{$_} } @fields ) {
+
+ my %hash = map { $_ => $opt{'params'}->{$add_param."_$_"} }
+ @{ $opt{'fields'} };
+ next unless grep { $_ =~ /\S/ } values %hash;
+
+ my $add_obj = "FS::$table"->new( { %$hashref, %hash } );
+ my $error = $add_obj->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+ '';
+}
+
+sub _load_table {
+ my( $self, $table ) = @_;
+ eval "use FS::$table";
+ die $@ if $@;
+ $table;
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::Record>
+
+=cut
+
+1;
+
diff --git a/FS/FS/phone_type.pm b/FS/FS/phone_type.pm
new file mode 100644
index 000000000..d2ef465bd
--- /dev/null
+++ b/FS/FS/phone_type.pm
@@ -0,0 +1,137 @@
+package FS::phone_type;
+
+use strict;
+use base qw( FS::Record );
+use FS::Record qw( qsearch ); # qsearchs );
+
+=head1 NAME
+
+FS::phone_type - Object methods for phone_type records
+
+=head1 SYNOPSIS
+
+ use FS::phone_type;
+
+ $record = new FS::phone_type \%hash;
+ $record = new FS::phone_type { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::phone_type object represents an phone number type (for example: Work,
+Home, Mobile, Fax). FS::phone_type inherits from FS::Record. The following
+fields are currently supported:
+
+=over 4
+
+=item phonetypenum
+
+Primary key
+
+=item typename
+
+Type name
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new type. To add the type to the database, see L<"insert">.
+
+Note that this stores the hash reference, not a distinct copy of the hash it
+points to. You can ask the object for a copy with the I<hash> method.
+
+=cut
+
+sub table { 'phone_type'; }
+
+=item insert
+
+Adds this record to the database. If there is an error, returns the error,
+otherwise returns false.
+
+=item delete
+
+Delete this record from the database.
+
+=item replace OLD_RECORD
+
+Replaces the OLD_RECORD with this one in the database. If there is an error,
+returns the error, otherwise returns false.
+
+=item check
+
+Checks all fields to make sure this is a valid type. If there is
+an error, returns the error, otherwise returns false. Called by the insert
+and replace methods.
+
+=cut
+
+sub check {
+ my $self = shift;
+
+ my $error =
+ $self->ut_numbern('phonetypenum')
+ || $self->ut_number('weight')
+ || $self->ut_text('typename')
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+# Used by FS::Setup to initialize a new database.
+sub _populate_initial_data {
+ my ($class, %opts) = @_;
+
+ my $weight = 10;
+
+ foreach ("Work", "Home", "Mobile", "Fax") {
+ my $object = $class->new({ 'typename' => $_,
+ 'weight' => $weight,
+ });
+ my $error = $object->insert;
+ die "error inserting $class into database: $error\n"
+ if $error;
+
+ $weight += 10;
+ }
+
+ '';
+
+}
+
+# Used by FS::Upgrade to migrate to a new database.
+sub _upgrade_data {
+ my $class = shift;
+
+ return $class->_populate_initial_data(@_)
+ unless scalar( qsearch( 'phone_type', {} ) );
+
+ '';
+
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::contact_phone>, L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/prospect_main.pm b/FS/FS/prospect_main.pm
new file mode 100644
index 000000000..369029bab
--- /dev/null
+++ b/FS/FS/prospect_main.pm
@@ -0,0 +1,292 @@
+package FS::prospect_main;
+
+use strict;
+use base qw( FS::o2m_Common FS::Record );
+use vars qw( $DEBUG );
+use Scalar::Util qw( blessed );
+use FS::Record qw( dbh qsearch ); #qsearchs );
+use FS::agent;
+use FS::cust_location;
+use FS::contact;
+
+$DEBUG = 0;
+
+=head1 NAME
+
+FS::prospect_main - Object methods for prospect_main records
+
+=head1 SYNOPSIS
+
+ use FS::prospect_main;
+
+ $record = new FS::prospect_main \%hash;
+ $record = new FS::prospect_main { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::prospect_main object represents a prospect. FS::prospect_main inherits
+from FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item prospectnum
+
+primary key
+
+=item company
+
+company
+
+=item locationnum
+
+locationnum
+
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new prospect. To add the prospect to the database, see L<"insert">.
+
+Note that this stores the hash reference, not a distinct copy of the hash it
+points to. You can ask the object for a copy with the I<hash> method.
+
+=cut
+
+sub table { 'prospect_main'; }
+
+=item insert
+
+Adds this record to the database. If there is an error, returns the error,
+otherwise returns false.
+
+=cut
+
+sub insert {
+ my $self = shift;
+ my %options = @_;
+ warn "FS::prospect_main::insert called on $self with options ".
+ join(', ', map "$_=>$options{$_}", keys %options)
+ if $DEBUG;
+
+ local $SIG{HUP} = 'IGNORE';
+ 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;
+
+ warn " inserting prospect_main record" if $DEBUG;
+ my $error = $self->SUPER::insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ if ( $options{'cust_location'} ) {
+ warn " inserting cust_location record" if $DEBUG;
+ my $cust_location = $options{'cust_location'};
+ $cust_location->prospectnum($self->prospectnum);
+ $error = $cust_location->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+
+ warn " commiting transaction" if $DEBUG;
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+ '';
+}
+
+=item delete
+
+Delete this record from the database.
+
+=cut
+
+#delete dangling locations?
+
+=item replace OLD_RECORD
+
+Replaces the OLD_RECORD with this one in the database. If there is an error,
+returns the error, otherwise returns false.
+
+=cut
+
+sub replace {
+ my $new = shift;
+
+ my $old = ( blessed($_[0]) && $_[0]->isa('FS::Record') )
+ ? shift
+ : $new->replace_old;
+
+ my %options = @_;
+
+ warn "FS::prospect_main::replace called on $new to replace $old with options".
+ " ". join(', ', map "$_ => ". $options{$_}, keys %options)
+ if $DEBUG;
+
+ local $SIG{HUP} = 'IGNORE';
+ 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;
+
+ warn " replacing prospect_main record" if $DEBUG;
+ my $error = $new->SUPER::replace($old);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ if ( $options{'cust_location'} ) {
+ my $cust_location = $options{'cust_location'};
+ $cust_location->prospectnum($new->prospectnum);
+ my $method = $cust_location->locationnum ? 'replace' : 'insert';
+ warn " ${method}ing cust_location record" if $DEBUG;
+ $error = $cust_location->$method();
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ } elsif ( exists($options{'cust_location'}) ) {
+ foreach my $cust_location (
+ qsearch('cust_location', { 'prospectnum' => $new->prospectnum } )
+ ) {
+ $error = $cust_location->delete();
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+ }
+
+ warn " commiting transaction" if $DEBUG;
+ $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+ '';
+}
+
+=item check
+
+Checks all fields to make sure this is a valid prospect. If there is
+an error, returns the error, otherwise returns false. Called by the insert
+and replace methods.
+
+=cut
+
+sub check {
+ my $self = shift;
+
+ my $error =
+ $self->ut_numbern('prospectnum')
+ || $self->ut_foreign_key('agentnum', 'agent', 'agentnum' )
+ || $self->ut_text('company')
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=item contact
+
+Returns the contacts (see L<FS::contact>) associated with this prospect.
+
+=cut
+
+sub contact {
+ my $self = shift;
+ qsearch( 'contact', { 'prospectnum' => $self->prospectnum } );
+}
+
+=item search HASHREF
+
+(Class method)
+
+Returns a qsearch hash expression to search for the parameters specified in
+HASHREF. Valid parameters are:
+
+=over 4
+
+=item agentnum
+
+=back
+
+=cut
+
+sub search {
+ my( $class, $params ) = @_;
+
+ my @where = ();
+ my $orderby;
+
+ ##
+ # parse agent
+ ##
+
+ if ( $params->{'agentnum'} =~ /^(\d+)$/ and $1 ) {
+ push @where,
+ "prospect_main.agentnum = $1";
+ }
+
+ ##
+ # setup queries, subs, etc. for the search
+ ##
+
+ $orderby ||= 'ORDER BY prospectnum';
+
+ # here is the agent virtualization
+ push @where, $FS::CurrentUser::CurrentUser->agentnums_sql;
+
+ my $extra_sql = scalar(@where) ? ' WHERE '. join(' AND ', @where) : '';
+
+ my $count_query = "SELECT COUNT(*) FROM prospect_main $extra_sql";
+
+ my $sql_query = {
+ 'table' => 'prospect_main',
+ #'select' => $select,
+ 'hashref' => {},
+ 'extra_sql' => $extra_sql,
+ 'order_by' => $orderby,
+ 'count_query' => $count_query,
+ #'extra_headers' => \@extra_headers,
+ #'extra_fields' => \@extra_fields,
+ };
+
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/MANIFEST b/FS/MANIFEST
index 8a02a1338..74b06a88c 100644
--- a/FS/MANIFEST
+++ b/FS/MANIFEST
@@ -463,3 +463,14 @@ FS/class_Common.pm
t/class_Common.t
FS/category_Common.pm
t/category_Common.t
+FS/contact.pm
+t/contact.t
+FS/contact_phone.pm
+t/contact_phone.t
+FS/phone_type.pm
+t/phone_type.t
+FS/contact_email.pm
+t/contact_email.t
+FS/prospect_main.pm
+t/prospect_main.t
+FS/o2m_Common.pm
diff --git a/FS/t/contact.t b/FS/t/contact.t
new file mode 100644
index 000000000..bf22bd10b
--- /dev/null
+++ b/FS/t/contact.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::contact;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/contact_email.t b/FS/t/contact_email.t
new file mode 100644
index 000000000..1cd13b628
--- /dev/null
+++ b/FS/t/contact_email.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::contact_email;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/contact_phone.t b/FS/t/contact_phone.t
new file mode 100644
index 000000000..493ced77a
--- /dev/null
+++ b/FS/t/contact_phone.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::contact_phone;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/phone_type.t b/FS/t/phone_type.t
new file mode 100644
index 000000000..b6bc7935e
--- /dev/null
+++ b/FS/t/phone_type.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::phone_type;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/prospect_main.t b/FS/t/prospect_main.t
new file mode 100644
index 000000000..045c2f041
--- /dev/null
+++ b/FS/t/prospect_main.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::prospect_main;
+$loaded=1;
+print "ok 1\n";