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_enum('addr_clean', [ '', 'Y' ])
190 || $self->ut_alphan('location_type')
191 || $self->ut_textn('location_number')
192 || $self->ut_enum('location_kind', [ '', 'R', 'B' ] )
193 || $self->ut_alphan('geocode')
194 || $self->ut_alphan('district')
196 return $error if $error;
199 unless $self->latitude && $self->longitude;
201 return "No prospect or customer!" unless $self->prospectnum || $self->custnum;
202 return "Prospect and customer!" if $self->prospectnum && $self->custnum;
204 my $conf = new FS::Conf;
205 return 'Location kind is required'
206 if $self->prospectnum
207 && $conf->exists('prospect_main-alt_address_format')
208 && ! $self->location_kind;
210 unless ( qsearch('cust_main_county', {
211 'country' => $self->country,
214 return "Unknown state/county/country: ".
215 $self->state. "/". $self->county. "/". $self->country
216 unless qsearch('cust_main_county',{
217 'state' => $self->state,
218 'county' => $self->county,
219 'country' => $self->country,
228 Returns this locations's full country name
234 code2country($self->country);
239 Synonym for location_label
245 $self->location_label;
248 =item has_ship_address
250 Returns false since cust_location objects do not have a separate shipping
255 sub has_ship_address {
261 Returns a list of key/value pairs, with the following keys: address1, address2,
262 city, county, state, zip, country, geocode, location_type, location_number,
267 =item move_to HASHREF
269 Takes a hashref with one or more cust_location fields. Creates a duplicate
270 of the existing location with all fields set to the values in the hashref.
271 Moves all packages that use the existing location to the new one, then sets
272 the "disabled" flag on the old location. Returns nothing on success, an
273 error message on error.
281 local $SIG{HUP} = 'IGNORE';
282 local $SIG{INT} = 'IGNORE';
283 local $SIG{QUIT} = 'IGNORE';
284 local $SIG{TERM} = 'IGNORE';
285 local $SIG{TSTP} = 'IGNORE';
286 local $SIG{PIPE} = 'IGNORE';
288 my $oldAutoCommit = $FS::UID::AutoCommit;
289 local $FS::UID::AutoCommit = 0;
293 my $new = FS::cust_location->new({
295 'custnum' => $old->custnum,
296 'prospectnum' => $old->prospectnum,
299 $error = $new->insert;
301 $dbh->rollback if $oldAutoCommit;
302 return "Error creating location: $error";
305 my @pkgs = qsearch('cust_pkg', {
306 'locationnum' => $old->locationnum,
309 foreach my $cust_pkg (@pkgs) {
310 $error = $cust_pkg->change(
311 'locationnum' => $new->locationnum,
314 if ( $error and not ref($error) ) {
315 $dbh->rollback if $oldAutoCommit;
316 return "Error moving pkgnum ".$cust_pkg->pkgnum.": $error";
321 $error = $old->replace;
323 $dbh->rollback if $oldAutoCommit;
324 return "Error disabling old location: $error";
327 $dbh->commit if $oldAutoCommit;
333 Attempts to parse data for location_type and location_number from address1
341 return '' if $self->get('location_type')
342 || $self->get('location_number');
345 if ( 1 ) { #ikano, switch on via config
346 { no warnings 'void';
347 eval { 'use FS::part_export::ikano;' };
350 %parse = FS::part_export::ikano->location_types_parse;
355 foreach my $from ('address1', 'address2') {
356 foreach my $parse ( keys %parse ) {
357 my $value = $self->get($from);
358 if ( $value =~ s/(^|\W+)$parse\W+(\w+)\W*$//i ) {
359 $self->set('location_type', $parse{$parse});
360 $self->set('location_number', $2);
361 $self->set($from, $value);
367 #nothing matched, no changes
368 $self->get('address2')
369 ? "Can't parse unit type and number from address2"
375 Moves data from location_type and location_number to the end of address1.
382 #false laziness w/geocode_Mixin.pm::line
383 my $lt = $self->get('location_type');
387 if ( 1 ) { #ikano, switch on via config
388 { no warnings 'void';
389 eval { 'use FS::part_export::ikano;' };
392 %location_type = FS::part_export::ikano->location_types;
394 %location_type = (); #?
397 $self->address1( $self->address1. ' '. $location_type{$lt} || $lt );
398 $self->location_type('');
401 if ( length($self->location_number) ) {
402 $self->address1( $self->address1. ' '. $self->location_number );
403 $self->location_number('');
413 Not yet used for cust_main billing and shipping addresses.
417 L<FS::cust_main_county>, L<FS::cust_pkg>, L<FS::Record>,
418 schema.html from the base documentation.