1 package FS::cust_location;
4 use base qw( FS::geocode_Mixin FS::Record );
7 use FS::Record qw( qsearch ); #qsearchs );
11 use FS::cust_main_county;
15 FS::cust_location - Object methods for cust_location records
19 use FS::cust_location;
21 $record = new FS::cust_location \%hash;
22 $record = new FS::cust_location { 'column' => 'value' };
24 $error = $record->insert;
26 $error = $new_record->replace($old_record);
28 $error = $record->delete;
30 $error = $record->check;
34 An FS::cust_location object represents a customer location. FS::cust_location
35 inherits from FS::Record. The following fields are currently supported:
49 Address line one (required)
53 Address line two (optional)
61 County (optional, see L<FS::cust_main_county>)
65 State (see L<FS::cust_main_county>)
73 Country (see L<FS::cust_main_county>)
81 Tax district code (optional)
85 Disabled flag; set to 'Y' to disable the location.
95 Creates a new location. To add the location to the database, see L<"insert">.
97 Note that this stores the hash reference, not a distinct copy of the hash it
98 points to. You can ask the object for a copy with the I<hash> method.
102 sub table { 'cust_location'; }
106 Adds this record to the database. If there is an error, returns the error,
107 otherwise returns false.
113 my $error = $self->SUPER::insert(@_);
115 #false laziness with cust_main, will go away eventually
116 my $conf = new FS::Conf;
117 if ( !$error and $conf->config('tax_district_method') ) {
119 my $queue = new FS::queue {
120 'job' => 'FS::geocode_Mixin::process_district_update'
122 $error = $queue->insert( ref($self), $self->locationnum );
131 Delete this record from the database.
133 =item replace OLD_RECORD
135 Replaces the OLD_RECORD with this one in the database. If there is an error,
136 returns the error, otherwise returns false.
143 $old ||= $self->replace_old;
144 my $error = $self->SUPER::replace($old);
146 #false laziness with cust_main, will go away eventually
147 my $conf = new FS::Conf;
148 if ( !$error and $conf->config('tax_district_method')
149 and $self->get('address1') ne $old->get('address1') ) {
151 my $queue = new FS::queue {
152 'job' => 'FS::geocode_Mixin::process_district_update'
154 $error = $queue->insert( ref($self), $self->locationnum );
164 Checks all fields to make sure this is a valid location. If there is
165 an error, returns the error, otherwise returns false. Called by the insert
170 #some false laziness w/cust_main, but since it should eventually lose these
176 $self->ut_numbern('locationnum')
177 || $self->ut_foreign_keyn('prospectnum', 'prospect_main', 'prospectnum')
178 || $self->ut_foreign_keyn('custnum', 'cust_main', 'custnum')
179 || $self->ut_text('address1')
180 || $self->ut_textn('address2')
181 || $self->ut_text('city')
182 || $self->ut_textn('county')
183 || $self->ut_textn('state')
184 || $self->ut_country('country')
185 || $self->ut_zip('zip', $self->country)
186 || $self->ut_coordn('latitude')
187 || $self->ut_coordn('longitude')
188 || $self->ut_enum('coord_auto', [ '', 'Y' ])
189 || $self->ut_alphan('location_type')
190 || $self->ut_textn('location_number')
191 || $self->ut_enum('location_kind', [ '', 'R', 'B' ] )
192 || $self->ut_alphan('geocode')
193 || $self->ut_alphan('district')
195 return $error if $error;
198 unless $self->latitude && $self->longitude;
200 return "No prospect or customer!" unless $self->prospectnum || $self->custnum;
201 return "Prospect and customer!" if $self->prospectnum && $self->custnum;
203 my $conf = new FS::Conf;
204 return 'Location kind is required'
205 if $self->prospectnum
206 && $conf->exists('prospect_main-alt_address_format')
207 && ! $self->location_kind;
209 unless ( qsearch('cust_main_county', {
210 'country' => $self->country,
213 return "Unknown state/county/country: ".
214 $self->state. "/". $self->county. "/". $self->country
215 unless qsearch('cust_main_county',{
216 'state' => $self->state,
217 'county' => $self->county,
218 'country' => $self->country,
227 Returns this locations's full country name
233 code2country($self->country);
238 Synonym for location_label
244 $self->location_label;
247 =item has_ship_address
249 Returns false since cust_location objects do not have a separate shipping
254 sub has_ship_address {
260 Returns a list of key/value pairs, with the following keys: address1, address2,
261 city, county, state, zip, country, geocode, location_type, location_number,
266 =item move_to HASHREF
268 Takes a hashref with one or more cust_location fields. Creates a duplicate
269 of the existing location with all fields set to the values in the hashref.
270 Moves all packages that use the existing location to the new one, then sets
271 the "disabled" flag on the old location. Returns nothing on success, an
272 error message on error.
280 local $SIG{HUP} = 'IGNORE';
281 local $SIG{INT} = 'IGNORE';
282 local $SIG{QUIT} = 'IGNORE';
283 local $SIG{TERM} = 'IGNORE';
284 local $SIG{TSTP} = 'IGNORE';
285 local $SIG{PIPE} = 'IGNORE';
287 my $oldAutoCommit = $FS::UID::AutoCommit;
288 local $FS::UID::AutoCommit = 0;
292 my $new = FS::cust_location->new({
294 'custnum' => $old->custnum,
295 'prospectnum' => $old->prospectnum,
298 $error = $new->insert;
300 $dbh->rollback if $oldAutoCommit;
301 return "Error creating location: $error";
304 my @pkgs = qsearch('cust_pkg', {
305 'locationnum' => $old->locationnum,
308 foreach my $cust_pkg (@pkgs) {
309 $error = $cust_pkg->change(
310 'locationnum' => $new->locationnum,
313 if ( $error and not ref($error) ) {
314 $dbh->rollback if $oldAutoCommit;
315 return "Error moving pkgnum ".$cust_pkg->pkgnum.": $error";
320 $error = $old->replace;
322 $dbh->rollback if $oldAutoCommit;
323 return "Error disabling old location: $error";
326 $dbh->commit if $oldAutoCommit;
332 Attempts to parse data for location_type and location_number from address1
340 return '' if $self->get('location_type')
341 || $self->get('location_number');
344 if ( 1 ) { #ikano, switch on via config
345 { no warnings 'void';
346 eval { 'use FS::part_export::ikano;' };
349 %parse = FS::part_export::ikano->location_types_parse;
354 foreach my $from ('address1', 'address2') {
355 foreach my $parse ( keys %parse ) {
356 my $value = $self->get($from);
357 if ( $value =~ s/(^|\W+)$parse\W+(\w+)\W*$//i ) {
358 $self->set('location_type', $parse{$parse});
359 $self->set('location_number', $2);
360 $self->set($from, $value);
366 #nothing matched, no changes
367 $self->get('address2')
368 ? "Can't parse unit type and number from address2"
374 Moves data from location_type and location_number to the end of address1.
381 #false laziness w/geocode_Mixin.pm::line
382 my $lt = $self->get('location_type');
386 if ( 1 ) { #ikano, switch on via config
387 { no warnings 'void';
388 eval { 'use FS::part_export::ikano;' };
391 %location_type = FS::part_export::ikano->location_types;
393 %location_type = (); #?
396 $self->address1( $self->address1. ' '. $location_type{$lt} || $lt );
397 $self->location_type('');
400 if ( length($self->location_number) ) {
401 $self->address1( $self->address1. ' '. $self->location_number );
402 $self->location_number('');
412 Not yet used for cust_main billing and shipping addresses.
416 L<FS::cust_main_county>, L<FS::cust_pkg>, L<FS::Record>,
417 schema.html from the base documentation.