use custnum-display_prefix in fancy location labels, #16815
[freeside.git] / FS / FS / cust_location.pm
index da586f0..bcdb50c 100644 (file)
@@ -1,13 +1,18 @@
 package FS::cust_location;
 package FS::cust_location;
+use base qw( FS::geocode_Mixin FS::Record );
 
 use strict;
 
 use strict;
-use base qw( FS::Record );
+use vars qw( $import );
 use Locale::Country;
 use Locale::Country;
+use FS::UID qw( dbh );
 use FS::Record qw( qsearch ); #qsearchs );
 use FS::Record qw( qsearch ); #qsearchs );
+use FS::Conf;
 use FS::prospect_main;
 use FS::cust_main;
 use FS::cust_main_county;
 
 use FS::prospect_main;
 use FS::cust_main;
 use FS::cust_main_county;
 
+$import = 0;
+
 =head1 NAME
 
 FS::cust_location - Object methods for cust_location records
 =head1 NAME
 
 FS::cust_location - Object methods for cust_location records
@@ -74,6 +79,14 @@ Country (see L<FS::cust_main_county>)
 
 Geocode
 
 
 Geocode
 
+=item district
+
+Tax district code (optional)
+
+=item disabled
+
+Disabled flag; set to 'Y' to disable the location.
+
 =back
 
 =head1 METHODS
 =back
 
 =head1 METHODS
@@ -96,6 +109,26 @@ sub table { 'cust_location'; }
 Adds this record to the database.  If there is an error, returns the error,
 otherwise returns false.
 
 Adds this record to the database.  If there is an error, returns the error,
 otherwise returns false.
 
+=cut
+
+sub insert {
+  my $self = shift;
+  my $error = $self->SUPER::insert(@_);
+
+  #false laziness with cust_main, will go away eventually
+  my $conf = new FS::Conf;
+  if ( !$error and $conf->config('tax_district_method') ) {
+
+    my $queue = new FS::queue {
+      'job' => 'FS::geocode_Mixin::process_district_update'
+    };
+    $error = $queue->insert( ref($self), $self->locationnum );
+
+  }
+
+  $error || '';
+}
+
 =item delete
 
 Delete this record from the database.
 =item delete
 
 Delete this record from the database.
@@ -105,6 +138,30 @@ Delete this record from the database.
 Replaces the OLD_RECORD with this one in the database.  If there is an error,
 returns the error, otherwise returns false.
 
 Replaces the OLD_RECORD with this one in the database.  If there is an error,
 returns the error, otherwise returns false.
 
+=cut
+
+sub replace {
+  my $self = shift;
+  my $old = shift;
+  $old ||= $self->replace_old;
+  my $error = $self->SUPER::replace($old);
+
+  #false laziness with cust_main, will go away eventually
+  my $conf = new FS::Conf;
+  if ( !$error and $conf->config('tax_district_method') 
+    and $self->get('address1') ne $old->get('address1') ) {
+
+    my $queue = new FS::queue {
+      'job' => 'FS::geocode_Mixin::process_district_update'
+    };
+    $error = $queue->insert( ref($self), $self->locationnum );
+
+  }
+
+  $error || '';
+}
+
+
 =item check
 
 Checks all fields to make sure this is a valid location.  If there is
 =item check
 
 Checks all fields to make sure this is a valid location.  If there is
@@ -129,13 +186,29 @@ sub check {
     || $self->ut_textn('state')
     || $self->ut_country('country')
     || $self->ut_zip('zip', $self->country)
     || $self->ut_textn('state')
     || $self->ut_country('country')
     || $self->ut_zip('zip', $self->country)
+    || $self->ut_coordn('latitude')
+    || $self->ut_coordn('longitude')
+    || $self->ut_enum('coord_auto', [ '', 'Y' ])
+    || $self->ut_alphan('location_type')
+    || $self->ut_textn('location_number')
+    || $self->ut_enum('location_kind', [ '', 'R', 'B' ] )
     || $self->ut_alphan('geocode')
     || $self->ut_alphan('geocode')
+    || $self->ut_alphan('district')
   ;
   return $error if $error;
 
   ;
   return $error if $error;
 
+  $self->set_coord
+    unless $import || ($self->latitude && $self->longitude);
+
   return "No prospect or customer!" unless $self->prospectnum || $self->custnum;
   return "Prospect and customer!"       if $self->prospectnum && $self->custnum;
 
   return "No prospect or customer!" unless $self->prospectnum || $self->custnum;
   return "Prospect and customer!"       if $self->prospectnum && $self->custnum;
 
+  my $conf = new FS::Conf;
+  return 'Location kind is required'
+    if $self->prospectnum
+    && $conf->exists('prospect_main-alt_address_format')
+    && ! $self->location_kind;
+
   unless ( qsearch('cust_main_county', {
     'country' => $self->country,
     'state'   => '',
   unless ( qsearch('cust_main_county', {
     'country' => $self->country,
     'state'   => '',
@@ -163,75 +236,212 @@ sub country_full {
   code2country($self->country);
 }
 
   code2country($self->country);
 }
 
-=item location_label [ OPTION => VALUE ... ]
+=item line
 
 
-Returns the label of the service location for this customer.
+Synonym for location_label
 
 
-Options are
+=cut
 
 
-=over 4
+sub line {
+  my $self = shift;
+  $self->location_label;
+}
 
 
-=item join_string
+=item has_ship_address
 
 
-used to separate the address elements (defaults to ', ')
+Returns false since cust_location objects do not have a separate shipping
+address.
 
 
-=item escape_function
+=cut
 
 
+sub has_ship_address {
+  '';
+}
 
 
-a callback used for escaping the text of the address elements
+=item location_hash
 
 
-=back
+Returns a list of key/value pairs, with the following keys: address1, address2,
+city, county, state, zip, country, geocode, location_type, location_number,
+location_kind.
 
 =cut
 
 
 =cut
 
-# false laziness with FS::cust_main::location_label
+=item move_to HASHREF
 
 
-sub location_label {
+Takes a hashref with one or more cust_location fields.  Creates a duplicate 
+of the existing location with all fields set to the values in the hashref.  
+Moves all packages that use the existing location to the new one, then sets 
+the "disabled" flag on the old location.  Returns nothing on success, an 
+error message on error.
+
+=cut
+
+sub move_to {
+  my $old = shift;
+  my $hashref = shift;
+
+  local $SIG{HUP} = 'IGNORE';
+  local $SIG{INT} = 'IGNORE';
+  local $SIG{QUIT} = 'IGNORE';
+  local $SIG{TERM} = 'IGNORE';
+  local $SIG{TSTP} = 'IGNORE';
+  local $SIG{PIPE} = 'IGNORE';
+
+  my $oldAutoCommit = $FS::UID::AutoCommit;
+  local $FS::UID::AutoCommit = 0;
+  my $dbh = dbh;
+  my $error = '';
+
+  my $new = FS::cust_location->new({
+      $old->location_hash,
+      'custnum'     => $old->custnum,
+      'prospectnum' => $old->prospectnum,
+      %$hashref
+    });
+  $error = $new->insert;
+  if ( $error ) {
+    $dbh->rollback if $oldAutoCommit;
+    return "Error creating location: $error";
+  }
+
+  my @pkgs = qsearch('cust_pkg', { 
+      'locationnum' => $old->locationnum,
+      'cancel' => '' 
+    });
+  foreach my $cust_pkg (@pkgs) {
+    $error = $cust_pkg->change(
+      'locationnum' => $new->locationnum,
+      'keep_dates'  => 1
+    );
+    if ( $error and not ref($error) ) {
+      $dbh->rollback if $oldAutoCommit;
+      return "Error moving pkgnum ".$cust_pkg->pkgnum.": $error";
+    }
+  }
+
+  $old->disabled('Y');
+  $error = $old->replace;
+  if ( $error ) {
+    $dbh->rollback if $oldAutoCommit;
+    return "Error disabling old location: $error";
+  }
+
+  $dbh->commit if $oldAutoCommit;
+  return;
+}
+
+=item alternize
+
+Attempts to parse data for location_type and location_number from address1
+and address2.
+
+=cut
+
+sub alternize {
   my $self = shift;
   my $self = shift;
-  my %opt = @_;
 
 
-  my $separator = $opt{join_string} || ', ';
-  my $escape = $opt{escape_function} || sub{ shift };
-  my $ds = $opt{double_space} || '  ';
-  my $line = '';
-  my $cydefault =
-    $opt{'countrydefault'} || FS::Conf->new->config('countrydefault') || 'US';
-  my $prefix = '';
+  return '' if $self->get('location_type')
+            || $self->get('location_number');
 
 
-  my $notfirst = 0;
-  foreach (qw ( address1 address2 ) ) {
-    my $method = "$prefix$_";
-    $line .= ($notfirst ? $separator : ''). &$escape($self->$method)
-      if $self->$method;
-    $notfirst++;
+  my %parse;
+  if ( 1 ) { #ikano, switch on via config
+    { no warnings 'void';
+      eval { 'use FS::part_export::ikano;' };
+      die $@ if $@;
+    }
+    %parse = FS::part_export::ikano->location_types_parse;
+  } else {
+    %parse = (); #?
   }
   }
-  $notfirst = 0;
-  foreach (qw ( city county state zip ) ) {
-    my $method = "$prefix$_";
-    if ( $self->$method ) {
-      $line .= ($notfirst ? ($method eq 'zip' ? $ds : ' ') : $separator);
-      $line .= '(' if $method eq 'county';
-      $line .= &$escape($self->$method);
-      $line .= ')' if $method eq 'county';
-      $notfirst++;
+
+  foreach my $from ('address1', 'address2') {
+    foreach my $parse ( keys %parse ) {
+      my $value = $self->get($from);
+      if ( $value =~ s/(^|\W+)$parse\W+(\w+)\W*$//i ) {
+        $self->set('location_type', $parse{$parse});
+        $self->set('location_number', $2);
+        $self->set($from, $value);
+        return '';
+      }
     }
     }
-    $line .= ',' if $method eq 'county';
   }
   }
-  $line .= $separator. &$escape(code2country($self->country))
-    if $self->country ne $cydefault;
 
 
-  $line;
+  #nothing matched, no changes
+  $self->get('address2')
+    ? "Can't parse unit type and number from address2"
+    : '';
 }
 
 }
 
-=item line
+=item dealternize
 
 
-Synonym for location_label
+Moves data from location_type and location_number to the end of address1.
 
 =cut
 
 
 =cut
 
-sub line {
+sub dealternize {
   my $self = shift;
   my $self = shift;
-  $self->location_label;
+
+  #false laziness w/geocode_Mixin.pm::line
+  my $lt = $self->get('location_type');
+  if ( $lt ) {
+
+    my %location_type;
+    if ( 1 ) { #ikano, switch on via config
+      { no warnings 'void';
+        eval { 'use FS::part_export::ikano;' };
+        die $@ if $@;
+      }
+      %location_type = FS::part_export::ikano->location_types;
+    } else {
+      %location_type = (); #?
+    }
+
+    $self->address1( $self->address1. ' '. $location_type{$lt} || $lt );
+    $self->location_type('');
+  }
+
+  if ( length($self->location_number) ) {
+    $self->address1( $self->address1. ' '. $self->location_number );
+    $self->location_number('');
+  }
+  '';
+}
+
+=item location_label
+
+Returns the label of the location object, with an optional site ID
+string (based on the cust_location-label_prefix config option).
+
+=cut
+
+sub location_label {
+  my $self = shift;
+  my %opt = @_;
+  my $conf = new FS::Conf;
+  my $prefix = '';
+  my $format = $conf->config('cust_location-label_prefix') || '';
+  if ( $format eq 'CoStAg' ) {
+    my $cust_or_prospect;
+    if ( $self->custnum ) {
+      $cust_or_prospect = FS::cust_main->by_key($self->custnum);
+    }
+    elsif ( $self->prospectnum )  {
+      $cust_or_prospect = FS::prospect_main->by_key($self->prospectnum);
+    }
+    my $agent = $conf->config('cust_main-custnum-display_prefix',
+                  $cust_or_prospect->agentnum)
+                || $cust_or_prospect->agent->agent;
+    # else this location is invalid
+    $prefix = uc( join('',
+        $self->country,
+        ($self->state =~ /^(..)/),
+        ($agent =~ /^(..)/),
+        sprintf('%05d', $self->locationnum)
+    ) );
+  }
+  $prefix .= ($opt{join_string} ||  ': ') if $prefix;
+  $prefix . $self->SUPER::location_label(%opt);
 }
 
 =back
 }
 
 =back