RT# 83450 - fixed rateplan export
[freeside.git] / FS / FS / part_export / cust_location_http.pm
1 package FS::part_export::cust_location_http;
2
3 use strict;
4 use base qw( FS::part_export::http );
5 use vars qw( %options %info );
6
7 my @location_fields = qw(
8   custnum
9   prospectnum
10   locationname
11   address1
12   address2
13   city
14   county
15   state
16   zip
17   country
18   latitude
19   longitude
20   censustract
21   censusyear
22   district
23   geocode
24   location_type
25   location_number
26   location_kind
27   incorporated
28 );
29
30 tie %options, 'Tie::IxHash',
31   'method' => { label   =>'Method',
32                 type    =>'select',
33                 #options =>[qw(POST GET)],
34                 options =>[qw(POST)],
35                 default =>'POST' },
36   'location_url'   => { label   => 'Location URL' },
37   'package_url'    => { label   => 'Package URL' },
38   'ssl_no_verify'  => { label => 'Skip SSL certificate validation',
39                         type  => 'checkbox',
40                       },
41   'include_fields' => { 'label' => 'Include fields',
42                         'type'  => 'select',
43                         'multiple' => 1,
44                         'options' => [ @location_fields ] },
45   'location_data'  => { 'label'   => 'Location data',
46                         'type'    => 'textarea' },
47   'package_data'   => { 'label'   => 'Package data',
48                         'type'    => 'textarea' },
49   'success_regexp' => {
50     label  => 'Success Regexp',
51     default => '',
52   },
53 ;
54
55 %info = (
56   'svc'     => [qw( cust_location )],
57   'desc'    => 'Send an HTTP or HTTPS GET or POST request, for customer locations',
58   'options' => \%options,
59   'no_machine' => 1,
60   'notes'   => <<'END',
61 Send an HTTP or HTTPS GET or POST to the specified URLs on customer location
62 creation/update (action 'location') and package location assignment/change (action 'package').
63 Leave a URL blank to skip that action.
64 Always sends locationnum, action, and fields specified in the export options.
65 Action 'package' also sends pkgnum and change_pkgnum (the previous pkgnum,
66 because location changes usually instigate a pkgnum change.)
67 Simple field values can be selected in 'Include fields', and more complex
68 values can be specified in the data field options as perl code using vars
69 $cust_location, $cust_main and (where relevant) $cust_pkg.
70 Action 'location' only sends on update if a specified field changed.
71 Note that scheduled future package changes are currently sent when the change is scheduled
72 (this may not be the case in future versions of this export.)
73 For HTTPS support, <a href="http://search.cpan.org/dist/Crypt-SSLeay">Crypt::SSLeay</a>
74 or <a href="http://search.cpan.org/dist/IO-Socket-SSL">IO::Socket::SSL</a> is required.
75 END
76 );
77
78 # we don't do anything on deletion because we generally don't delete locations
79 #
80 # we don't send blank custnum/prospectnum because we do a lot of inserting/replacing 
81 #   with blank values and then immediately overwriting, but that unfortunately
82 #   makes it difficult to indicate if this is the first time we've sent the location
83 #   to the customer--hence we don't distinguish insert from update in the cgi vars
84
85 # gets invoked by FS::part_export::http _export_insert
86 sub _export_command {
87   my( $self, $action, $cust_location ) = @_;
88
89   # redundant--cust_location exports don't get invoked by cust_location->delete,
90   # or by any status trigger, but just to be clear, since http export has other actions...
91   return '' unless $action eq 'insert';
92
93   $self->_http_queue_standard(
94     'action' => 'location',
95     (map { $_ => $cust_location->get($_) } ('locationnum', $self->_include_fields)),
96     $self->_eval_replace('location_data',$cust_location,$cust_location->cust_main),
97   );
98
99 }
100
101 sub _export_replace {
102   my( $self, $new, $old ) = @_;
103
104   my $changed = 0;
105
106   # even if they don't want custnum/prospectnum exported,
107   # inserts that lack custnum/prospectnum don't trigger exports,
108   # so we might not have previously reported these
109   $changed = 1 if $new->custnum && !$old->custnum;
110   $changed = 1 if $new->prospectnum && !$old->prospectnum;
111
112   foreach my $field ($self->_include_fields) {
113     last if $changed;
114     next if $new->get($field) eq $old->get($field);
115     next if ($field =~ /latitude|longitude/) and $new->get($field) == $old->get($field);
116     $changed = 1;
117   }
118
119   my %old_eval;
120   unless ($changed) {
121     %old_eval = $self->_eval_replace('location_data', $old, $old->cust_main),
122   }
123
124   my %eval = $self->_eval_replace('location_data', $new, $new->cust_main);
125
126   foreach my $key (keys %eval) {
127     last if $changed;
128     next if $eval{$key} eq $old_eval{$key};
129     $changed = 1;
130   }
131
132   return '' unless $changed;
133
134   $self->_http_queue_standard(
135     'action' => 'location',
136     (map { $_ => $new->get($_) } ('locationnum', $self->_include_fields)),
137     %eval,
138   );
139 }
140
141 # not to be confused with export_pkg_change, which is for svcs
142 sub export_pkg_location {
143   my ($self, $cust_pkg) = @_;
144
145   return '' unless $cust_pkg->locationnum;
146
147   my $cust_location = $cust_pkg->cust_location;
148
149   $self->_http_queue_standard(
150     'action' => 'package',
151     (map { $_ => $cust_pkg->get($_) } ('pkgnum', 'change_pkgnum', 'locationnum')),
152     (map { $_ => $cust_location->get($_) } $self->_include_fields),
153     $self->_eval_replace('package_data',$cust_location,$cust_pkg->cust_main,$cust_pkg),
154   );
155 }
156
157 sub _http_queue_standard {
158   my $self = shift;
159   my %opts = @_;
160   my $url;
161   if ($opts{'action'} eq 'location') {
162     $url = $self->option('location_url');
163     return '' unless $url;
164   } elsif ($opts{'action'} eq 'package') {
165     $url = $self->option('package_url');
166     return '' unless $url;
167   } else {
168     return "Bad action ".$opts{'action'};
169   }
170   $self->http_queue( '',
171     ( $self->option('ssl_no_verify') ? 'ssl_no_verify' : '' ),
172     $self->option('method'),
173     $url,
174     $self->option('success_regexp'),
175     %opts
176   );
177 }
178
179 sub _include_fields {
180   my $self = shift;
181   split( /\s+/, $self->option('include_fields') );
182 }
183
184 sub _eval_replace {
185   my ($self,$option,$cust_location,$cust_main,$cust_pkg) = @_;
186   return
187     map {
188       /^\s*(\S+)\s+(.*)$/ or /()()/;
189       my( $field, $value_expression ) = ( $1, $2 );
190       my $value = eval $value_expression;
191       die $@ if $@;
192       ( $field, $value );
193     } split(/\n/, $self->option($option) );
194 }
195
196 1;