From 03ceab71dad1e5eb366865d304e5e459cc905ce4 Mon Sep 17 00:00:00 2001 From: ivan Date: Mon, 28 Dec 2009 19:20:25 +0000 Subject: [PATCH] beginning of prospect/CRM/contact work --- FS/FS.pm | 12 +- FS/FS/AccessRight.pm | 14 ++ FS/FS/Mason.pm | 2 + FS/FS/Schema.pm | 78 +++++- FS/FS/Setup.pm | 5 +- FS/FS/Upgrade.pm | 3 + FS/FS/contact.pm | 177 ++++++++++++++ FS/FS/contact_email.pm | 128 ++++++++++ FS/FS/contact_phone.pm | 143 +++++++++++ FS/FS/cust_location.pm | 19 +- FS/FS/o2m_Common.pm | 152 ++++++++++++ FS/FS/phone_type.pm | 137 +++++++++++ FS/FS/prospect_main.pm | 292 +++++++++++++++++++++++ FS/MANIFEST | 11 + FS/t/contact.t | 5 + FS/t/contact_email.t | 5 + FS/t/contact_phone.t | 5 + FS/t/phone_type.t | 5 + FS/t/prospect_main.t | 5 + httemplate/edit/elements/edit.html | 7 +- httemplate/edit/process/elements/process.html | 28 ++- httemplate/edit/process/prospect_main.html | 34 +++ httemplate/edit/prospect_main.html | 83 +++++++ httemplate/elements/city.html | 4 +- httemplate/elements/contact.html | 72 ++++++ httemplate/elements/header.html | 49 ++-- httemplate/elements/menu.html | 2 + httemplate/elements/tr-contact.html | 24 ++ httemplate/elements/tr-select-cust_location.html | 79 ++++-- httemplate/misc/location.cgi | 15 +- httemplate/search/prospect_main.html | 74 ++++++ httemplate/search/report_prospect_main.html | 32 +++ httemplate/view/cust_main/packages/location.html | 6 +- httemplate/view/prospect_main.html | 92 +++++++ 34 files changed, 1748 insertions(+), 51 deletions(-) create mode 100644 FS/FS/contact.pm create mode 100644 FS/FS/contact_email.pm create mode 100644 FS/FS/contact_phone.pm create mode 100644 FS/FS/o2m_Common.pm create mode 100644 FS/FS/phone_type.pm create mode 100644 FS/FS/prospect_main.pm create mode 100644 FS/t/contact.t create mode 100644 FS/t/contact_email.t create mode 100644 FS/t/contact_phone.t create mode 100644 FS/t/phone_type.t create mode 100644 FS/t/prospect_main.t create mode 100644 httemplate/edit/process/prospect_main.html create mode 100644 httemplate/edit/prospect_main.html create mode 100644 httemplate/elements/contact.html create mode 100644 httemplate/elements/tr-contact.html create mode 100644 httemplate/search/prospect_main.html create mode 100644 httemplate/search/report_prospect_main.html create mode 100644 httemplate/view/prospect_main.html 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 - Reason class L - Package reason class +L - Contact class + +L - Contact phone class + +L - Phone type class + +L - Contact email class + +L - Prospect class + L - Customer class -L - Customer location class +L - Customer location class L - 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 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, 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 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, 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 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, 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 CVars)> 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 + +=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 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, L, 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 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) 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, 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"; diff --git a/httemplate/edit/elements/edit.html b/httemplate/edit/elements/edit.html index fd73e031e..4935ddc1a 100644 --- a/httemplate/edit/elements/edit.html +++ b/httemplate/edit/elements/edit.html @@ -409,7 +409,7 @@ Example: % $label[0] = '/elements/tr-td-label.html'; <% include( @label ) %> - + <% include( @existing ) %> @@ -465,7 +465,7 @@ Example: % $label[0] = '/elements/tr-td-label.html'; <% include( @label ) %> - + <% include( @include ) %> @@ -503,7 +503,7 @@ Example: // only spawn if we're the last element... return if not - var field_regex = /(\d+)$/; + var field_regex = /(\d+)(_[a-z]+)?$/; var match = field_regex.exec(what.name); if ( !match ) { alert(what.name + " didn't match?!"); @@ -574,6 +574,7 @@ Example: widget_cell.style.borderTop = "1px solid black"; widget_cell.style.paddingTop = "3px"; + widget_cell.colSpan = "<% $f->{'colspan'} || 1 %>" widget_cell.innerHTML = newrow; diff --git a/httemplate/edit/process/elements/process.html b/httemplate/edit/process/elements/process.html index 5befdd337..e24f3f681 100644 --- a/httemplate/edit/process/elements/process.html +++ b/httemplate/edit/process/elements/process.html @@ -56,13 +56,17 @@ Example: }, + 'process_o2m' => { 'table' => table_name', + 'num_col' => 'column', #if column name is different in + #link_table than source_table + }, #checks CGI params and whatever else before much else runs #return an error string or empty for no error 'precheck_callback' => sub { my( $cgi ) = @_; }, #supplies arguments to insert() and replace() - # for use with tables that are FS::option_Common + # for use with tables that are FS::option_Common (among other things) 'args_callback' => sub { my( $cgi, $object ) = @_; }, 'debug' => 1, #turns on debugging output @@ -255,6 +259,28 @@ if ( !$error && $opt{'process_m2name'} ) { } +if ( !$error && $opt{'process_o2m'} ) { + + my @process_o2m = ref($opt{'process_o2m'}) eq 'ARRAY' + ? @{ $opt{'process_o2m'} } + : ( $opt{'process_o2m'} ); + + + foreach my $process_o2m (@process_o2m) { + + if ( $opt{'debug'} ) { + warn "$me processing o2m:\n". Dumper( %{ $process_o2m }, + 'params' => scalar($cgi->Vars), + ); + } + + $error = $new->process_o2m( %{ $process_o2m }, + 'params' => scalar($cgi->Vars), + ); + } + +} + if ( $error ) { $cgi->param('error', $error); diff --git a/httemplate/edit/process/prospect_main.html b/httemplate/edit/process/prospect_main.html new file mode 100644 index 000000000..34d26421b --- /dev/null +++ b/httemplate/edit/process/prospect_main.html @@ -0,0 +1,34 @@ +<% include('elements/process.html', + 'table' => 'prospect_main', + 'args_callback' => $args_callback, + 'agent_virt' => 1, + 'process_o2m' => { + 'table' => 'contact', + 'fields' => [qw( first last title comment )], + }, + 'redirect' => popurl(3). 'view/prospect_main.html?', + ) +%> +<%init> + +my $args_callback = sub { + my( $cgi, $object ) = @_; + + $cgi->param('locationnum') =~ /^(\-?\d*)$/ + or die 'illegal locationnum '. $cgi->param('locationnum'); + my $locationnum = $1; + + return ( 'cust_location' => '' ) unless $locationnum; + + my $cust_location = new FS::cust_location { + map { $_ => scalar($cgi->param($_)) } + qw( address1 address2 city county state zip country ) + }; + + $cust_location->locationnum($locationnum) unless $locationnum == -1; + + ( 'cust_location' => $cust_location ); + +}; + + diff --git a/httemplate/edit/prospect_main.html b/httemplate/edit/prospect_main.html new file mode 100644 index 000000000..c4123a078 --- /dev/null +++ b/httemplate/edit/prospect_main.html @@ -0,0 +1,83 @@ +<% include('elements/edit.html', + 'name_singular' => 'prospect', + 'table' => 'prospect_main', + 'labels' => { 'prospectnum' => 'Prospect', + 'agentnum' => 'Agent', + 'company' => 'Company', + 'contactnum' => 'Contact', + }, + 'fields' => [ + { 'field' => 'agentnum', + 'type' => 'select-agent', + 'empty_label' => 'Select agent', + }, + { 'field' => 'company', + 'type' => 'text', + 'size' => 50, + }, + { 'field' => 'contactnum', + 'type' => 'contact', + 'colspan' => 6, + #actually o2m, but this seems to be working for edit so far + 'm2name_table' => 'contact', + 'm2name_namecol' => 'contactnum', + 'm2_label' => 'Contact', + 'm2_error_callback' => sub { my($cgi, $object) = @_; (); }, #XXX + }, + { 'field' => 'locationnum', + 'type' => 'select-cust_location', + 'empty_label' => 'No address', + }, + ], + 'edit_callback' => $edit_callback, + 'error_callbacck' => $error_callback, + 'agent_virt' => 1, + ) +%> +<%init> + +my $curuser = $FS::CurrentUser::CurrentUser; + +my $prospectnum; +if ( $cgi->param('error') ) { + $prospectnum = scalar($cgi->param('prospectnum')); + + die "access denied" + unless $curuser->access_right(($prospectnum ? 'Edit' : 'New'). ' prospect'); + +} elsif ( $cgi->keywords ) { #editing + + die "access denied" + unless $curuser->access_right('Edit prospect'); + +} else { #new prospect + + die "access denied" + unless $curuser->access_right('New prospect'); + +} + +my $edit_callback = sub { + #my( $cgi, $prospect_main, $fields_listref, $opt_hashref ) = @_; + my( $cgi, $prospect_main ) = @_; + my @cust_location = + qsearch('cust_location', { 'prospectnum' => $prospect_main->prospectnum } ); + die 'multiple locations for prospect '. $prospect_main->prospectnum + if scalar(@cust_location) > 1; + $prospect_main->set('locationnum', $cust_location[0]->locationnum) + if scalar(@cust_location); + #warn 'prospect_main.locationnum '.$prospect_main->get('locationnum'); +}; + +my $error_callback = sub { + #my( $cgi, $prospect_main, $fields_listref, $opt_hashref ) = @_; + my( $cgi, $prospect_main ) = @_; + $cgi->param('locationnum') =~ /^(\-?\d*)$/ + or die 'illegal locationnum '. $cgi->param('locationnum'); + my $locationnum = $1; + $prospect_main->set('locationnum', $locationnum); +}; + +my @agentnums = $FS::CurrentUser::CurrentUser->agentnums; + + diff --git a/httemplate/elements/city.html b/httemplate/elements/city.html index 47e5c37c2..61d057889 100644 --- a/httemplate/elements/city.html +++ b/httemplate/elements/city.html @@ -34,7 +34,7 @@ Example: what.options[length] = optionName; } - var saved_<%$pre%>city= ''; + var saved_<%$pre%>city= '<% $saved_city |h %>'; function <% $pre %>county_changed(what, callback) { @@ -124,10 +124,12 @@ my $text_style = $opt{'style'} ? [ @{ $opt{'style'} } ] : []; my $select_style = $opt{'style'} ? [ @{ $opt{'style'} } ] : []; my @cities = cities( $opt{'county'}, $opt{'state'}, $opt{'country'} ); +my $saved_city = ''; if ( scalar(@cities) > 1 || $cities[0] ) { push @$text_style, 'display:none'; } else { push @$select_style, 'display:none'; + $saved_city = $opt{'city'}; } $text_style = diff --git a/httemplate/elements/contact.html b/httemplate/elements/contact.html new file mode 100644 index 000000000..38703bfef --- /dev/null +++ b/httemplate/elements/contact.html @@ -0,0 +1,72 @@ +% unless ( $opt{'js_only'} ) { + + + + + + + + + + +
+ + >
+ First name +
+ + >
+ Last name +
+ + >
+ Title/Position +
+ + >
+ Comment +
+ +% } +<%init> + +my( %opt ) = @_; + +my $name = $opt{'element_name'} || $opt{'field'} || 'contactnum'; +my $id = $opt{'id'} || 'contactnum'; + +my $curr_value = $opt{'curr_value'} || $opt{'value'}; + +my $onchange = ''; +if ( $opt{'onchange'} ) { + $onchange = $opt{'onchange'}; + $onchange .= '(this)' unless $onchange =~ /\(\w*\);?$/; + $onchange =~ s/\(what\);/\(this\);/g; #ugh, terrible hack. all onchange + #callbacks should act the same + $onchange = 'onChange="'. $onchange. '"'; +} + +my $contact; +if ( $curr_value ) { + $contact = qsearchs('contact', { 'contactnum' => $curr_value } ); +} else { + $contact = new FS::contact {}; +} + + diff --git a/httemplate/elements/header.html b/httemplate/elements/header.html index 22e872eca..495923c4d 100644 --- a/httemplate/elements/header.html +++ b/httemplate/elements/header.html @@ -39,6 +39,12 @@ Example: <% include('init_overlib.html') |n %> <% $head |n %> @@ -140,6 +147,7 @@ input.fstext { vertical-align:bottom; text-align:right; font-family: Arial,Verdana,Helvetica,sans-serif; + font-size: 13px; padding-left: 0px; padding-right: 0px; padding-top: 0px; @@ -154,14 +162,14 @@ input.fstext { - + % if ( $menu_position eq 'top' ) { - - - @@ -193,7 +201,17 @@ input.fstext { - + + @@ -217,30 +235,29 @@ input.fstext { - - + + +% } +<%init> + +my( %opt ) = @_; + +my $cell_style = $opt{'cell_style'} ? 'STYLE="'. $opt{'cell_style'}. '"' : ''; + +$opt{'label'} ||= 'Contact'; + + diff --git a/httemplate/elements/tr-select-cust_location.html b/httemplate/elements/tr-select-cust_location.html index ab043ee7e..5e938b53a 100644 --- a/httemplate/elements/tr-select-cust_location.html +++ b/httemplate/elements/tr-select-cust_location.html @@ -4,7 +4,13 @@ Example: include('/elements/tr-select-cust_location.html', 'cgi' => $cgi, - 'cust_main' => $cust_main, + + 'cust_main' => $cust_main, + #or + 'prospect_main' => $prospect_main, + + #optional + 'empty_label' => '(default service address)', ) @@ -42,6 +48,7 @@ Example: } else { if ( locationnum == 0 ) { +% if ( $cust_main ) { what.form.address1.value = <% $cust_main->get($prefix.'address1') |js_string %>; what.form.address2.value = <% $cust_main->get($prefix.'address2') |js_string %>; what.form.city.value = <% $cust_main->get($prefix.'city') |js_string %>; @@ -54,16 +61,33 @@ Example: <% $cust_main->get($prefix.'county') | js_string %> ) ); +% } } else { get_location( locationnum, update_location ); } +% if ( $editable ) { + if ( locationnum == 0 ) { +% } + %#sleep/wait until dropdowns are updated? -% for (@location_fields, 'city_select') { - what.form.<%$_%>.disabled = true; - what.form.<%$_%>.style.backgroundColor = '#dddddd'; -% } +% for (@location_fields, 'city_select') { + what.form.<%$_%>.disabled = true; + what.form.<%$_%>.style.backgroundColor = '#dddddd'; +% } + +% if ( $editable ) { + } else { + +%#sleep/wait until dropdowns are updated? +% for (@location_fields, 'city_select') { + what.form.<%$_%>.disabled = false; + what.form.<%$_%>.style.backgroundColor = '#ffffff'; +% } + + } +% } } } @@ -122,15 +146,20 @@ Example: @@ -138,7 +167,7 @@ Example: <% include('/elements/location.html', 'object' => $cust_location, #'onchange' ? probably not - 'disabled' => ( $locationnum == -1 ? '' : 'DISABLED' ), + 'disabled' => $disabled, 'no_asterisks' => 1, ) %> @@ -156,13 +185,25 @@ my $statedefault = $conf->config('statedefault') || ($countrydefault eq 'US' ? 'CA' : ''); my %opt = @_; -my $cgi = $opt{'cgi'}; -my $cust_main = $opt{'cust_main'}; +my $cgi = $opt{'cgi'}; +my $cust_main = $opt{'cust_main'}; +my $prospect_main = $opt{'prospect_main'}; + +my $prefix = ($cust_main && length($cust_main->ship_last)) ? 'ship_' : ''; -my $prefix = length($cust_main->ship_last) ? 'ship_' : ''; +my $locationnum; +if ( length($opt{'curr_value'}) ) { + $locationnum = $opt{'curr_value'}; +} else { + $cgi->param('locationnum') =~ /^(\-?\d*)$/ or die "illegal locationnum"; + $locationnum = $1; +} + +#probably could use explicit controls +# (cust_main locations not editable for tax reasons) +my $editable = $cust_main ? 0 : 1; #could use explicit control +my $addnew = $cust_main ? 1 : ( $locationnum>0 ? 0 : 1 ); -$cgi->param('locationnum') =~ /^(\-?\d*)$/ or die "illegal locationnum"; -my $locationnum = $1; my $cust_location; if ( $locationnum && $locationnum != -1 ) { $cust_location = qsearchs('cust_location', { 'locationnum' => $locationnum } ) @@ -171,9 +212,13 @@ if ( $locationnum && $locationnum != -1 ) { $cust_location = new FS::cust_location; if ( $locationnum == -1 ) { $cust_location->$_( $cgi->param($_) ) foreach @location_fields; - } else { + } elsif ( $cust_main ) { $cust_location->$_( $cust_main->get($prefix.$_) ) foreach @location_fields; } } +my $disabled = ( $locationnum == -1 || ($editable && $locationnum) ) + ? '' + : 'DISABLED'; + diff --git a/httemplate/misc/location.cgi b/httemplate/misc/location.cgi index 419c59f2e..82ad636c4 100644 --- a/httemplate/misc/location.cgi +++ b/httemplate/misc/location.cgi @@ -3,12 +3,23 @@ my $locationnum = $cgi->param('arg'); +my $curuser = $FS::CurrentUser::CurrentUser; + my $cust_location = qsearchs({ 'select' => 'cust_location.*', 'table' => 'cust_location', 'hashref' => { 'locationnum' => $locationnum }, - 'addl_from' => 'LEFT JOIN cust_main USING ( custnum )', - 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql, + 'addl_from' => ' LEFT JOIN cust_main USING ( custnum ) ', + ' LEFT JOIN prospect_main USING ( prospectnum ) ', + 'extra_sql' => ' AND ( '. + ' ( custnum IS NOT NULL AND '. + $curuser->agentnums_sql( table=>'cust_main' ). + ' ) '. + ' OR '. + ' ( prospectnum IS NOT NULL AND '. + $curuser->agentnums_sql( table=>'prospect_main' ). + ' ) '. + ' )', }); my %hash = (); diff --git a/httemplate/search/prospect_main.html b/httemplate/search/prospect_main.html new file mode 100644 index 000000000..12e3e1812 --- /dev/null +++ b/httemplate/search/prospect_main.html @@ -0,0 +1,74 @@ +<% include('elements/search.html', + 'title' => 'Prospect Search Results', + 'name_singular' => 'prospect', + 'query' => $query, + 'count_query' => $count_query, + 'header' => [ '#', + 'Prospect', + 'Contact(s)', + ], + 'fields' => [ 'prospectnum', + 'company', + sub { + my $pm = shift; + [ map { + [ { 'data' => $_->line, }, ]; + } + $pm->contact + ]; + }, + ], + 'links' => [ '', + $link, + '', #link to contact edit??? + ], + 'agent_virt' => 1, + ) +%> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('List prospects'); + +my %search_hash = (); + +#$search_hash{'query'} = $cgi->keywords; + +#scalars +my @scalars = qw ( + agentnum +); + +for my $param ( @scalars ) { + $search_hash{$param} = scalar( $cgi->param($param) ) + if $cgi->param($param); +} + +#lists +#for my $param () { +# $search_hash{$param} = [ $cgi->param($param) ]; +#} + +# parse dates +#foreach my $field (qw( signupdate )) { +# +# my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi, $field); +# +# next if $beginning == 0 && $ending == 4294967295; +# #or $disable{$cgi->param('status')}->{$field}; +# +# $search_hash{$field} = [ $beginning, $ending ]; +# +#} + +my $query = FS::prospect_main->search(\%search_hash); +my $count_query = delete($query->{'count_query'}); +#my @extra_headers = @{ delete($query->{'extra_headers'}) }; +#my @extra_fields = @{ delete($query->{'extra_fields'}) }; + +my $link = sub { + my $prospect_main = shift; + [ "${p}view/prospect_main.html?", 'prospectnum' ]; +}; + + diff --git a/httemplate/search/report_prospect_main.html b/httemplate/search/report_prospect_main.html new file mode 100644 index 000000000..5e3834346 --- /dev/null +++ b/httemplate/search/report_prospect_main.html @@ -0,0 +1,32 @@ +<% include('/elements/header.html', 'Prospect Report' ) %> + + + +
+ @@ -180,12 +188,12 @@ input.fstext {
+
+
+ +% if ( $curuser->access_right('List prospects') ) { +
+
+ Adv + +
+% } +
% if ( $curuser->access_right('List customers') ) {

@@ -209,7 +227,7 @@ input.fstext {
- +
% }
% if ( $curuser->access_right('View invoices') ) {
- + % if ( $curuser->access_right('List invoices') ) { Adv\ % } -
- +
% }
+ % if ( $curuser->access_right('View customer services') ) {
-
+
Advanced
% }
+ % if ( $conf->config("ticket_system") ) {
-
+
Advanced
@@ -334,11 +351,13 @@ if ( scalar(@agentnums) == 1 ) { $company_name = $conf->config('company_name'); } -my $cust_width = 288; #251 #ok for IE, slightly bigger for windows firefox +my $prospect_label = '(name, company or phone)'; + +my $cust_width = 246; my $cust_label = '(cust #, name, company'; if ( $conf->exists('address1-search') ) { $cust_label .= ', address'; - $cust_width += 64; + $cust_width += 56; } $cust_label .= ' or contact phone)'; diff --git a/httemplate/elements/menu.html b/httemplate/elements/menu.html index caf227409..ce0278f13 100644 --- a/httemplate/elements/menu.html +++ b/httemplate/elements/menu.html @@ -476,6 +476,8 @@ if ( $conf->config('ticket_system') ) { 'Ticketing start page', ], } +$menu{'New prospect'} = [ $fsurl.'edit/prospect_main.html', 'Add a new prospect' ] + if $curuser->access_right('New prospect'); $menu{'New customer'} = [ $fsurl.'edit/cust_main.cgi', 'Add a new customer' ] if $curuser->access_right('New customer'); $menu{'Reports'} = [ \%report_menu, 'Lists, reporting and graphing' ] diff --git a/httemplate/elements/tr-contact.html b/httemplate/elements/tr-contact.html new file mode 100644 index 000000000..ee0e6e824 --- /dev/null +++ b/httemplate/elements/tr-contact.html @@ -0,0 +1,24 @@ +% unless ( $opt{'js_only'} ) { + + <% include('tr-td-label.html', %opt) %> +
> + +% } +% + <% include( '/elements/contact.html', %opt ) %> +% +% unless ( $opt{'js_only'} ) { + +
Service location
+ + + + + + <% include( '/elements/tr-select-agent.html', + 'curr_value' => scalar($cgi->param('agentnum')), + 'disable_empty' => 0, + ) + %> + +
Search options
+ +
+ + + + +<% include('/elements/footer.html') %> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('List prospects'); + +my $conf = new FS::Conf; + + diff --git a/httemplate/view/cust_main/packages/location.html b/httemplate/view/cust_main/packages/location.html index 9f348e5d3..41155cbae 100644 --- a/httemplate/view/cust_main/packages/location.html +++ b/httemplate/view/cust_main/packages/location.html @@ -4,12 +4,16 @@ (default service address)
% } - <% $loc->location_label( 'join_string' => '
', + <% $loc->location_label( 'join_string' => '
', + 'double_space' => '   ', 'escape_function' => \&encode_entities, + 'countrydefault' => $countrydefault, ) %> +% unless ( $cust_pkg->locationnum ) {
+% } % if ( ! $cust_pkg->get('cancel') % && $FS::CurrentUser::CurrentUser->access_right('Change customer package') diff --git a/httemplate/view/prospect_main.html b/httemplate/view/prospect_main.html new file mode 100644 index 000000000..de446a9fb --- /dev/null +++ b/httemplate/view/prospect_main.html @@ -0,0 +1,92 @@ +<% include('/elements/header.html', + 'Prospect View: '. $prospect_main->company + ) +%> + +% if ( $curuser->access_right('Edit prospect') ) { + Edit this prospect +% } + +<% ntable("#cccccc",2) %> + + + Prospect # + <% $prospectnum %> + + +%unless ( scalar(@agentnums) == 1 ) { +% my $agent = qsearchs('agent',{ 'agentnum' => $prospect_main->agentnum } ); + + Agent + <% $agent->agentnum %>: <% $agent->agent %> + +%} + + + Company + <% $prospect_main->company |h %> + + +% foreach my $contact ( $prospect_main->contact ) { + + Contact + <% $contact->line %> + +%} + +% my @cust_location = +% qsearch('cust_location', { 'prospectnum' => $prospectnum } ); +% #but only one, for now +% foreach my $cust_location (@cust_location) { + + Address + + <% $cust_location->location_label( + 'join_string' => '
', + 'double_space' => '   ', + 'escape_function' => \&encode_entities, + ) + %> + + +% } + + + +
+ +<% ntable("#cccccc") %> + + + Tickets + + + + +<%init> + +my $curuser = $FS::CurrentUser::CurrentUser; + +die "access denied" + unless $curuser->access_right('View prospect'); + +my $prospectnum; +if ( $cgi->param('prospectnum') =~ /^(\d+)$/ ) { + $prospectnum = $1; +} else { + die "No prospect specified (bad URL)!" unless $cgi->keywords; + my($query) = $cgi->keywords; # needs parens with my, ->keywords returns array + $query =~ /^(\d+)$/; + $prospectnum = $1; +} + +my $prospect_main = qsearchs( { + 'table' => 'prospect_main', + 'hashref' => { 'prospectnum' => $prospectnum }, + 'extra_sql' => ' AND '. $curuser->agentnums_sql, +}); +die "Prospect not found!" unless $prospect_main; + +my @agentnums = $curuser->agentnums; + + -- 2.11.0