[freeside-commits] branch FREESIDE_4_BRANCH updated. f805cf286bb990b4bc99cc8136ffbaeef00af805

Jonathan Prykop jonathan at 420.am
Fri Apr 22 10:25:18 PDT 2016


The branch, FREESIDE_4_BRANCH has been updated
       via  f805cf286bb990b4bc99cc8136ffbaeef00af805 (commit)
       via  89e3f368f19ae733b2f82531d73bba2d7bf9e25a (commit)
       via  5c321cb0e6488b0b76fec03395ec815703867b22 (commit)
       via  fd0483007b898b9944cc31fd5d6e90932fe2c8c0 (commit)
       via  401aff3ec100c05c64f9eb0d003494758a3e74ee (commit)
      from  aa2b3ef7970a9ba5b5ac0806df05350c988a3997 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit f805cf286bb990b4bc99cc8136ffbaeef00af805
Author: Jonathan Prykop <jonathan at freeside.biz>
Date:   Wed Apr 20 19:48:53 2016 -0500

    RT#29296: API stuff: Add new locations [added eval field specs, debugged]

diff --git a/FS/FS/API.pm b/FS/FS/API.pm
index 1c4cbd9..9d0ef25 100644
--- a/FS/FS/API.pm
+++ b/FS/FS/API.pm
@@ -568,6 +568,8 @@ secret
 
 locationnum - pass this, or the following keys (don't pass both)
 
+locationname
+
 address1
 
 address2
@@ -580,8 +582,22 @@ state
 
 zip
 
+addr_clean
+
 country
 
+censustract
+
+censusyear
+
+location_type
+
+location_number
+
+location_kind
+
+incorporated
+
 On error, returns a hashref with an 'error' key.
 On success, returns a hashref with 'pkgnum' and 'locationnum' keys,
 containing the new values.
@@ -598,7 +614,24 @@ sub change_package_location {
 
   my %changeopt;
 
-  foreach my $field ('locationnum',FS::cust_location::API::API_editable_fields()) {
+  foreach my $field ( qw(
+    locationnum
+    locationname
+    address1
+    address2
+    city
+    county
+    state
+    zip
+    addr_clean
+    country
+    censustract
+    censusyear
+    location_type
+    location_number
+    location_kind
+    incorporated
+  )) {
     $changeopt{$field} = $opt{$field} if $opt{$field};
   }
 
diff --git a/FS/FS/cust_location/API.pm b/FS/FS/cust_location/API.pm
deleted file mode 100644
index 7d9140f..0000000
--- a/FS/FS/cust_location/API.pm
+++ /dev/null
@@ -1,34 +0,0 @@
-package FS::cust_location::API;
-
-use strict;
-
-# this gets used by FS::cust_main::API and FS::cust_pkg::API,
-# so don't use FS::cust_main or FS::cust_pkg here
-
-# some of these could probably be included, but in general,
-# probably a bad idea to expose everything in 
-# cust_main::Location::location_fields by default
-#
-#locationname
-#district
-#latitude
-#longitude
-#censustract
-#censusyear
-#geocode
-#coord_auto
-#addr_clean
-
-sub API_editable_fields {
-  return qw(
-    address1
-    address2
-    city
-    county
-    state
-    zip
-    country
-  );
-}
-
-1;
diff --git a/FS/FS/cust_main/API.pm b/FS/FS/cust_main/API.pm
index f9ba140..2d6da9e 100644
--- a/FS/FS/cust_main/API.pm
+++ b/FS/FS/cust_main/API.pm
@@ -4,7 +4,6 @@ use strict;
 use FS::Conf;
 use FS::part_tag;
 use FS::Record qw( qsearchs );
-use FS::cust_location::API;
 
 =item API_getinfo FIELD => VALUE, ...
 
@@ -23,7 +22,9 @@ use vars qw(
 );
 #  locale
 #  ss stateid stateid_state
- at location_editable_fields = FS::cust_location::API::API_editable_fields();
+ at location_editable_fields = qw(
+  address1 address2 city county state zip country
+);
 
 sub API_getinfo {
   my( $self, %opt ) = @_;
diff --git a/FS/FS/cust_pkg/API.pm b/FS/FS/cust_pkg/API.pm
index 7ef3f72..837cf40 100644
--- a/FS/FS/cust_pkg/API.pm
+++ b/FS/FS/cust_pkg/API.pm
@@ -2,8 +2,6 @@ package FS::cust_pkg::API;
 
 use strict;
 
-use FS::cust_location::API;
-
 sub API_getinfo {
   my $self = shift;
 
@@ -25,7 +23,23 @@ sub API_change {
 
   # update location--accepts raw fields OR location
   my %location_hash;
-  foreach my $field (FS::cust_location::API::API_editable_fields()) {
+  foreach my $field ( qw(
+    locationname
+    address1
+    address2
+    city
+    county
+    state
+    zip
+    addr_clean
+    country
+    censustract
+    censusyear
+    location_type
+    location_number
+    location_kind
+    incorporated
+  ) ) {
     $location_hash{$field} = $opt{$field} if $opt{$field};
   }
   return { 'error' => 'Cannot pass both locationnum and location fields' }
diff --git a/FS/FS/part_export/cust_http.pm b/FS/FS/part_export/cust_http.pm
index f72d006..c13e18d 100644
--- a/FS/FS/part_export/cust_http.pm
+++ b/FS/FS/part_export/cust_http.pm
@@ -55,7 +55,7 @@ tie %options, 'Tie::IxHash',
 ;
 
 %info = (
-  'svc'     => [qw( cust_main cust_location )],
+  'svc'     => [qw( cust_main )],
   'desc'    => 'Send an HTTP or HTTPS GET or POST request, for customers.',
   'options' => \%options,
   'no_machine' => 1,
diff --git a/FS/FS/part_export/cust_location_http.pm b/FS/FS/part_export/cust_location_http.pm
index e2830db..fe77223 100644
--- a/FS/FS/part_export/cust_location_http.pm
+++ b/FS/FS/part_export/cust_location_http.pm
@@ -4,9 +4,28 @@ use strict;
 use base qw( FS::part_export::http );
 use vars qw( %options %info );
 
-use FS::cust_main::Location;
-
-my @location_fields = ( qw( custnum prospectnum ), FS::cust_main::Location->location_fields );
+my @location_fields = qw(
+  custnum
+  prospectnum
+  locationname
+  address1
+  address2
+  city
+  county
+  state
+  zip
+  country
+  latitude
+  longitude
+  censustract
+  censusyear
+  district
+  geocode
+  location_type
+  location_number
+  location_kind
+  incorporated
+);
 
 tie %options, 'Tie::IxHash',
   'method' => { label   =>'Method',
@@ -23,6 +42,10 @@ tie %options, 'Tie::IxHash',
                         'type'  => 'select',
                         'multiple' => 1,
                         'options' => [ @location_fields ] },
+  'location_data'  => { 'label'   => 'Location data',
+                        'type'    => 'textarea' },
+  'package_data'   => { 'label'   => 'Package data',
+                        'type'    => 'textarea' },
   'success_regexp' => {
     label  => 'Success Regexp',
     default => '',
@@ -37,10 +60,16 @@ tie %options, 'Tie::IxHash',
   'notes'   => <<'END',
 Send an HTTP or HTTPS GET or POST to the specified URLs on customer location
 creation/update (action 'location') and package location assignment/change (action 'package').
-Always sends locationnum, action and any fields specified in the 'Include fields' 
-export option.  Action 'package' also sends pkgnum and old_pkgnum (because location
-changes usually instigate a pkgnum change.)  Action 'location' only sends on replace 
-if one of the specified fields changed.  Leave a URL blank to skip that action.
+Leave a URL blank to skip that action.
+Always sends locationnum, action, and fields specified in the export options.
+Action 'package' also sends pkgnum and change_pkgnum (the previous pkgnum,
+because location changes usually instigate a pkgnum change.)
+Simple field values can be selected in 'Include fields', and more complex
+values can be specified in the data field options as perl code using vars
+$cust_location, $cust_main and (where relevant) $cust_pkg.
+Action 'location' only sends on update if a specified field changed.
+Note that scheduled future package changes are currently sent when the change is scheduled
+(this may not be the case in future versions of this export.)
 For HTTPS support, <a href="http://search.cpan.org/dist/Crypt-SSLeay">Crypt::SSLeay</a>
 or <a href="http://search.cpan.org/dist/IO-Socket-SSL">IO::Socket::SSL</a> is required.
 END
@@ -51,11 +80,11 @@ END
 # we don't send blank custnum/prospectnum because we do a lot of inserting/replacing 
 #   with blank values and then immediately overwriting, but that unfortunately
 #   makes it difficult to indicate if this is the first time we've sent the location
-#   to the customer--hence we don't distinguish creation from update in the cgi vars
+#   to the customer--hence we don't distinguish insert from update in the cgi vars
 
 # gets invoked by FS::part_export::http _export_insert
 sub _export_command {
-  my( $self, $action, $cust_location ) = ( shift, shift, shift );
+  my( $self, $action, $cust_location ) = @_;
 
   # redundant--cust_location exports don't get invoked by cust_location->delete,
   # or by any status trigger, but just to be clear, since http export has other actions...
@@ -63,32 +92,55 @@ sub _export_command {
 
   $self->_http_queue_standard(
     'action' => 'location',
-    map { $_ => $cust_location->get($_) } ('locationnum', $self->_include_fields)
+    (map { $_ => $cust_location->get($_) } ('locationnum', $self->_include_fields)),
+    $self->_eval_replace('location_data',$cust_location,$cust_location->cust_main),
   );
 
 }
 
 sub _export_replace {
-  my( $self, $new, $old ) = ( shift, shift, shift );
+  my( $self, $new, $old ) = @_;
 
   my $changed = 0;
+
+  # even if they don't want custnum/prospectnum exported,
+  # inserts that lack custnum/prospectnum don't trigger exports,
+  # so we might not have previously reported these
+  $changed = 1 if $new->custnum && !$old->custnum;
+  $changed = 1 if $new->prospectnum && !$old->prospectnum;
+
   foreach my $field ($self->_include_fields) {
+    last if $changed;
     next if $new->get($field) eq $old->get($field);
     next if ($field =~ /latitude|longitude/) and $new->get($field) == $old->get($field);
     $changed = 1;
-    last;
   }
+
+  my %old_eval;
+  unless ($changed) {
+    %old_eval = $self->_eval_replace('location_data', $old, $old->cust_main),
+  }
+
+  my %eval = $self->_eval_replace('location_data', $new, $new->cust_main);
+
+  foreach my $key (keys %eval) {
+    last if $changed;
+    next if $eval{$key} eq $old_eval{$key};
+    $changed = 1;
+  }
+
   return '' unless $changed;
 
   $self->_http_queue_standard(
     'action' => 'location',
-    map { $_ => $new->get($_) } ('locationnum', $self->_include_fields)
+    (map { $_ => $new->get($_) } ('locationnum', $self->_include_fields)),
+    %eval,
   );
 }
 
 # not to be confused with export_pkg_change, which is for svcs
 sub export_pkg_location {
-  my( $self, $cust_pkg ) = ( shift, shift, shift );
+  my ($self, $cust_pkg) = @_;
 
   return '' unless $cust_pkg->locationnum;
 
@@ -98,6 +150,7 @@ sub export_pkg_location {
     'action' => 'package',
     (map { $_ => $cust_pkg->get($_) } ('pkgnum', 'change_pkgnum', 'locationnum')),
     (map { $_ => $cust_location->get($_) } $self->_include_fields),
+    $self->_eval_replace('package_data',$cust_location,$cust_pkg->cust_main,$cust_pkg),
   );
 }
 
@@ -128,4 +181,16 @@ sub _include_fields {
   split( /\s+/, $self->option('include_fields') );
 }
 
+sub _eval_replace {
+  my ($self,$option,$cust_location,$cust_main,$cust_pkg) = @_;
+  return
+    map {
+      /^\s*(\S+)\s+(.*)$/ or /()()/;
+      my( $field, $value_expression ) = ( $1, $2 );
+      my $value = eval $value_expression;
+      die $@ if $@;
+      ( $field, $value );
+    } split(/\n/, $self->option($option) );
+}
+
 1;

commit 89e3f368f19ae733b2f82531d73bba2d7bf9e25a
Author: Jonathan Prykop <jonathan at freeside.biz>
Date:   Tue Apr 19 12:16:15 2016 -0500

    RT#29296: API stuff: Add new locations [API bug fixes]

diff --git a/FS/FS/API.pm b/FS/FS/API.pm
index 77ae03b..1c4cbd9 100644
--- a/FS/FS/API.pm
+++ b/FS/FS/API.pm
@@ -589,7 +589,7 @@ containing the new values.
 =cut
 
 sub change_package_location {
-  my $self = shift;
+  my $class = shift;
   my %opt  = @_;
   return _shared_secret_error() unless _check_shared_secret($opt{'secret'});
 
@@ -597,13 +597,10 @@ sub change_package_location {
     or return { 'error' => 'Unknown pkgnum' };
 
   my %changeopt;
-  $changeopt{'pkgnum'} = $pkgnum;
 
-  my $cust_location = FS::cust_location->new({
-    'custnum' => $cust_pkg->custnum,
-    %location_hash,
-  });
-  $changeopt{'cust_location'} = $cust_location;
+  foreach my $field ('locationnum',FS::cust_location::API::API_editable_fields()) {
+    $changeopt{$field} = $opt{$field} if $opt{$field};
+  }
 
   $cust_pkg->API_change(%changeopt);
 }
diff --git a/FS/FS/cust_pkg/API.pm b/FS/FS/cust_pkg/API.pm
index 6e03b8e..7ef3f72 100644
--- a/FS/FS/cust_pkg/API.pm
+++ b/FS/FS/cust_pkg/API.pm
@@ -19,7 +19,7 @@ sub API_change {
   my %opt = @_;
 
   return { 'error' => 'Cannot change canceled package' }
-    if $self->cancel;
+    if $self->get('cancel');
 
   my %changeopt;
 

commit 5c321cb0e6488b0b76fec03395ec815703867b22
Author: Jonathan Prykop <jonathan at freeside.biz>
Date:   Mon Apr 18 21:47:51 2016 -0500

    RT#29296: API stuff: Add new locations [change_package_location API call]

diff --git a/FS/FS/API.pm b/FS/FS/API.pm
index 32400f7..77ae03b 100644
--- a/FS/FS/API.pm
+++ b/FS/FS/API.pm
@@ -8,6 +8,7 @@ use FS::cust_location;
 use FS::cust_pay;
 use FS::cust_credit;
 use FS::cust_refund;
+use FS::cust_pkg;
 
 =head1 NAME
 
@@ -556,6 +557,57 @@ sub location_info {
   return \%return;
 }
 
+=item change_package_location
+
+Updates package location. Takes a list of keys and values 
+as paramters with the following keys: 
+
+pkgnum
+
+secret
+
+locationnum - pass this, or the following keys (don't pass both)
+
+address1
+
+address2
+
+city
+
+county
+
+state
+
+zip
+
+country
+
+On error, returns a hashref with an 'error' key.
+On success, returns a hashref with 'pkgnum' and 'locationnum' keys,
+containing the new values.
+
+=cut
+
+sub change_package_location {
+  my $self = shift;
+  my %opt  = @_;
+  return _shared_secret_error() unless _check_shared_secret($opt{'secret'});
+
+  my $cust_pkg = qsearchs('cust_pkg', { 'pkgnum' => $opt{'pkgnum'} })
+    or return { 'error' => 'Unknown pkgnum' };
+
+  my %changeopt;
+  $changeopt{'pkgnum'} = $pkgnum;
+
+  my $cust_location = FS::cust_location->new({
+    'custnum' => $cust_pkg->custnum,
+    %location_hash,
+  });
+  $changeopt{'cust_location'} = $cust_location;
+
+  $cust_pkg->API_change(%changeopt);
+}
+
 =item bill_now OPTION => VALUE, ...
 
 Bills a single customer now, in the same fashion as the "Bill now" link in the
diff --git a/FS/FS/cust_location.pm b/FS/FS/cust_location.pm
index 3588cee..f38e8ef 100644
--- a/FS/FS/cust_location.pm
+++ b/FS/FS/cust_location.pm
@@ -728,7 +728,7 @@ sub label_prefix {
   $prefix;
 }
 
-=item county_state_county
+=item county_state_country
 
 Returns a string consisting of just the county, state and country.
 
diff --git a/FS/FS/cust_location/API.pm b/FS/FS/cust_location/API.pm
new file mode 100644
index 0000000..7d9140f
--- /dev/null
+++ b/FS/FS/cust_location/API.pm
@@ -0,0 +1,34 @@
+package FS::cust_location::API;
+
+use strict;
+
+# this gets used by FS::cust_main::API and FS::cust_pkg::API,
+# so don't use FS::cust_main or FS::cust_pkg here
+
+# some of these could probably be included, but in general,
+# probably a bad idea to expose everything in 
+# cust_main::Location::location_fields by default
+#
+#locationname
+#district
+#latitude
+#longitude
+#censustract
+#censusyear
+#geocode
+#coord_auto
+#addr_clean
+
+sub API_editable_fields {
+  return qw(
+    address1
+    address2
+    city
+    county
+    state
+    zip
+    country
+  );
+}
+
+1;
diff --git a/FS/FS/cust_main/API.pm b/FS/FS/cust_main/API.pm
index 2d6da9e..f9ba140 100644
--- a/FS/FS/cust_main/API.pm
+++ b/FS/FS/cust_main/API.pm
@@ -4,6 +4,7 @@ use strict;
 use FS::Conf;
 use FS::part_tag;
 use FS::Record qw( qsearchs );
+use FS::cust_location::API;
 
 =item API_getinfo FIELD => VALUE, ...
 
@@ -22,9 +23,7 @@ use vars qw(
 );
 #  locale
 #  ss stateid stateid_state
- at location_editable_fields = qw(
-  address1 address2 city county state zip country
-);
+ at location_editable_fields = FS::cust_location::API::API_editable_fields();
 
 sub API_getinfo {
   my( $self, %opt ) = @_;
diff --git a/FS/FS/cust_pkg/API.pm b/FS/FS/cust_pkg/API.pm
index f87eed3..6e03b8e 100644
--- a/FS/FS/cust_pkg/API.pm
+++ b/FS/FS/cust_pkg/API.pm
@@ -2,6 +2,8 @@ package FS::cust_pkg::API;
 
 use strict;
 
+use FS::cust_location::API;
+
 sub API_getinfo {
   my $self = shift;
 
@@ -10,4 +12,50 @@ sub API_getinfo {
 
 }
 
+# currently only handles location change...
+# eventually have it handle all sorts of package changes
+sub API_change {
+  my $self = shift;
+  my %opt = @_;
+
+  return { 'error' => 'Cannot change canceled package' }
+    if $self->cancel;
+
+  my %changeopt;
+
+  # update location--accepts raw fields OR location
+  my %location_hash;
+  foreach my $field (FS::cust_location::API::API_editable_fields()) {
+    $location_hash{$field} = $opt{$field} if $opt{$field};
+  }
+  return { 'error' => 'Cannot pass both locationnum and location fields' }
+    if $opt{'locationnum'} && %location_hash;
+
+  if (%location_hash) {
+    my $cust_location = FS::cust_location->new({
+      'custnum' => $self->custnum,
+      %location_hash,
+    });
+    $changeopt{'cust_location'} = $cust_location;
+  } elsif ($opt{'locationnum'}) {
+    $changeopt{'locationnum'} = $opt{'locationnum'};
+  }
+
+  # not quite "nothing changed" because passed changes might be identical to current record,
+  #   we don't currently check for that, don't want to imply that we do...but maybe we should?
+  return { 'error' => 'No changes passed to method' }
+    unless $changeopt{'cust_location'} || $changeopt{'locationnum'};
+
+  $changeopt{'keep_dates'} = 1;
+
+  my $pkg_or_error = $self->change( \%changeopt );
+  my $error = ref($pkg_or_error) ? '' : $pkg_or_error;
+
+  return { 'error' => $error } if $error;
+
+  # return all fields?  we don't yet expose them through FS::API
+  return { map { $_ => $pkg_or_error->get($_) } qw( pkgnum locationnum ) };
+
+}
+
 1;

commit fd0483007b898b9944cc31fd5d6e90932fe2c8c0
Author: Jonathan Prykop <jonathan at freeside.biz>
Date:   Mon Apr 18 14:33:02 2016 -0500

    RT#29296: API stuff: Add new locations [package location export]

diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm
index 3796624..31133d2 100644
--- a/FS/FS/Conf.pm
+++ b/FS/FS/Conf.pm
@@ -5306,7 +5306,7 @@ and customer address. Include units.',
       my @part_export =
         map { qsearch( 'part_export', {exporttype => $_ } ) }
           keys %{FS::part_export::export_info('cust_main')};
-      map { $_->exportnum => $_->exporttype.' to '.$_->machine } @part_export;
+      map { $_->exportnum => $_->exportname } @part_export;
     },
     'option_sub'  => sub {
       require FS::Record;
@@ -5315,7 +5315,7 @@ and customer address. Include units.',
         'part_export', { 'exportnum' => shift }
       );
       $part_export
-        ? $part_export->exporttype.' to '.$part_export->machine
+        ? $part_export->exportname
         : '';
     },
   },
@@ -5324,7 +5324,7 @@ and customer address. Include units.',
   {
     'key'         => 'cust_location-exports',
     'section'     => '',
-    'description' => 'Export(s) to call on cust_location insert, modification and deletion.',
+    'description' => 'Export(s) to call on cust_location insert or modification',
     'type'        => 'select-sub',
     'multiple'    => 1,
     'options_sub' => sub {
diff --git a/FS/FS/cust_location.pm b/FS/FS/cust_location.pm
index 2b8a5c8..3588cee 100644
--- a/FS/FS/cust_location.pm
+++ b/FS/FS/cust_location.pm
@@ -249,20 +249,22 @@ sub insert {
   # cust_location exports
   #my $export_args = $options{'export_args'} || [];
 
-  my @part_export =
-    map qsearch( 'part_export', {exportnum=>$_} ),
-      $conf->config('cust_location-exports'); #, $agentnum
-
-  foreach my $part_export ( @part_export ) {
-    my $error = $part_export->export_insert($self); #, @$export_args);
-    if ( $error ) {
-      $dbh->rollback if $oldAutoCommit;
-      return "exporting to ". $part_export->exporttype.
-             " (transaction rolled back): $error";
+  # don't export custnum_pending cases, let follow-up replace handle that
+  if ($self->custnum || $self->prospectnum) {
+    my @part_export =
+      map qsearch( 'part_export', {exportnum=>$_} ),
+        $conf->config('cust_location-exports'); #, $agentnum
+
+    foreach my $part_export ( @part_export ) {
+      my $error = $part_export->export_insert($self); #, @$export_args);
+      if ( $error ) {
+        $dbh->rollback if $oldAutoCommit;
+        return "exporting to ". $part_export->exporttype.
+               " (transaction rolled back): $error";
+      }
     }
   }
 
-
   $dbh->commit or die $dbh->errstr if $oldAutoCommit;
   '';
 }
@@ -311,20 +313,22 @@ sub replace {
   # cust_location exports
   #my $export_args = $options{'export_args'} || [];
 
-  my @part_export =
-    map qsearch( 'part_export', {exportnum=>$_} ),
-      $conf->config('cust_location-exports'); #, $agentnum
-
-  foreach my $part_export ( @part_export ) {
-    my $error = $part_export->export_replace($self, $old); #, @$export_args);
-    if ( $error ) {
-      $dbh->rollback if $oldAutoCommit;
-      return "exporting to ". $part_export->exporttype.
-             " (transaction rolled back): $error";
+  # don't export custnum_pending cases, let follow-up replace handle that
+  if ($self->custnum || $self->prospectnum) {
+    my @part_export =
+      map qsearch( 'part_export', {exportnum=>$_} ),
+        $conf->config('cust_location-exports'); #, $agentnum
+
+    foreach my $part_export ( @part_export ) {
+      my $error = $part_export->export_replace($self, $old); #, @$export_args);
+      if ( $error ) {
+        $dbh->rollback if $oldAutoCommit;
+        return "exporting to ". $part_export->exporttype.
+               " (transaction rolled back): $error";
+      }
     }
   }
 
-
   $dbh->commit or die $dbh->errstr if $oldAutoCommit;
   '';
 }
diff --git a/FS/FS/cust_pkg.pm b/FS/FS/cust_pkg.pm
index 31f2e82..ed9e1c8 100644
--- a/FS/FS/cust_pkg.pm
+++ b/FS/FS/cust_pkg.pm
@@ -442,6 +442,21 @@ sub insert {
 
   my $conf = new FS::Conf;
 
+  if ($self->locationnum) {
+    my @part_export =
+      map qsearch( 'part_export', {exportnum=>$_} ),
+        $conf->config('cust_location-exports'); #, $agentnum
+
+    foreach my $part_export ( @part_export ) {
+      my $error = $part_export->export_pkg_location($self); #, @$export_args);
+      if ( $error ) {
+        $dbh->rollback if $oldAutoCommit;
+        return "exporting to ". $part_export->exporttype.
+               " (transaction rolled back): $error";
+      }
+    }
+  }
+
   if ( ! $import && $conf->config('ticket_system') && $options{ticket_subject} ) {
 
     #this init stuff is still inefficient, but at least its limited to 
@@ -696,6 +711,24 @@ sub replace {
     }
   }
 
+  # also run exports if removing locationnum?
+  #   doesn't seem to happen, and we don't export blank locationnum on insert...
+  if ($new->locationnum and ($new->locationnum != $old->locationnum)) {
+    my $conf = new FS::Conf;
+    my @part_export =
+      map qsearch( 'part_export', {exportnum=>$_} ),
+        $conf->config('cust_location-exports'); #, $agentnum
+
+    foreach my $part_export ( @part_export ) {
+      my $error = $part_export->export_pkg_location($new); #, @$export_args);
+      if ( $error ) {
+        $dbh->rollback if $oldAutoCommit;
+        return "exporting to ". $part_export->exporttype.
+               " (transaction rolled back): $error";
+      }
+    }
+  }
+
   $dbh->commit or die $dbh->errstr if $oldAutoCommit;
   '';
 
diff --git a/FS/FS/part_export/cust_location_http.pm b/FS/FS/part_export/cust_location_http.pm
index 460080c..e2830db 100644
--- a/FS/FS/part_export/cust_location_http.pm
+++ b/FS/FS/part_export/cust_location_http.pm
@@ -4,27 +4,9 @@ use strict;
 use base qw( FS::part_export::http );
 use vars qw( %options %info );
 
-my @location_fields = qw(
-  custnum
-  address1
-  address2
-  city
-  state
-  zip
-  country
-  locationname
-  county
-  latitude
-  longitude
-  prospectnum
-  location_type
-  location_number
-  location_kind
-  geocode
-  district
-  censusyear
-  incorporated
-);
+use FS::cust_main::Location;
+
+my @location_fields = ( qw( custnum prospectnum ), FS::cust_main::Location->location_fields );
 
 tie %options, 'Tie::IxHash',
   'method' => { label   =>'Method',
@@ -32,10 +14,11 @@ tie %options, 'Tie::IxHash',
                 #options =>[qw(POST GET)],
                 options =>[qw(POST)],
                 default =>'POST' },
-  'url'    => { label   => 'URL', default => 'http://', },
-  'ssl_no_verify' => { label => 'Skip SSL certificate validation',
-                       type  => 'checkbox',
-                     },
+  'location_url'   => { label   => 'Location URL' },
+  'package_url'    => { label   => 'Package URL' },
+  'ssl_no_verify'  => { label => 'Skip SSL certificate validation',
+                        type  => 'checkbox',
+                      },
   'include_fields' => { 'label' => 'Include fields',
                         'type'  => 'select',
                         'multiple' => 1,
@@ -52,45 +35,39 @@ tie %options, 'Tie::IxHash',
   'options' => \%options,
   'no_machine' => 1,
   'notes'   => <<'END',
-Send an HTTP or HTTPS GET or POST to the specified URL on customer location insert
-or replace.  Always sends cgi fields action ('insert' or 'replace') and locationnum,
-as well as any fields specified below.  Only sends on replace if one of the
-specified fields changed.
+Send an HTTP or HTTPS GET or POST to the specified URLs on customer location
+creation/update (action 'location') and package location assignment/change (action 'package').
+Always sends locationnum, action and any fields specified in the 'Include fields' 
+export option.  Action 'package' also sends pkgnum and old_pkgnum (because location
+changes usually instigate a pkgnum change.)  Action 'location' only sends on replace 
+if one of the specified fields changed.  Leave a URL blank to skip that action.
 For HTTPS support, <a href="http://search.cpan.org/dist/Crypt-SSLeay">Crypt::SSLeay</a>
 or <a href="http://search.cpan.org/dist/IO-Socket-SSL">IO::Socket::SSL</a> is required.
 END
 );
 
-sub http_queue_standard {
-  my $self = shift;
-  $self->http_queue( '',
-    ( $self->option('ssl_no_verify') ? 'ssl_no_verify' : '' ),
-    $self->option('method'),
-    $self->option('url'),
-    $self->option('success_regexp'),
-    @_
-  );
-}
-
-sub _include_fields {
-  my $self = shift;
-  split( /\s+/, $self->option('include_fields') );
-}
+# we don't do anything on deletion because we generally don't delete locations
+#
+# we don't send blank custnum/prospectnum because we do a lot of inserting/replacing 
+#   with blank values and then immediately overwriting, but that unfortunately
+#   makes it difficult to indicate if this is the first time we've sent the location
+#   to the customer--hence we don't distinguish creation from update in the cgi vars
 
+# gets invoked by FS::part_export::http _export_insert
 sub _export_command {
   my( $self, $action, $cust_location ) = ( shift, shift, shift );
 
+  # redundant--cust_location exports don't get invoked by cust_location->delete,
+  # or by any status trigger, but just to be clear, since http export has other actions...
   return '' unless $action eq 'insert';
 
-  $self->http_queue_standard(
-    'action' => $action,
+  $self->_http_queue_standard(
+    'action' => 'location',
     map { $_ => $cust_location->get($_) } ('locationnum', $self->_include_fields)
   );
 
 }
 
-# currently, only custnum can change (when converting prospect to customer)
-# but using more generic logic for ease of adding other changeable fields
 sub _export_replace {
   my( $self, $new, $old ) = ( shift, shift, shift );
 
@@ -99,13 +76,56 @@ sub _export_replace {
     next if $new->get($field) eq $old->get($field);
     next if ($field =~ /latitude|longitude/) and $new->get($field) == $old->get($field);
     $changed = 1;
+    last;
   }
   return '' unless $changed;
 
-  $self->http_queue_standard(
-    'action' => 'replace',
+  $self->_http_queue_standard(
+    'action' => 'location',
     map { $_ => $new->get($_) } ('locationnum', $self->_include_fields)
   );
 }
 
+# not to be confused with export_pkg_change, which is for svcs
+sub export_pkg_location {
+  my( $self, $cust_pkg ) = ( shift, shift, shift );
+
+  return '' unless $cust_pkg->locationnum;
+
+  my $cust_location = $cust_pkg->cust_location;
+
+  $self->_http_queue_standard(
+    'action' => 'package',
+    (map { $_ => $cust_pkg->get($_) } ('pkgnum', 'change_pkgnum', 'locationnum')),
+    (map { $_ => $cust_location->get($_) } $self->_include_fields),
+  );
+}
+
+sub _http_queue_standard {
+  my $self = shift;
+  my %opts = @_;
+  my $url;
+  if ($opts{'action'} eq 'location') {
+    $url = $self->option('location_url');
+    return '' unless $url;
+  } elsif ($opts{'action'} eq 'package') {
+    $url = $self->option('package_url');
+    return '' unless $url;
+  } else {
+    return "Bad action ".$opts{'action'};
+  }
+  $self->http_queue( '',
+    ( $self->option('ssl_no_verify') ? 'ssl_no_verify' : '' ),
+    $self->option('method'),
+    $url,
+    $self->option('success_regexp'),
+    %opts
+  );
+}
+
+sub _include_fields {
+  my $self = shift;
+  split( /\s+/, $self->option('include_fields') );
+}
+
 1;

commit 401aff3ec100c05c64f9eb0d003494758a3e74ee
Author: Jonathan Prykop <jonathan at freeside.biz>
Date:   Fri Apr 15 11:43:59 2016 -0500

    RT#29296: API stuff: Add new locations

diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm
index 2a5bffd..3796624 100644
--- a/FS/FS/Conf.pm
+++ b/FS/FS/Conf.pm
@@ -5333,7 +5333,7 @@ and customer address. Include units.',
       my @part_export =
         map { qsearch( 'part_export', {exporttype => $_ } ) }
           keys %{FS::part_export::export_info('cust_location')};
-      map { $_->exportnum => $_->exporttype.' to '.$_->machine } @part_export;
+      map { $_->exportnum => $_->exportname } @part_export;
     },
     'option_sub'  => sub {
       require FS::Record;
@@ -5342,7 +5342,7 @@ and customer address. Include units.',
         'part_export', { 'exportnum' => shift }
       );
       $part_export
-        ? $part_export->exporttype.' to '.$part_export->machine
+        ? $part_export->exportname
         : '';
     },
   },
diff --git a/FS/FS/part_export/cust_location_http.pm b/FS/FS/part_export/cust_location_http.pm
new file mode 100644
index 0000000..460080c
--- /dev/null
+++ b/FS/FS/part_export/cust_location_http.pm
@@ -0,0 +1,111 @@
+package FS::part_export::cust_location_http;
+
+use strict;
+use base qw( FS::part_export::http );
+use vars qw( %options %info );
+
+my @location_fields = qw(
+  custnum
+  address1
+  address2
+  city
+  state
+  zip
+  country
+  locationname
+  county
+  latitude
+  longitude
+  prospectnum
+  location_type
+  location_number
+  location_kind
+  geocode
+  district
+  censusyear
+  incorporated
+);
+
+tie %options, 'Tie::IxHash',
+  'method' => { label   =>'Method',
+                type    =>'select',
+                #options =>[qw(POST GET)],
+                options =>[qw(POST)],
+                default =>'POST' },
+  'url'    => { label   => 'URL', default => 'http://', },
+  'ssl_no_verify' => { label => 'Skip SSL certificate validation',
+                       type  => 'checkbox',
+                     },
+  'include_fields' => { 'label' => 'Include fields',
+                        'type'  => 'select',
+                        'multiple' => 1,
+                        'options' => [ @location_fields ] },
+  'success_regexp' => {
+    label  => 'Success Regexp',
+    default => '',
+  },
+;
+
+%info = (
+  'svc'     => [qw( cust_location )],
+  'desc'    => 'Send an HTTP or HTTPS GET or POST request, for customer locations',
+  'options' => \%options,
+  'no_machine' => 1,
+  'notes'   => <<'END',
+Send an HTTP or HTTPS GET or POST to the specified URL on customer location insert
+or replace.  Always sends cgi fields action ('insert' or 'replace') and locationnum,
+as well as any fields specified below.  Only sends on replace if one of the
+specified fields changed.
+For HTTPS support, <a href="http://search.cpan.org/dist/Crypt-SSLeay">Crypt::SSLeay</a>
+or <a href="http://search.cpan.org/dist/IO-Socket-SSL">IO::Socket::SSL</a> is required.
+END
+);
+
+sub http_queue_standard {
+  my $self = shift;
+  $self->http_queue( '',
+    ( $self->option('ssl_no_verify') ? 'ssl_no_verify' : '' ),
+    $self->option('method'),
+    $self->option('url'),
+    $self->option('success_regexp'),
+    @_
+  );
+}
+
+sub _include_fields {
+  my $self = shift;
+  split( /\s+/, $self->option('include_fields') );
+}
+
+sub _export_command {
+  my( $self, $action, $cust_location ) = ( shift, shift, shift );
+
+  return '' unless $action eq 'insert';
+
+  $self->http_queue_standard(
+    'action' => $action,
+    map { $_ => $cust_location->get($_) } ('locationnum', $self->_include_fields)
+  );
+
+}
+
+# currently, only custnum can change (when converting prospect to customer)
+# but using more generic logic for ease of adding other changeable fields
+sub _export_replace {
+  my( $self, $new, $old ) = ( shift, shift, shift );
+
+  my $changed = 0;
+  foreach my $field ($self->_include_fields) {
+    next if $new->get($field) eq $old->get($field);
+    next if ($field =~ /latitude|longitude/) and $new->get($field) == $old->get($field);
+    $changed = 1;
+  }
+  return '' unless $changed;
+
+  $self->http_queue_standard(
+    'action' => 'replace',
+    map { $_ => $new->get($_) } ('locationnum', $self->_include_fields)
+  );
+}
+
+1;

-----------------------------------------------------------------------

Summary of changes:
 FS/FS/API.pm                            |   82 +++++++++++++
 FS/FS/Conf.pm                           |   10 +-
 FS/FS/cust_location.pm                  |   50 ++++----
 FS/FS/cust_pkg.pm                       |   33 ++++++
 FS/FS/cust_pkg/API.pm                   |   62 ++++++++++
 FS/FS/part_export/cust_http.pm          |    2 +-
 FS/FS/part_export/cust_location_http.pm |  196 +++++++++++++++++++++++++++++++
 7 files changed, 406 insertions(+), 29 deletions(-)
 create mode 100644 FS/FS/part_export/cust_location_http.pm




More information about the freeside-commits mailing list