In contrast to the self-service API, which authenticates an end-user and offers
functionality to that end user, the backend API performs a simple shared-secret
authentication and offers full, administrator functionality, enabling
-integration with other back-office systems. Only ccess this API from a secure
+integration with other back-office systems. Only access this API from a secure
network from other backoffice machines. DON'T use this API to create customer
portal functionality.
my $payby = delete $validate->{'payby'};
- my $error = $cust_main->realtime_bop( $FS::payby::payby2bop{$payby}, $amount,
- 'quiet' => 1,
- 'manual' => 1,
- 'selfservice' => 1,
- 'paynum_ref' => \$paynum,
- %$validate,
- );
- return { 'error' => $error } if $error;
-
- #no error, so order the fee package if applicable...
- my $conf = new FS::Conf;
- my $fee_pkgpart = $conf->config('selfservice_process-pkgpart', $cust_main->agentnum);
- my $fee_skip_first = $conf->exists('selfservice_process-skip_first');
-
- if ( $fee_pkgpart and ! $fee_skip_first || scalar($cust_main->cust_pay) ) {
-
- my $cust_pkg = new FS::cust_pkg { 'pkgpart' => $fee_pkgpart };
-
- $error = $cust_main->order_pkg( 'cust_pkg' => $cust_pkg );
- return { 'error' => "payment processed successfully, but error ordering fee: $error" }
- if $error;
-
- #and generate an invoice for it now too
- $error = $cust_main->bill( 'pkg_list' => [ $cust_pkg ] );
- return { 'error' => "payment processed and fee ordered sucessfully, but error billing fee: $error" }
- if $error;
-
- }
-
- $cust_main->apply_payments;
-
if ( $validate->{'save'} ) {
my $new = new FS::cust_main { $cust_main->hash };
if ($payby eq 'CARD' || $payby eq 'DCRD') {
stateid stateid_state );
$new->set( 'payby' => $validate->{'auto'} ? 'CHEK' : 'DCHK' );
}
- $new->set( 'payinfo' => $cust_main->card_token || $validate->{'payinfo'} );
+ $new->payinfo( $validate->{'payinfo'} ); #to properly set paymask
$new->set( 'paydate' => $validate->{'paydate'} );
my $error = $new->replace($cust_main);
if ( $error ) {
#return { 'error' => $error };
#XXX just warn verosely for now so i can figure out how these happen in
# the first place, eventually should redirect them to the "change
- #address" page but indicate the payment did process??
+ #address" page but indicate if the payment processed?
delete($validate->{'payinfo'}); #don't want to log this!
warn "WARNING: error changing customer info when processing payment (not returning to customer as a processing error): $error\n".
"NEW: ". Dumper($new)."\n".
"OLD: ". Dumper($cust_main)."\n".
"PACKET: ". Dumper($validate)."\n";
- #} else {
- #not needed...
- #$cust_main = $new;
+ } else {
+ $cust_main = $new;
}
}
+ my $error = $cust_main->realtime_bop( $FS::payby::payby2bop{$payby}, $amount,
+ 'quiet' => 1,
+ 'manual' => 1,
+ 'selfservice' => 1,
+ 'paynum_ref' => \$paynum,
+ %$validate,
+ );
+ return { 'error' => $error } if $error;
+
+ #no error, so order the fee package if applicable...
+ my $conf = new FS::Conf;
+ my $fee_pkgpart = $conf->config('selfservice_process-pkgpart', $cust_main->agentnum);
+ my $fee_skip_first = $conf->exists('selfservice_process-skip_first');
+
+ if ( $fee_pkgpart and ! $fee_skip_first || scalar($cust_main->cust_pay) ) {
+
+ my $cust_pkg = new FS::cust_pkg { 'pkgpart' => $fee_pkgpart };
+
+ $error = $cust_main->order_pkg( 'cust_pkg' => $cust_pkg );
+ return { 'error' => "payment processed successfully, but error ordering fee: $error" }
+ if $error;
+
+ #and generate an invoice for it now too
+ $error = $cust_main->bill( 'pkg_list' => [ $cust_pkg ] );
+ return { 'error' => "payment processed and fee ordered sucessfully, but error billing fee: $error" }
+ if $error;
+
+ }
+
+ $cust_main->apply_payments;
+
my $cust_pay = '';
my $receipt_html = '';
if ($paynum) {
'type' => 'select',
'per_agent' => 1,
'select_enum' => [
- '', 'Payable upon receipt', 'Net 0', 'Net 3', 'Net 5', 'Net 9', 'Net 10', 'Net 14',
+ '', 'Payable upon receipt', 'Net 0', 'Net 3', 'Net 5', 'Net 7', 'Net 9', 'Net 10', 'Net 14',
'Net 15', 'Net 18', 'Net 20', 'Net 21', 'Net 25', 'Net 30', 'Net 45',
'Net 60', 'Net 90'
], },
{
'key' => 'disable_previous_balance',
'section' => 'invoicing',
- 'description' => 'Disable inclusion of previous balance, payment, and credit lines on invoices.',
+ 'description' => 'Show new charges only; do not list previous invoices, payments, or credits on the invoice.',
'type' => 'checkbox',
'per_agent' => 1,
},
'zonenum', 'serial', '', '', '', '',
'description', 'char', 'NULL', $char_d, '', '',
'agentnum', 'int', '', '', '', '',
+ 'censusyear', 'char', 'NULL', 4, '', '',
'dbaname', 'char', 'NULL', $char_d, '', '',
'zonetype', 'char', '', 1, '', '',
'technology', 'int', '', '', '', '',
'blocknum', 'serial', '', '', '', '',
'zonenum', 'int', '', '', '', '',
'censusblock', 'char', '', 15, '', '',
- 'censusyear', 'char', '', 4, '', '',
+ 'censusyear', 'char','NULL', 4, '', '',
],
'primary_key' => 'blocknum',
'unique' => [],
my( $pr_total, @pr_cust_bill ) = $self->previous; #previous balance
# my( $cr_total, @cr_cust_credit ) = $self->cust_credit; #credits
#my $balance_due = $self->owed + $pr_total - $cr_total;
- my $balance_due = $self->owed + $pr_total;
+ my $balance_due = $self->owed;
+ if ( $self->enable_previous ) {
+ $balance_due += $pr_total;
+ }
+ # otherwise the previous balance is not shown, so including it in the
+ # balance due is just confusing
# the sum of amount owed on all invoices
# (this is used in the summary & on the payment coupon)
my ($previous_charges_desc, $new_charges_desc, $new_charges_amount);
if ( $conf->exists('previous_balance-exclude_from_total') ) {
- # can we do some caching on this stuff? it's going to change infrequently
- # in production
+ # if enabled, specifically add a line for the previous balance total
$previous_charges_desc = $self->mt(
$conf->config('previous_balance-text') || 'Previous Balance'
);
total_amount => sprintf('%.2f',$pr_total)
};
}
+ }
+
+ if ( $conf->exists('previous_balance-exclude_from_total')
+ or !$self->enable_previous ) {
+ # show new charges only
+
$new_charges_desc = $self->mt(
$conf->config('previous_balance-text-total_new_charges')
|| 'Total New Charges'
$new_charges_amount = $self->charged;
} else {
+ # show new charges + previous invoice total
$new_charges_desc = $self->mt('Total Charges');
- $new_charges_amount = sprintf('%.2f',$self->charged + $pr_total);
+ if ( $self->enable_previous ) {
+ $new_charges_amount = sprintf('%.2f', $self->charged + $pr_total);
+ } else {
+ $new_charges_amount = sprintf('%.2f', $self->charged);
+ }
}
return "$@ running calc_setup for $cust_pkg\n"
if $@;
- $unitsetup = $cust_pkg->base_setup()
- || $setup; #XXX uuh
+ # Only increment unitsetup here if there IS a setup fee.
+ # prorate_defer_bill may cause calc_setup on a setup-stage package
+ # to return zero, and the setup fee to be charged later. (This happens
+ # when it's first billed on the prorate cutoff day. RT#31276.)
+ if ( $setup ) {
+ $unitsetup = $cust_pkg->base_setup()
+ || $setup; #XXX uuh
+ }
if ( $setup_param{'billed_currency'} ) {
$setup_billed_currency = delete $setup_param{'billed_currency'};
# Add an additional setup fee at the billing stage.
# Used for prorate_defer_bill.
$setup += $param{'setup_fee'};
- $unitsetup += $param{'setup_fee'};
+ $unitsetup = $cust_pkg->base_setup();
$lineitems++;
}
if ( $transaction->can('card_token') && $transaction->card_token ) {
- $self->card_token($transaction->card_token);
-
if ( $options{'payinfo'} eq $self->payinfo ) {
$self->payinfo($transaction->card_token);
my $error = $self->replace;
use Storable qw(thaw);
use MIME::Base64;
+use JSON qw(encode_json decode_json) ;
+use LWP::UserAgent;
+use HTTP::Request::Common;
+
+# update this in 2020, along with the URL for the TIGERweb service
+our $CENSUS_YEAR = 2010;
+
=head1 NAME
FS::deploy_zone - Object methods for deploy_zone records
The agent that serves this zone.
+=item censusyear
+
+The census map year for which this zone was last updated. May be null for
+zones that contain no census blocks (mobile zones, or fixed zones that haven't
+had their block lists filled in yet).
+
=item dbaname
The name under which service is marketed in this zone. If null, will
The way the zone geography is defined: "B" for a list of census blocks
(used by the FCC for fixed broadband service), "P" for a polygon (for
mobile services). See L<FS::deploy_zone_block> and L<FS::deploy_zone_vertex>.
+Note that block-type zones are still allowed to have a vertex list, for
+use by the map editor.
=item technology
local $FS::UID::AutoCommit = 0;
# clean up linked records
my $self = shift;
- my $error = $self->process_o2m(
- 'table' => $self->element_table,
- 'num_col' => 'zonenum',
- 'fields' => 'zonenum',
- 'params' => {},
- ) || $self->SUPER::delete(@_);
+ my $error;
+ foreach (qw(deploy_zone_block deploy_zone_vertex)) {
+ $error ||= $self->process_o2m(
+ 'table' => $_,
+ 'num_col' => 'zonenum',
+ 'fields' => 'zonenum',
+ 'params' => {},
+ );
+ }
+ $error ||= $self->SUPER::delete(@_);
if ($error) {
dbh->rollback if $oldAutoCommit;
$self->ut_numbern('zonenum')
|| $self->ut_text('description')
|| $self->ut_number('agentnum')
+ || $self->ut_numbern('censusyear')
|| $self->ut_foreign_key('agentnum', 'agent', 'agentnum')
|| $self->ut_textn('dbaname')
|| $self->ut_enum('zonetype', [ 'B', 'P' ])
$self->SUPER::check;
}
-=item element_table
-
-Returns the name of the table that contains the zone's elements (blocks or
-vertices).
-
-=cut
-
-sub element_table {
- my $self = shift;
- if ($self->zonetype eq 'B') {
- return 'deploy_zone_block';
- } elsif ( $self->zonetype eq 'P') {
- return 'deploy_zone_vertex';
- } else {
- die 'unknown zonetype';
- }
-}
-
=item deploy_zone_block
Returns the census block records in this zone, in order by census block
=item deploy_zone_vertex
-Returns the vertex records for this zone, in order by sequence number. Only
-appropriate to polygon-type zones.
+Returns the vertex records for this zone, in order by sequence number.
=cut
});
}
-=back
+=item vertices_json
+
+Returns the vertex list for this zone, as a JSON string of
+
+[ [ latitude0, longitude0 ], [ latitude1, longitude1 ] ... ]
+
+=cut
+
+sub vertices_json {
+ my $self = shift;
+ my @vertices = map { [ $_->latitude, $_->longitude ] } $self->deploy_zone_vertex;
+ encode_json(\@vertices);
+}
=head2 SUBROUTINES
FS::Record::process_batch_import( $job, $opt, $param );
}
-
+
+=item process_block_lookup JOB, ZONENUM
+
+Look up all the census blocks in the zone's footprint, and insert them.
+This will replace any existing block list.
+
+=cut
+
+sub process_block_lookup {
+ my $job = shift;
+ my $param = shift;
+ if (!ref($param)) {
+ $param = thaw(decode_base64($param));
+ }
+ my $zonenum = $param->{zonenum};
+ my $zone = FS::deploy_zone->by_key($zonenum)
+ or die "zone $zonenum not found\n";
+
+ # wipe the existing list of blocks
+ my $error = $zone->process_o2m(
+ 'table' => 'deploy_zone_block',
+ 'num_col' => 'zonenum',
+ 'fields' => 'zonenum',
+ 'params' => {},
+ );
+ die $error if $error;
+
+ $job->update_statustext('0,querying census database') if $job;
+
+ # negotiate the rugged jungle trails of the ArcGIS REST protocol:
+ # 1. unlike most places, longitude first.
+ my @zone_vertices = map { [ $_->longitude, $_->latitude ] }
+ $zone->deploy_zone_vertex;
+
+ return if scalar(@zone_vertices) < 3; # then don't bother
+
+ # 2. package this as "rings", inside a JSON geometry object
+ # 3. announce loudly and frequently that we are using spatial reference
+ # 4326, "true GPS coordinates"
+ my $geometry = encode_json({
+ 'rings' => [ \@zone_vertices ],
+ 'wkid' => 4326,
+ });
+
+ my %query = (
+ f => 'json', # duh
+ geometry => $geometry,
+ geometryType => 'esriGeometryPolygon', # as opposed to a bounding box
+ inSR => 4326,
+ outSR => 4326,
+ spatialRel => 'esriSpatialRelIntersects', # the test to perform
+ outFields => 'OID,GEOID',
+ returnGeometry => 'false',
+ orderByFields => 'OID',
+ );
+ my $url = 'http://tigerweb.geo.census.gov/arcgis/rest/services/TIGERweb/Tracts_Blocks/MapServer/12/query';
+ my $ua = LWP::UserAgent->new;
+
+ # first find out how many of these we're dealing with
+ my $response = $ua->request(
+ POST $url, Content => [
+ %query,
+ returnCountOnly => 1,
+ ]
+ );
+ die $response->status_line unless $response->is_success;
+ my $data = decode_json($response->content);
+ # their error messages are mostly useless, but don't just blindly continue
+ die $data->{error}{message} if $data->{error};
+
+ my $count = $data->{count};
+ my $inserted = 0;
+
+ #warn "Census block lookup: $count\n";
+
+ # we have to do our own pagination on this, because the census bureau
+ # doesn't support resultOffset (maybe they don't have ArcGIS 10.3 yet).
+ # that's why we're ordering by OID, it's globally unique
+ my $last_oid = 0;
+ my $done = 0;
+ while (!$done) {
+ $response = $ua->request(
+ POST $url, Content => [
+ %query,
+ where => "OID>$last_oid",
+ ]
+ );
+ die $response->status_line unless $response->is_success;
+ $data = decode_json($response->content);
+ die $data->{error}{message} if $data->{error};
+
+ foreach my $feature (@{ $data->{features} }) {
+ my $geoid = $feature->{attributes}{GEOID}; # the prize
+ my $block = FS::deploy_zone_block->new({
+ zonenum => $zonenum,
+ censusblock => $geoid
+ });
+ $error = $block->insert;
+ die "$error (inserting census block $geoid)" if $error;
+
+ $inserted++;
+ if ($job and $inserted % 100 == 0) {
+ my $percent = sprintf('%.0f', $inserted / $count * 100);
+ $job->update_statustext("$percent,creating block records");
+ }
+ }
+
+ #warn "Inserted $inserted records\n";
+ $last_oid = $data->{features}[-1]{attributes}{OID};
+ $done = 1 unless $data->{exceededTransferLimit};
+ }
+
+ $zone->set('censusyear', $CENSUS_YEAR);
+ $error = $zone->replace;
+ warn "$error (updating zone census year)" if $error; # whatever, continue
+
+ return;
+}
+
=head1 BUGS
=head1 SEE ALSO
U.S. census block number (15 digits).
-=item censusyear
-
-The year of the census map where the block appeared or was last verified.
-
=back
=head1 METHODS
$self->ut_numbern('blocknum')
|| $self->ut_number('zonenum')
|| $self->ut_number('censusblock')
- || $self->ut_number('censusyear')
;
return $error if $error;
table (required) - Table into which the records are inserted.
-num_col (optional) - Column in table which links to the primary key of the base table. If not specified, it is assumed this has the same name.
-
-params (required) - Hashref of keys and values, often passed as C<scalar($cgi->Vars)> from a form.
-
-fields (required) - Arrayref of field names for each record in table. Pulled from params as "pkeyNN_field" where pkey is table's primary key and NN is the entry's numeric identifier.
+fields (required) - Arrayref of the field names in the "many" table.
+
+params (required) - Hashref of keys and values, often passed as
+C<scalar($cgi->Vars)> from a form. This will be scanned for keys of the form
+"pkeyNN" (where pkey is the primary key column name, and NN is an integer).
+Each of these designates one record in the "many" table. The contents of
+that record will be taken from other parameters with the names
+"pkeyNN_myfield" (where myfield is one of the fields in the 'fields'
+array).
+
+num_col (optional) - Name of the foreign key column in the "many" table, which
+links to the primary key of the base table. If not specified, it is assumed
+this has the same name as in the base table.
=cut
95 => 'Wireless Communications Service (WCS) Band',
96 => 'Broadband Radio Service/Educational Broadband Service Band',
97 => 'Satellite (e.g. L-band, Big LEO, Little LEO)',
- 98 => 'Unlicensed (including broadcast television “white spaces”) Bands',
+ 98 => 'Unlicensed (including broadcast television "white spaces") Bands',
99 => '600 MHz',
100 => 'H Block',
101 => 'Advanced Wireless Services (AWS) 3 Band',
#!/usr/bin/perl -w
use strict;
-use vars qw($opt_s $opt_u $opt_p);
+use vars qw($opt_s $opt_u $opt_p $opt_e);
use Getopt::Std;
use FS::UID qw(adminsuidsetup);
use FS::Record qw(qsearch qsearchs);
or die "no exports of type $export_x found\n";
}
-getopts('s:u:p:');
+getopts('s:u:p:e:');
my @svc_x = ();
if ( $opt_s ) {
die "no services with svcpart $opt_p found\n" unless @svc_x;
}
+$opt_e ||= 'insert';
+die &usage unless grep { $_ eq $opt_e } qw( insert replace delete suspend unsuspend );
+my $method = 'export_' . $opt_e;
+
foreach my $part_export ( @part_export ) {
foreach my $svc_x ( @svc_x ) {
- my $error = $part_export->export_insert($svc_x);
+ my $error = $part_export->$method($svc_x,$svc_x);
die $error if $error;
}
}
sub usage {
- die "Usage:\n\n freeside-reexport user exportnum|exporttype [ -s svcnum | -u username | -p svcpart ]\n";
+ return "Usage:\n\n freeside-reexport user exportnum|exporttype [ -s svcnum | -u username | -p svcpart ] [ -e insert|replace|delete|suspend|unsuspend ]\n";
}
=head1 NAME
=head1 SYNOPSIS
- freeside-reexport user exportnum|exporttype [ -s svcnum | -u username | -p svcpart ]
+ freeside-reexport user exportnum|exporttype [ -s svcnum | -u username | -p svcpart ] [ -e insert|replace|delete|suspend|unsuspend ]
=head1 DESCRIPTION
Re-queues the export job for the specified exportnum or exporttype(s) and
- specified service (selected by svcnum or username).
+ specified service (selected by svcnum, username or svcpart). Optionally
+ specify the phase of export using the -e flag (default is insert.)
=head1 SEE ALSO
'Market',
'Advertised Mbps',
'Contractual Mbps',
+ 'Vertices',
'Census blocks',
],
fields => [ 'zonenum',
)
},
sub { my $self = shift;
+ FS::deploy_zone_vertex->count('zonenum = '.$self->zonenum)
+ },
+ sub { my $self = shift;
FS::deploy_zone_block->count('zonenum = '.$self->zonenum)
},
],
'(cir_speed_down, cir_speed_up)',
],
links => [ $link_fixed, $link_fixed, ],
- align => 'clllllr',
+ align => 'cllllrr',
nohtmlheader => 1,
disable_maxselect => 1,
disable_total => 1,
value => 'Contractually guaranteed speed (Mbps)' },
'cir_speed_down',
'cir_speed_up',
-
- { type => 'tablebreak-tr-title', value => 'Census blocks'},
- { field => 'file',
- type => 'file-upload',
- },
- { field => 'format',
- type => 'hidden',
- value => 'plain',
- },
- { field => 'censusyear',
- type => 'select',
- options => [ '', qw( 2013 2012 2011 ) ],
- },
-
- { type => 'tablebreak-tr-title', value => '', },
- { field => 'blocknum',
- type => 'deploy_zone_block',
- o2m_table => 'deploy_zone_block',
- m2_label => ' ',
- m2_error_callback => $m2_error_callback,
- },
+ { type => 'tablebreak-tr-title', value => 'Footprint'},
+ { field => 'vertices',
+ type => 'polygon',
+ curr_value_callback => sub {
+ my ($cgi, $object) = @_;
+ $cgi->param('vertices') || $object->vertices_json;
+ },
+ }
+#
+# { type => 'tablebreak-tr-title', value => 'Census blocks'},
+# { field => 'file',
+# type => 'file-upload',
+# },
+# { field => 'format',
+# type => 'hidden',
+# value => 'plain',
+# },
+# { field => 'censusyear',
+# type => 'hidden',
+# options => [ '', qw( 2013 2012 2011 ) ],
+# },
+#
+# { type => 'tablebreak-tr-title', value => '', },
+# { field => 'blocknum',
+# type => 'deploy_zone_block',
+# o2m_table => 'deploy_zone_block',
+# m2_label => ' ',
+# m2_error_callback => $m2_error_callback,
+# },
],
-
&>
<%init>
my $curuser = $FS::CurrentUser::CurrentUser;
my $media_types = FS::part_pkg_fcc_option->media_types;
delete $media_types->{'Mobile Wireless'}; # cause this is the fixed zone page
-my $m2_error_callback = sub {
- my ($cgi, $deploy_zone) = @_;
- my @blocknums = grep {
- /^blocknum\d+/ and length($cgi->param($_.'_censusblock'))
- } $cgi->param;
-
- sort { $a->censusblock <=> $b->censusblock }
- map {
- my $k = $_;
- FS::deploy_zone_block->new({
- blocknum => scalar($cgi->param($k)),
- zonenum => $deploy_zone->zonenum,
- censusblock => scalar($cgi->param($k.'_censusblock')),
- censusyear => scalar($cgi->param($k.'_censusyear')),
- })
- } @blocknums;
-};
+#my $m2_error_callback = sub {
+# my ($cgi, $deploy_zone) = @_;
+# my @blocknums = grep {
+# /^blocknum\d+/ and length($cgi->param($_.'_censusblock'))
+# } $cgi->param;
+#
+# sort { $a->censusblock <=> $b->censusblock }
+# map {
+# my $k = $_;
+# FS::deploy_zone_block->new({
+# blocknum => scalar($cgi->param($k)),
+# zonenum => $deploy_zone->zonenum,
+# censusblock => scalar($cgi->param($k.'_censusblock')),
+# censusyear => scalar($cgi->param($k.'_censusyear')),
+# })
+# } @blocknums;
+#};
</%init>
'adv_speed_down',
'adv_speed_up',
{ type => 'tablebreak-tr-title', value => 'Footprint'},
- { field => 'vertexnum',
- type => 'deploy_zone_vertex',
- o2m_table => 'deploy_zone_vertex',
- m2_label => ' ',
- m2_error_callback => $m2_error_callback,
- },
- ],
+ { field => 'vertices',
+ type => 'polygon',
+ curr_value_callback => sub {
+ my ($cgi, $object) = @_;
+ $cgi->param('vertices') || $object->vertices_json;
+ },
+ }
+# { field => 'vertexnum',
+# type => 'deploy_zone_vertex',
+# o2m_table => 'deploy_zone_vertex',
+# m2_label => ' ',
+# m2_error_callback => $m2_error_callback,
+# },
+ ],
&>
<%init>
my $curuser = $FS::CurrentUser::CurrentUser;
error_redirect => popurl(2).'deploy_zone-fixed.html',
table => 'deploy_zone',
viewall_dir => 'browse',
- process_o2m => {
- 'table' => 'deploy_zone_block',
- 'fields' => [qw( censusblock censusyear )]
- },
- process_upload => {
- 'process' => 'misc/process/deploy_zone-import.html',
- 'fields' => [qw( censusyear format )],
- },
+ precheck_callback => $precheck_callback,
+ process_o2m =>
+ { 'table' => 'deploy_zone_vertex',
+ 'fields' => [qw( latitude longitude )]
+ },
+ progress_init => [
+ 'PostForm',
+ [ 'zonenum' ],
+ $fsurl.'misc/process/deploy_zone-block_lookup.cgi',
+ $fsurl.'browse/deploy_zone.html',
+ ],
&>
+<%init>
+my $precheck_callback = sub {
+ # convert the vertex list into a process_o2m-style parameter list
+ if ( $cgi->param('vertices') ) {
+ my $vertices = decode_json($cgi->param('vertices'));
+ my $i = 0;
+ foreach (@$vertices) {
+ $cgi->param("vertexnum${i}", '');
+ $cgi->param("vertexnum${i}_latitude", $_->[0]);
+ $cgi->param("vertexnum${i}_longitude", $_->[1]);
+ $i++;
+ }
+ }
+ '';
+};
+</%init>
error_redirect => popurl(2).'deploy_zone-mobile.html',
table => 'deploy_zone',
viewall_dir => 'browse',
- process_o2m =>
+ precheck_callback => $precheck_callback,
+ process_o2m =>
{ 'table' => 'deploy_zone_vertex',
'fields' => [qw( latitude longitude )]
},
&>
+<%init>
+my $precheck_callback = sub {
+ # convert the vertex list into a process_o2m-style parameter list
+ if ( $cgi->param('vertices') ) {
+ my $vertices = decode_json($cgi->param('vertices'));
+ my $i = 0;
+ foreach (@$vertices) {
+ $cgi->param("vertexnum${i}", '');
+ $cgi->param("vertexnum${i}_latitude", $_->[0]);
+ $cgi->param("vertexnum${i}_longitude", $_->[1]);
+ $i++;
+ }
+ }
+ '';
+};
+</%init>
</script>
<& /elements/footer.html &>
-%} elsif ( $opt{'popup_reload'} ) {
+% } elsif ( $opt{'progress_init'} ) {
+% # some false laziness with the above
+% my ($form_name, $job_fields) = @{ $opt{'progress_init'} };
+<form name="<% $form_name %>">
+% foreach my $field (@$job_fields) {
+ <input type="hidden" name="<% $field %>" value="<% $cgi->param($field) |h %>">
+% }
+<& /elements/progress-init.html,
+ @{ $opt{'progress_init'} }
+&>
+<input type="submit" style="display:none">
+</form>
+<script>
+<&| /elements/onload.js &>
+process();
+</&>
+</script>
+<& /elements/footer.html &>
+
+% } elsif ( $opt{'popup_reload'} ) {
<% include('/elements/header-popup.html', $opt{'popup_reload'} ) %>
--- /dev/null
+<%init>
+my %opt = @_;
+my $field = $opt{'field'};
+my $id = $opt{'id'} || $opt{'field'};
+my $div_id = "div_$id";
+
+my $vertices_json = $opt{'curr_value'} || '[]';
+</%init>
+<& hidden.html, %opt &>
+<div id="<% $div_id %>" style="height: 600px; width: 600px"></div>
+
+<script src="https://maps.googleapis.com/maps/api/js?libraries=drawing"></script>
+<script>
+var map;
+var drawingManager;
+
+function updateFormInput(event) {
+ var path = window.polygon.getPath();
+ var vertices = []; // array of arrays, geoJSON style
+ for (var i =0; i < path.getLength(); i++) {
+ var xy = path.getAt(i);
+ vertices[i] = [ xy.lat(), xy.lng() ];
+ }
+ console.log(vertices); //XXX
+ $('#<% $field %>').prop('value', JSON.stringify(vertices));
+}
+
+$(function() {
+ mapOptions = {
+ zoom: 4,
+ center: {lat: 39.40114, lng: -96.57127}, // continental U.S.
+ mapTypeId: google.maps.MapTypeId.ROADMAP,
+ panControl: true,
+ scaleControl: true,
+ streetViewControl: false,
+ };
+ map = new google.maps.Map($('#<% $div_id %>')[0], mapOptions);
+
+ var polygonComplete = function(p) {
+ window.polygon = p;
+ if (drawingManager) {
+ drawingManager.setDrawingMode(null);
+ drawingManager.setOptions({ drawingControl: false });
+ }
+ // double click to delete a vertex (so long as it remains a polygon)
+ p.addListener('dblclick', function (mev) {
+ if (mev.vertex != null && window.polygon.getPath().length > 3) {
+ p.getPath().removeAt(mev.vertex);
+ }
+ });
+ // any time the polygon is modified, update the vertex list
+ p.getPath().addListener('set_at', updateFormInput);
+ p.getPath().addListener('insert_at', updateFormInput);
+ p.getPath().addListener('remove_at', updateFormInput);
+
+ // and also now
+ updateFormInput();
+ };
+
+ var polygonOptions = {
+ fillColor: '#0000a0',
+ fillOpacity: 0.2,
+ strokeColor: '#0000a0',
+ strokeWeight: 2,
+ clickable: false,
+ editable: true,
+ zIndex: 1,
+ map: map,
+ };
+
+ var vertex_array = <% $vertices_json %>;
+ if ( vertex_array.length > 2 ) {
+ // then we already have a polygon. make it acceptable to google maps,
+ // and also create a bounding box for it and fit the map to that.
+
+ var path = [];
+ var bounds = new google.maps.LatLngBounds();
+ for (var i = 0; i < vertex_array.length; i++) {
+ var xy = new google.maps.LatLng(vertex_array[i][0], vertex_array[i][1]);
+ path.push(xy);
+ bounds.extend(xy);
+ }
+
+ polygonOptions.paths = [ path ];
+ polygonComplete(new google.maps.Polygon(polygonOptions));
+ map.fitBounds(bounds);
+
+ } else {
+ // there are no vertices, or not enough to make a polygon, so
+ // enable drawing mode to create a new one
+
+ drawingManager = new google.maps.drawing.DrawingManager({
+ drawingMode: google.maps.drawing.OverlayType.POLYGON,
+ drawingControl: true,
+ drawingControlOptions: {
+ position: google.maps.ControlPosition.TOP_CENTER,
+ drawingModes: [
+ google.maps.drawing.OverlayType.POLYGON,
+ ]
+ },
+ polygonOptions: polygonOptions,
+ });
+
+ // after a single polygon is drawn: remember it, add a listener to let
+ // nodes be deleted, and exit drawing mode
+ drawingManager.addListener('polygoncomplete', polygonComplete);
+ drawingManager.setMap(map);
+
+ // center the map on the user (for lack of a better choice)
+ if (navigator.geolocation) {
+ navigator.geolocation.getCurrentPosition(function(position) {
+ var pos = {
+ lat: position.coords.latitude,
+ lng: position.coords.longitude
+ };
+
+ map.setCenter(pos);
+ map.setZoom(12);
+ });
+ } // on error, or if geolocation isn't available, do nothing
+ }
+
+});
+
+ </script>
+ </body>
+</html>
my @terms = ( emt('Payable upon receipt'),
( map "Net $_",
- 0, 3, 5, 9, 10, 14, 15, 18, 20, 21, 25, 30, 45, 60, 90 ),
+ 0, 3, 5, 7, 9, 10, 14, 15, 18, 20, 21, 25, 30, 45, 60, 90 ),
);
my @pre_options = $opt{pre_options} ? @{ $opt{pre_options} } : ();
--- /dev/null
+<tr>
+<td colspan=2>
+<& polygon.html, @_ &>
+</td>
+</tr>
--- /dev/null
+<% $server->process %>
+<%init>
+my $curuser = $FS::CurrentUser::CurrentUser;
+die "access denied"
+ unless $curuser->access_right([
+ 'Edit FCC report configuration',
+ 'Edit FCC report configuration for all agents',
+ ]);
+
+my $server = FS::UI::Web::JSRPC->new(
+ 'FS::deploy_zone::process_block_lookup', $cgi
+);
+</%init>
or errorpage("illegal discount_term");
my $discount_term = $1;
+# save first, for proper tokenization later
+if ( $cgi->param('save') ) {
+ my $new = new FS::cust_main { $cust_main->hash };
+ if ( $payby eq 'CARD' ) {
+ $new->set( 'payby' => ( $cgi->param('auto') ? 'CARD' : 'DCRD' ) );
+ } elsif ( $payby eq 'CHEK' ) {
+ $new->set( 'payby' => ( $cgi->param('auto') ? 'CHEK' : 'DCHK' ) );
+ } else {
+ die "unknown payby $payby";
+ }
+ $new->payinfo($payinfo); #to properly set paymask
+ $new->set( 'paydate' => "$year-$month-01" );
+ $new->set( 'payname' => $payname );
+
+ #false laziness w/FS:;cust_main::realtime_bop - check both to make sure
+ # working correctly
+ if ( $payby eq 'CARD' &&
+ grep { $_ eq cardtype($payinfo) } $conf->config('cvv-save') ) {
+ $new->set( 'paycvv' => $paycvv );
+ } else {
+ $new->set( 'paycvv' => '');
+ }
+
+ if ( $payby eq 'CARD' ) {
+ my $bill_location = FS::cust_location->new;
+ $bill_location->set( $_ => $cgi->param($_) )
+ foreach @{$payby2fields{$payby}};
+ $new->set('bill_location' => $bill_location);
+ # will do nothing if the fields are all unchanged
+ } else {
+ $new->set( $_ => $cgi->param($_) ) foreach @{$payby2fields{$payby}};
+ }
+
+ my $error = $new->replace($cust_main);
+ errorpage("error saving info, payment not processed: $error")
+ if $error;
+ $cust_main = $new;
+}
+
my $error = '';
my $paynum = '';
if ( $cgi->param('batch') ) {
}
-if ( $cgi->param('save') ) {
- my $new = new FS::cust_main { $cust_main->hash };
- if ( $payby eq 'CARD' ) {
- $new->set( 'payby' => ( $cgi->param('auto') ? 'CARD' : 'DCRD' ) );
- } elsif ( $payby eq 'CHEK' ) {
- $new->set( 'payby' => ( $cgi->param('auto') ? 'CHEK' : 'DCHK' ) );
- } else {
- die "unknown payby $payby";
- }
- $new->set( 'payinfo' => $cust_main->card_token || $payinfo );
- $new->set( 'paydate' => "$year-$month-01" );
- $new->set( 'payname' => $payname );
-
- #false laziness w/FS:;cust_main::realtime_bop - check both to make sure
- # working correctly
- if ( $payby eq 'CARD' &&
- grep { $_ eq cardtype($payinfo) } $conf->config('cvv-save') ) {
- $new->set( 'paycvv' => $paycvv );
- } else {
- $new->set( 'paycvv' => '');
- }
-
- if ( $payby eq 'CARD' ) {
- my $bill_location = FS::cust_location->new;
- $bill_location->set( $_ => $cgi->param($_) )
- foreach @{$payby2fields{$payby}};
- $new->set('bill_location' => $bill_location);
- # will do nothing if the fields are all unchanged
- } else {
- $new->set( $_ => $cgi->param($_) ) foreach @{$payby2fields{$payby}};
- }
-
- my $error = $new->replace($cust_main);
- errorpage("payment processed successfully, but error saving info: $error")
- if $error;
- $cust_main = $new;
-}
-
#success!
</%init>
% # cust_bill_pkg.cgi wants a list of specific taxnums (and package class)
% # cust_credit_bill_pkg.html wants a geographic scope (and package class)
% my $rowlink = ';taxnum=' . $row->{taxnums};
-% my $rowregion = '';
+% my $rowregion = ';country=' . $cgi->param('country');
% foreach my $loc (qw(state county city district)) {
% if ( $row->{$loc} ) {
% $rowregion .= ";$loc=" . uri_escape($row->{$loc});
% $rowlink .= ';classnum=' . ($row->{pkgclass} || 0);
% $rowregion .= ';classnum=' . ($row->{pkgclass} || 0);
% }
+%warn $rowregion;
%
% if ( $row->{total} ) {
</TBODY><TBODY CLASS="total">
<% $money_sprintf->( $row->{sales_credited} ) %>
</A>
</TD>
- <TD CLASS="bigmath"> × </TD>
- <TD><% $row->{rate} %></TD>
% # taxable sales
<TD>
<A HREF="<% $saleslink . $rowlink . ";taxable=1" %>">
<% $money_sprintf->( $row->{taxable} ) %>
</A>
</TD>
+ <TD CLASS="bigmath"> × </TD>
+ <TD><% $row->{rate} %></TD>
% # estimated tax
<TD CLASS="bigmath"> = </TD>
<TD>