5590f8869ff3c7f335f4974783e6a1b87770836c
[freeside.git] / FS / FS / cust_main / Location.pm
1 package FS::cust_main::Location;
2
3 use strict;
4 use vars qw( $DEBUG $me @location_fields );
5 use FS::Record qw(qsearch qsearchs);
6 use FS::UID qw(dbh);
7 use FS::cust_location;
8
9 use Carp qw(carp);
10
11 $DEBUG = 0;
12 $me = '[FS::cust_main::Location]';
13
14 my $init = 0;
15 BEGIN {
16   # set up accessors for location fields
17   if (!$init) {
18     no strict 'refs';
19     @location_fields = 
20       qw( address1 address2 city county state zip country district
21         latitude longitude coord_auto censustract censusyear geocode
22         addr_clean );
23
24     foreach my $f (@location_fields) {
25       *{"FS::cust_main::Location::$f"} = sub {
26         carp "WARNING: tried to set cust_main.$f with accessor" if (@_ > 1);
27         shift->bill_location->$f
28       };
29       *{"FS::cust_main::Location::ship_$f"} = sub {
30         carp "WARNING: tried to set cust_main.ship_$f with accessor" if (@_ > 1);
31         shift->ship_location->$f
32       };
33     }
34     $init++;
35   }
36 }
37
38 #debugging shim--probably a performance hit, so remove this at some point
39 sub get {
40   my $self = shift;
41   my $field = shift;
42   if ( $DEBUG and grep (/^(ship_)?($field)$/, @location_fields) ) {
43     carp "WARNING: tried to get() location field $field";
44     $self->$field;
45   }
46   $self->FS::Record::get($field);
47 }
48
49 =head1 NAME
50
51 FS::cust_main::Location - Location-related methods for cust_main
52
53 =head1 DESCRIPTION
54
55 These methods are available on FS::cust_main objects;
56
57 =head1 METHODS
58
59 =over 4
60
61 =item bill_location
62
63 Returns an L<FS::cust_location> object for the customer's billing address.
64
65 =cut
66
67 sub bill_location {
68   my $self = shift;
69   $self->hashref->{bill_location} 
70     ||= FS::cust_location->by_key($self->bill_locationnum);
71 }
72
73 =item ship_location
74
75 Returns an L<FS::cust_location> object for the customer's service address.
76
77 =cut
78
79 sub ship_location {
80   my $self = shift;
81   $self->hashref->{ship_location}
82     ||= FS::cust_location->by_key($self->ship_locationnum);
83 }
84
85 =item location TYPE
86
87 An alternative way of saying "bill_location or ship_location, depending on 
88 if TYPE is 'bill' or 'ship'".
89
90 =cut
91
92 sub location {
93   my $self = shift;
94   return $self->bill_location if $_[0] eq 'bill';
95   return $self->ship_location if $_[0] eq 'ship';
96   die "bad location type '$_[0]'";
97 }
98
99 =back
100
101 =head1 CLASS METHODS
102
103 =over 4
104
105 =item location_fields
106
107 Returns a list of fields found in the location objects.  All of these fields
108 can be read (but not written) by calling them as methods on the 
109 L<FS::cust_main> object (prefixed with 'ship_' for the service address 
110 fields).
111
112 =cut
113
114 sub location_fields { @location_fields }
115
116 sub _upgrade_data {
117   my $class = shift;
118   eval "use FS::contact;
119         use FS::contact_class;
120         use FS::contact_phone;
121         use FS::phone_type";
122
123   local $FS::cust_location::import = 1;
124   local $DEBUG = 0;
125   my $error;
126
127   my $tax_prefix = 'bill_';
128   if ( FS::Conf->new->exists('tax-ship_address') ) {
129     $tax_prefix = 'ship_';
130   }
131
132   # Step 0: set up contact classes and phone types
133   my $service_contact_class = 
134     qsearchs('contact_class', { classname => 'Service'})
135     || new FS::contact_class { classname => 'Service'};
136
137   if ( !$service_contact_class->classnum ) {
138     $error = $service_contact_class->insert;
139     die "error creating contact class for Service: $error" if $error;
140   }
141   my %phone_type = ( # fudge slightly
142     daytime => 'Work',
143     night   => 'Home',
144     mobile  => 'Mobile',
145     fax     => 'Fax'
146   );
147   my $w = 10;
148   foreach (keys %phone_type) {
149     $phone_type{$_} = qsearchs('phone_type', { typename => $phone_type{$_}})
150                       || new FS::phone_type  { typename => $phone_type{$_},
151                                                weight   => $w };
152     # just in case someone still doesn't have these
153     if ( !$phone_type{$_}->phonetypenum ) {
154       $error = $phone_type{$_}->insert;
155       die "error creating phone type '$_': $error" if $error;
156     }
157   }
158
159   foreach my $cust_main (qsearch('cust_main', { bill_locationnum => '' })) {
160     # Step 1: extract billing and service addresses into cust_location
161     my $custnum = $cust_main->custnum;
162     my $bill_location = FS::cust_location->new(
163       {
164         custnum => $custnum,
165         map { $_ => $cust_main->get($_) } location_fields(),
166       }
167     );
168     $bill_location->set('censustract', ''); # properly goes with ship_location
169     my $ship_location = $bill_location; # until proven otherwise
170
171     if ( $cust_main->get('ship_address1') ) {
172       # detect duplicates
173       my $same = 1;
174       foreach (location_fields()) {
175         if ( length($cust_main->get("ship_$_")) and
176              $cust_main->get($_) ne $cust_main->get("ship_$_") ) {
177           $same = 0;
178         }
179       }
180
181       if ( !$same ) {
182         $ship_location = FS::cust_location->new(
183           {
184             custnum => $custnum,
185             map { $_ => $cust_main->get("ship_$_") } location_fields()
186           }
187         );
188       } # else it stays equal to $bill_location
189
190       $ship_location->set('censustract', $cust_main->get('censustract'));
191
192       # Step 2: Extract shipping address contact fields into contact
193       my %unlike = map { $_ => 1 }
194         grep { $cust_main->get($_) ne $cust_main->get("ship_$_") }
195         qw( last first company daytime night fax mobile );
196
197       if ( %unlike ) {
198         # then there IS a service contact
199         my $contact = FS::contact->new({
200           'custnum'     => $custnum,
201           'classnum'    => $service_contact_class->classnum,
202           'locationnum' => $ship_location->locationnum,
203           'last'        => $cust_main->get('ship_last'),
204           'first'       => $cust_main->get('ship_first'),
205         });
206         if ( !$cust_main->get('ship_last') or !$cust_main->get('ship_first') )
207         {
208           warn "customer $custnum has no service contact name; substituting ".
209                "customer name\n";
210           $contact->set('last' => $cust_main->get('last'));
211           $contact->set('first' => $cust_main->get('first'));
212         }
213
214         if ( $unlike{'company'} ) {
215           # there's no contact.company field, but keep a record of it
216           $contact->set(comment => 'Company: '.$cust_main->get('ship_company'));
217         }
218         $error = $contact->insert;
219         die "error migrating service contact for customer $custnum: $error"
220           if $error;
221
222         foreach ( grep { $unlike{$_} } qw( daytime night fax mobile ) ) {
223           my $phone = $cust_main->get("ship_$_");
224           next if !$phone;
225           my $contact_phone = FS::contact_phone->new({
226             'contactnum'    => $contact->contactnum,
227             'phonetypenum'  => $phone_type{$_}->phonetypenum,
228             FS::contact::_parse_phonestring( $phone )
229           });
230           $error = $contact_phone->insert;
231           # die "whose responsible this"
232           die "error migrating service contact phone for customer $custnum: $error"
233             if $error;
234           $cust_main->set("ship_$_" => '');
235         }
236
237         $cust_main->set("ship_$_" => '') foreach qw(last first company);
238       } #if %unlike
239     } #if ship_address1
240
241     # special case: should go with whichever location is used to calculate
242     # taxes, because that's the one it originally came from
243     if ( my $geocode = $cust_main->get('geocode') ) {
244       $bill_location->set('geocode' => '');
245       $ship_location->set('geocode' => '');
246
247       if ( $tax_prefix eq 'bill_' ) {
248         $bill_location->set('geocode', $geocode);
249       } elsif ( $tax_prefix eq 'ship_' ) {
250         $ship_location->set('geocode', $geocode);
251       }
252     }
253
254     $error = $bill_location->insert;
255     die "error migrating billing address for customer $custnum: $error"
256       if $error;
257
258     $cust_main->set(bill_locationnum => $bill_location->locationnum);
259
260     if (!$ship_location->locationnum) {
261       $error = $ship_location->insert;
262       die "error migrating service address for customer $custnum: $error"
263         if $error;
264     }
265
266     $cust_main->set(ship_locationnum => $ship_location->locationnum);
267
268     # Step 3: Wipe the migrated fields and update the cust_main
269
270     $cust_main->set("ship_$_" => '') foreach location_fields();
271     $cust_main->set($_ => '') foreach location_fields();
272
273     $error = $cust_main->replace;
274     die "error migrating addresses for customer $custnum: $error"
275       if $error;
276
277     # Step 4: set packages at the "default service location" to ship_location
278     foreach my $cust_pkg (
279       qsearch('cust_pkg', { custnum => $custnum, locationnum => '' })  
280     ) {
281       # not a location change
282       $cust_pkg->set('locationnum', $cust_main->ship_locationnum);
283       $error = $cust_pkg->replace;
284       die "error migrating package ".$cust_pkg->pkgnum.": $error"
285         if $error;
286     }
287
288   } #foreach $cust_main
289 }
290
291 =back
292
293 =cut
294
295 1;