1 package FS::cust_location;
2 use base qw( FS::geocode_Mixin FS::Record );
5 use vars qw( $import );
8 use FS::Record qw( qsearch ); #qsearchs );
10 use FS::prospect_main;
12 use FS::cust_main_county;
18 FS::cust_location - Object methods for cust_location records
22 use FS::cust_location;
24 $record = new FS::cust_location \%hash;
25 $record = new FS::cust_location { 'column' => 'value' };
27 $error = $record->insert;
29 $error = $new_record->replace($old_record);
31 $error = $record->delete;
33 $error = $record->check;
37 An FS::cust_location object represents a customer location. FS::cust_location
38 inherits from FS::Record. The following fields are currently supported:
52 Address line one (required)
56 Address line two (optional)
64 County (optional, see L<FS::cust_main_county>)
68 State (see L<FS::cust_main_county>)
76 Country (see L<FS::cust_main_county>)
84 Tax district code (optional)
88 Disabled flag; set to 'Y' to disable the location.
98 Creates a new location. To add the location to the database, see L<"insert">.
100 Note that this stores the hash reference, not a distinct copy of the hash it
101 points to. You can ask the object for a copy with the I<hash> method.
105 sub table { 'cust_location'; }
109 Adds this record to the database. If there is an error, returns the error,
110 otherwise returns false.
116 my $error = $self->SUPER::insert(@_);
118 #false laziness with cust_main, will go away eventually
119 my $conf = new FS::Conf;
120 if ( !$error and $conf->config('tax_district_method') ) {
122 my $queue = new FS::queue {
123 'job' => 'FS::geocode_Mixin::process_district_update'
125 $error = $queue->insert( ref($self), $self->locationnum );
134 Delete this record from the database.
136 =item replace OLD_RECORD
138 Replaces the OLD_RECORD with this one in the database. If there is an error,
139 returns the error, otherwise returns false.
146 $old ||= $self->replace_old;
147 my $error = $self->SUPER::replace($old);
149 #false laziness with cust_main, will go away eventually
150 my $conf = new FS::Conf;
151 if ( !$error and $conf->config('tax_district_method')
152 and $self->get('address1') ne $old->get('address1') ) {
154 my $queue = new FS::queue {
155 'job' => 'FS::geocode_Mixin::process_district_update'
157 $error = $queue->insert( ref($self), $self->locationnum );
167 Checks all fields to make sure this is a valid location. If there is
168 an error, returns the error, otherwise returns false. Called by the insert
173 #some false laziness w/cust_main, but since it should eventually lose these
179 $self->ut_numbern('locationnum')
180 || $self->ut_foreign_keyn('prospectnum', 'prospect_main', 'prospectnum')
181 || $self->ut_foreign_keyn('custnum', 'cust_main', 'custnum')
182 || $self->ut_text('address1')
183 || $self->ut_textn('address2')
184 || $self->ut_text('city')
185 || $self->ut_textn('county')
186 || $self->ut_textn('state')
187 || $self->ut_country('country')
188 || $self->ut_zip('zip', $self->country)
189 || $self->ut_coordn('latitude')
190 || $self->ut_coordn('longitude')
191 || $self->ut_enum('coord_auto', [ '', 'Y' ])
192 || $self->ut_alphan('location_type')
193 || $self->ut_textn('location_number')
194 || $self->ut_enum('location_kind', [ '', 'R', 'B' ] )
195 || $self->ut_alphan('geocode')
196 || $self->ut_alphan('district')
198 return $error if $error;
201 unless $import || ($self->latitude && $self->longitude);
203 return "No prospect or customer!" unless $self->prospectnum || $self->custnum;
204 return "Prospect and customer!" if $self->prospectnum && $self->custnum;
206 my $conf = new FS::Conf;
207 return 'Location kind is required'
208 if $self->prospectnum
209 && $conf->exists('prospect_main-alt_address_format')
210 && ! $self->location_kind;
212 unless ( qsearch('cust_main_county', {
213 'country' => $self->country,
216 return "Unknown state/county/country: ".
217 $self->state. "/". $self->county. "/". $self->country
218 unless qsearch('cust_main_county',{
219 'state' => $self->state,
220 'county' => $self->county,
221 'country' => $self->country,
230 Returns this locations's full country name
236 code2country($self->country);
241 Synonym for location_label
247 $self->location_label;
250 =item has_ship_address
252 Returns false since cust_location objects do not have a separate shipping
257 sub has_ship_address {
263 Returns a list of key/value pairs, with the following keys: address1, address2,
264 city, county, state, zip, country, geocode, location_type, location_number,
269 =item move_to HASHREF
271 Takes a hashref with one or more cust_location fields. Creates a duplicate
272 of the existing location with all fields set to the values in the hashref.
273 Moves all packages that use the existing location to the new one, then sets
274 the "disabled" flag on the old location. Returns nothing on success, an
275 error message on error.
283 local $SIG{HUP} = 'IGNORE';
284 local $SIG{INT} = 'IGNORE';
285 local $SIG{QUIT} = 'IGNORE';
286 local $SIG{TERM} = 'IGNORE';
287 local $SIG{TSTP} = 'IGNORE';
288 local $SIG{PIPE} = 'IGNORE';
290 my $oldAutoCommit = $FS::UID::AutoCommit;
291 local $FS::UID::AutoCommit = 0;
295 my $new = FS::cust_location->new({
297 'custnum' => $old->custnum,
298 'prospectnum' => $old->prospectnum,
301 $error = $new->insert;
303 $dbh->rollback if $oldAutoCommit;
304 return "Error creating location: $error";
307 my @pkgs = qsearch('cust_pkg', {
308 'locationnum' => $old->locationnum,
311 foreach my $cust_pkg (@pkgs) {
312 $error = $cust_pkg->change(
313 'locationnum' => $new->locationnum,
316 if ( $error and not ref($error) ) {
317 $dbh->rollback if $oldAutoCommit;
318 return "Error moving pkgnum ".$cust_pkg->pkgnum.": $error";
323 $error = $old->replace;
325 $dbh->rollback if $oldAutoCommit;
326 return "Error disabling old location: $error";
329 $dbh->commit if $oldAutoCommit;
335 Attempts to parse data for location_type and location_number from address1
343 return '' if $self->get('location_type')
344 || $self->get('location_number');
347 if ( 1 ) { #ikano, switch on via config
348 { no warnings 'void';
349 eval { 'use FS::part_export::ikano;' };
352 %parse = FS::part_export::ikano->location_types_parse;
357 foreach my $from ('address1', 'address2') {
358 foreach my $parse ( keys %parse ) {
359 my $value = $self->get($from);
360 if ( $value =~ s/(^|\W+)$parse\W+(\w+)\W*$//i ) {
361 $self->set('location_type', $parse{$parse});
362 $self->set('location_number', $2);
363 $self->set($from, $value);
369 #nothing matched, no changes
370 $self->get('address2')
371 ? "Can't parse unit type and number from address2"
377 Moves data from location_type and location_number to the end of address1.
384 #false laziness w/geocode_Mixin.pm::line
385 my $lt = $self->get('location_type');
389 if ( 1 ) { #ikano, switch on via config
390 { no warnings 'void';
391 eval { 'use FS::part_export::ikano;' };
394 %location_type = FS::part_export::ikano->location_types;
396 %location_type = (); #?
399 $self->address1( $self->address1. ' '. $location_type{$lt} || $lt );
400 $self->location_type('');
403 if ( length($self->location_number) ) {
404 $self->address1( $self->address1. ' '. $self->location_number );
405 $self->location_number('');
413 Returns the label of the location object, with an optional site ID
414 string (based on the cust_location-label_prefix config option).
421 my $conf = new FS::Conf;
423 my $format = $conf->config('cust_location-label_prefix') || '';
424 if ( $format eq 'CoStAg' ) {
425 my $cust_or_prospect;
426 if ( $self->custnum ) {
427 $cust_or_prospect = FS::cust_main->by_key($self->custnum);
429 elsif ( $self->prospectnum ) {
430 $cust_or_prospect = FS::prospect_main->by_key($self->prospectnum);
432 my $agent = $conf->config('cust_main-custnum-display_prefix',
433 $cust_or_prospect->agentnum)
434 || $cust_or_prospect->agent->agent;
435 # else this location is invalid
436 $prefix = uc( join('',
438 ($self->state =~ /^(..)/),
440 sprintf('%05d', $self->locationnum)
443 $prefix .= ($opt{join_string} || ': ') if $prefix;
444 $prefix . $self->SUPER::location_label(%opt);
447 =item county_state_county
449 Returns a string consisting of just the county, state and country.
453 sub county_state_country {
455 my $label = $self->country;
456 $label = $self->state.", $label" if $self->state;
457 $label = $self->county." County, $label" if $self->county;
465 Not yet used for cust_main billing and shipping addresses.
469 L<FS::cust_main_county>, L<FS::cust_pkg>, L<FS::Record>,
470 schema.html from the base documentation.