autoload methods returning foreign records, RT#13971
[freeside.git] / FS / FS / contact.pm
1 package FS::contact;
2 use base qw( FS::Record );
3
4 use strict;
5 use FS::Record qw( qsearchs dbh ); # qw( qsearch qsearchs dbh );
6
7 =head1 NAME
8
9 FS::contact - Object methods for contact records
10
11 =head1 SYNOPSIS
12
13   use FS::contact;
14
15   $record = new FS::contact \%hash;
16   $record = new FS::contact { 'column' => 'value' };
17
18   $error = $record->insert;
19
20   $error = $new_record->replace($old_record);
21
22   $error = $record->delete;
23
24   $error = $record->check;
25
26 =head1 DESCRIPTION
27
28 An FS::contact object represents an example.  FS::contact inherits from
29 FS::Record.  The following fields are currently supported:
30
31 =over 4
32
33 =item contactnum
34
35 primary key
36
37 =item prospectnum
38
39 prospectnum
40
41 =item custnum
42
43 custnum
44
45 =item locationnum
46
47 locationnum
48
49 =item last
50
51 last
52
53 =item first
54
55 first
56
57 =item title
58
59 title
60
61 =item comment
62
63 comment
64
65 =item disabled
66
67 disabled
68
69
70 =back
71
72 =head1 METHODS
73
74 =over 4
75
76 =item new HASHREF
77
78 Creates a new example.  To add the example to the database, see L<"insert">.
79
80 Note that this stores the hash reference, not a distinct copy of the hash it
81 points to.  You can ask the object for a copy with the I<hash> method.
82
83 =cut
84
85 # the new method can be inherited from FS::Record, if a table method is defined
86
87 sub table { 'contact'; }
88
89 =item insert
90
91 Adds this record to the database.  If there is an error, returns the error,
92 otherwise returns false.
93
94 =cut
95
96 sub insert {
97   my $self = shift;
98
99   local $SIG{INT} = 'IGNORE';
100   local $SIG{QUIT} = 'IGNORE';
101   local $SIG{TERM} = 'IGNORE';
102   local $SIG{TSTP} = 'IGNORE';
103   local $SIG{PIPE} = 'IGNORE';
104
105   my $oldAutoCommit = $FS::UID::AutoCommit;
106   local $FS::UID::AutoCommit = 0;
107   my $dbh = dbh;
108
109   my $error = $self->SUPER::insert;
110   if ( $error ) {
111     $dbh->rollback if $oldAutoCommit;
112     return $error;
113   }
114
115   foreach my $pf ( grep { /^phonetypenum(\d+)$/ && $self->get($_) =~ /\S/ }
116                         keys %{ $self->hashref } ) {
117     $pf =~ /^phonetypenum(\d+)$/ or die "wtf (daily, the)";
118     my $phonetypenum = $1;
119
120     my $contact_phone = new FS::contact_phone {
121       'contactnum' => $self->contactnum,
122       'phonetypenum' => $phonetypenum,
123       _parse_phonestring( $self->get($pf) ),
124     };
125     $error = $contact_phone->insert;
126     if ( $error ) {
127       $dbh->rollback if $oldAutoCommit;
128       return $error;
129     }
130   }
131
132   if ( $self->get('emailaddress') =~ /\S/ ) {
133
134     foreach my $email ( split(/\s*,\s*/, $self->get('emailaddress') ) ) {
135  
136       my $contact_email = new FS::contact_email {
137         'contactnum'   => $self->contactnum,
138         'emailaddress' => $email,
139       };
140       $error = $contact_email->insert;
141       if ( $error ) {
142         $dbh->rollback if $oldAutoCommit;
143         return $error;
144       }
145
146     }
147
148   }
149
150   #unless ( $import || $skip_fuzzyfiles ) {
151     #warn "  queueing fuzzyfiles update\n"
152     #  if $DEBUG > 1;
153     $error = $self->queue_fuzzyfiles_update;
154     if ( $error ) {
155       $dbh->rollback if $oldAutoCommit;
156       return "updating fuzzy search cache: $error";
157     }
158   #}
159
160   $dbh->commit or die $dbh->errstr if $oldAutoCommit;
161
162   '';
163
164 }
165
166 =item delete
167
168 Delete this record from the database.
169
170 =cut
171
172 # the delete method can be inherited from FS::Record
173
174 sub delete {
175   my $self = shift;
176
177   local $SIG{HUP} = 'IGNORE';
178   local $SIG{INT} = 'IGNORE';
179   local $SIG{QUIT} = 'IGNORE';
180   local $SIG{TERM} = 'IGNORE';
181   local $SIG{TSTP} = 'IGNORE';
182   local $SIG{PIPE} = 'IGNORE';
183
184   my $oldAutoCommit = $FS::UID::AutoCommit;
185   local $FS::UID::AutoCommit = 0;
186   my $dbh = dbh;
187
188   foreach my $object ( $self->contact_phone, $self->contact_email ) {
189     my $error = $object->delete;
190     if ( $error ) {
191       $dbh->rollback if $oldAutoCommit;
192       return $error;
193     }
194   }
195
196   my $error = $self->SUPER::delete;
197   if ( $error ) {
198     $dbh->rollback if $oldAutoCommit;
199     return $error;
200   }
201
202   $dbh->commit or die $dbh->errstr if $oldAutoCommit;
203   '';
204
205 }
206
207 =item replace OLD_RECORD
208
209 Replaces the OLD_RECORD with this one in the database.  If there is an error,
210 returns the error, otherwise returns false.
211
212 =cut
213
214 sub replace {
215   my $self = shift;
216
217   local $SIG{INT} = 'IGNORE';
218   local $SIG{QUIT} = 'IGNORE';
219   local $SIG{TERM} = 'IGNORE';
220   local $SIG{TSTP} = 'IGNORE';
221   local $SIG{PIPE} = 'IGNORE';
222
223   my $oldAutoCommit = $FS::UID::AutoCommit;
224   local $FS::UID::AutoCommit = 0;
225   my $dbh = dbh;
226
227   my $error = $self->SUPER::replace(@_);
228   if ( $error ) {
229     $dbh->rollback if $oldAutoCommit;
230     return $error;
231   }
232
233   foreach my $pf ( grep { /^phonetypenum(\d+)$/ && $self->get($_) }
234                         keys %{ $self->hashref } ) {
235     $pf =~ /^phonetypenum(\d+)$/ or die "wtf (daily, the)";
236     my $phonetypenum = $1;
237
238     my %cp = ( 'contactnum'   => $self->contactnum,
239                'phonetypenum' => $phonetypenum,
240              );
241     my $contact_phone = qsearchs('contact_phone', \%cp)
242                         || new FS::contact_phone   \%cp;
243
244     my %cpd = _parse_phonestring( $self->get($pf) );
245     $contact_phone->set( $_ => $cpd{$_} ) foreach keys %cpd;
246
247     my $method = $contact_phone->contactphonenum ? 'replace' : 'insert';
248
249     $error = $contact_phone->$method;
250     if ( $error ) {
251       $dbh->rollback if $oldAutoCommit;
252       return $error;
253     }
254   }
255
256   if ( defined($self->get('emailaddress')) ) {
257
258     #ineffecient but whatever, how many email addresses can there be?
259
260     foreach my $contact_email ( $self->contact_email ) {
261       my $error = $contact_email->delete;
262       if ( $error ) {
263         $dbh->rollback if $oldAutoCommit;
264         return $error;
265       }
266     }
267
268     foreach my $email ( split(/\s*,\s*/, $self->get('emailaddress') ) ) {
269  
270       my $contact_email = new FS::contact_email {
271         'contactnum'   => $self->contactnum,
272         'emailaddress' => $email,
273       };
274       $error = $contact_email->insert;
275       if ( $error ) {
276         $dbh->rollback if $oldAutoCommit;
277         return $error;
278       }
279
280     }
281
282   }
283
284   #unless ( $import || $skip_fuzzyfiles ) {
285     #warn "  queueing fuzzyfiles update\n"
286     #  if $DEBUG > 1;
287     $error = $self->queue_fuzzyfiles_update;
288     if ( $error ) {
289       $dbh->rollback if $oldAutoCommit;
290       return "updating fuzzy search cache: $error";
291     }
292   #}
293
294   $dbh->commit or die $dbh->errstr if $oldAutoCommit;
295
296   '';
297
298 }
299
300 #i probably belong in contact_phone.pm
301 sub _parse_phonestring {
302   my $value = shift;
303
304   my($countrycode, $extension) = ('1', '');
305
306   #countrycode
307   if ( $value =~ s/^\s*\+\s*(\d+)// ) {
308     $countrycode = $1;
309   } else {
310     $value =~ s/^\s*1//;
311   }
312   #extension
313   if ( $value =~ s/\s*(ext|x)\s*(\d+)\s*$//i ) {
314      $extension = $2;
315   }
316
317   ( 'countrycode' => $countrycode,
318     'phonenum'    => $value,
319     'extension'   => $extension,
320   );
321 }
322
323 =item queue_fuzzyfiles_update
324
325 Used by insert & replace to update the fuzzy search cache
326
327 =cut
328
329 use FS::cust_main::Search;
330 sub queue_fuzzyfiles_update {
331   my $self = shift;
332
333   local $SIG{HUP} = 'IGNORE';
334   local $SIG{INT} = 'IGNORE';
335   local $SIG{QUIT} = 'IGNORE';
336   local $SIG{TERM} = 'IGNORE';
337   local $SIG{TSTP} = 'IGNORE';
338   local $SIG{PIPE} = 'IGNORE';
339
340   my $oldAutoCommit = $FS::UID::AutoCommit;
341   local $FS::UID::AutoCommit = 0;
342   my $dbh = dbh;
343
344   foreach my $field ( 'first', 'last' ) {
345     my $queue = new FS::queue { 
346       'job' => 'FS::cust_main::Search::append_fuzzyfiles_fuzzyfield'
347     };
348     my @args = "contact.$field", $self->get($field);
349     my $error = $queue->insert( @args );
350     if ( $error ) {
351       $dbh->rollback if $oldAutoCommit;
352       return "queueing job (transaction rolled back): $error";
353     }
354   }
355
356   $dbh->commit or die $dbh->errstr if $oldAutoCommit;
357   '';
358
359 }
360
361 =item check
362
363 Checks all fields to make sure this is a valid example.  If there is
364 an error, returns the error, otherwise returns false.  Called by the insert
365 and replace methods.
366
367 =cut
368
369 # the check method should currently be supplied - FS::Record contains some
370 # data checking routines
371
372 sub check {
373   my $self = shift;
374
375   my $error = 
376     $self->ut_numbern('contactnum')
377     || $self->ut_foreign_keyn('prospectnum', 'prospect_main', 'prospectnum')
378     || $self->ut_foreign_keyn('custnum',     'cust_main',     'custnum')
379     || $self->ut_foreign_keyn('locationnum', 'cust_location', 'locationnum')
380     || $self->ut_foreign_keyn('classnum',    'contact_class', 'classnum')
381     || $self->ut_namen('last')
382     || $self->ut_namen('first')
383     || $self->ut_textn('title')
384     || $self->ut_textn('comment')
385     || $self->ut_enum('disabled', [ '', 'Y' ])
386   ;
387   return $error if $error;
388
389   return "No prospect or customer!" unless $self->prospectnum || $self->custnum;
390   return "Prospect and customer!"       if $self->prospectnum && $self->custnum;
391
392   return "One of first name, last name, or title must have a value"
393     if ! grep $self->$_(), qw( first last title);
394
395   $self->SUPER::check;
396 }
397
398 sub line {
399   my $self = shift;
400   my $data = $self->first. ' '. $self->last;
401   $data .= ', '. $self->title
402     if $self->title;
403   $data .= ' ('. $self->comment. ')'
404     if $self->comment;
405   $data;
406 }
407
408 sub contact_classname {
409   my $self = shift;
410   my $contact_class = $self->contact_class or return '';
411   $contact_class->classname;
412 }
413
414 =back
415
416 =head1 BUGS
417
418 =head1 SEE ALSO
419
420 L<FS::Record>, schema.html from the base documentation.
421
422 =cut
423
424 1;
425