From faf4866cd552cc40045fd048ac77ea8f2bece2d3 Mon Sep 17 00:00:00 2001
From: Mitch Jackson
Date: Wed, 24 Jan 2018 00:43:57 -0600
Subject: RT# 77144 replace bytes_substr with Unicode::Truncate
---
FS/FS/Misc.pm | 26 ++++++++++++++------------
FS/FS/pay_batch/paymentech.pm | 16 +++++++++-------
debian/control | 2 +-
3 files changed, 24 insertions(+), 20 deletions(-)
diff --git a/FS/FS/Misc.pm b/FS/FS/Misc.pm
index 669c44e70..d84aaced5 100644
--- a/FS/FS/Misc.pm
+++ b/FS/FS/Misc.pm
@@ -22,7 +22,6 @@ use Encode;
generate_ps generate_pdf do_print
csv_from_fixed
ocr_image
- bytes_substr
money_pretty
);
@@ -982,23 +981,26 @@ sub ocr_image {
=item bytes_substr STRING, OFFSET[, LENGTH[, REPLACEMENT] ]
+DEPRECATED
+ Use Unicode::Truncate truncate_egc instead
+
A replacement for "substr" that counts raw bytes rather than logical
characters. Unlike "bytes::substr", will suppress fragmented UTF-8 characters
rather than output them. Unlike real "substr", is not an lvalue.
=cut
-sub bytes_substr {
- my ($string, $offset, $length, $repl) = @_;
- my $bytes = substr(
- Encode::encode('utf8', $string),
- $offset,
- $length,
- Encode::encode('utf8', $repl)
- );
- my $chk = $DEBUG ? Encode::FB_WARN : Encode::FB_QUIET;
- return Encode::decode('utf8', $bytes, $chk);
-}
+# sub bytes_substr {
+# my ($string, $offset, $length, $repl) = @_;
+# my $bytes = substr(
+# Encode::encode('utf8', $string),
+# $offset,
+# $length,
+# Encode::encode('utf8', $repl)
+# );
+# my $chk = $DEBUG ? Encode::FB_WARN : Encode::FB_QUIET;
+# return Encode::decode('utf8', $bytes, $chk);
+# }
=item money_pretty
diff --git a/FS/FS/pay_batch/paymentech.pm b/FS/FS/pay_batch/paymentech.pm
index 3cf3134ff..bb2c2588d 100644
--- a/FS/FS/pay_batch/paymentech.pm
+++ b/FS/FS/pay_batch/paymentech.pm
@@ -8,7 +8,7 @@ use Date::Format 'time2str';
use Date::Parse 'str2time';
use Tie::IxHash;
use FS::Conf;
-use FS::Misc 'bytes_substr';
+use Unicode::Truncate 'truncate_egc';
my $conf;
my ($bin, $merchantID, $terminalID, $username, $password, $with_recurringInd);
@@ -131,12 +131,14 @@ my %paymentech_countries = map { $_ => 1 } qw( US CA GB UK );
ecpBankAcctType => $paytype{lc($_->paytype)},
ecpDelvMethod => 'A',
),
- avsZip => bytes_substr($_->zip, 0, 10),
- avsAddress1 => bytes_substr($_->address1, 0, 30),
- avsAddress2 => bytes_substr($_->address2, 0, 30),
- avsCity => bytes_substr($_->city, 0, 20),
- avsState => bytes_substr($_->state, 0, 2),
- avsName => bytes_substr($_->first. ' '. $_->last, 0, 30),
+ # truncate_egc will die() on empty string
+ avsZip => $_->zip ? truncate_egc($_->zip, 10) : undef,
+ avsAddress1 => $_->address1 ? truncate_egc($_->address1, 30) : undef,
+ avsAddress2 => $_->address2 ? truncate_egc($_->address2, 30) : undef,
+ avsCity => $_->city ? truncate_egc($_->city, 20) : undef,
+ avsState => $_->state ? truncate_egc($_->state, 2) : undef,
+ avsName => ($_->first || $_->last)
+ ? truncate_egc($_->first. ' '. $_->last, 30) : undef,
( $paymentech_countries{ $_->country }
? ( avsCountryCode => $_->country )
: ()
diff --git a/debian/control b/debian/control
index 2391f73f7..2ec36892f 100644
--- a/debian/control
+++ b/debian/control
@@ -101,7 +101,7 @@ Depends: aspell-en,gnupg,ghostscript,gsfonts,gzip,latex-xcolor,
libmap-splat-perl, libdatetime-format-ical-perl, librest-client-perl,
libgeo-streetaddress-us-perl, libbusiness-onlinepayment-perl,
libnet-vitelity-perl (>= 0.05), libnet-sslglue-perl, libexpect-perl,
- libspreadsheet-parsexlsx-perl
+ libspreadsheet-parsexlsx-perl, libunicode-truncate-perl (>= 0.303-1)
Conflicts: libparams-classify-perl (>= 0.013-6)
Replaces: freeside (<<4)
Breaks: freeside (<<4)
--
cgit v1.2.1
From e3a53cf6d4915941327a316927310586c0126d77 Mon Sep 17 00:00:00 2001
From: Christopher Burger
Date: Thu, 1 Mar 2018 14:38:54 -0500
Subject: RT# 79239 - added option to prorate first month to synchronize with
customers other packages with sql_export plan
---
FS/FS/part_pkg/prorate_Mixin.pm | 2 +-
FS/FS/part_pkg/sql_external.pm | 44 ++++++++++++++++++++++++++++++++++++++++-
2 files changed, 44 insertions(+), 2 deletions(-)
diff --git a/FS/FS/part_pkg/prorate_Mixin.pm b/FS/FS/part_pkg/prorate_Mixin.pm
index 9e97cc593..9252143b9 100644
--- a/FS/FS/part_pkg/prorate_Mixin.pm
+++ b/FS/FS/part_pkg/prorate_Mixin.pm
@@ -30,7 +30,7 @@ tie our %prorate_round_day_opts, 'Tie::IxHash',
},
'prorate_defer_bill' => {
'name' => 'When prorating, defer the first bill until the '.
- 'billing day',
+ 'billing day or customers next bill date if synchronizing.',
'type' => 'checkbox',
},
'prorate_verbose' => {
diff --git a/FS/FS/part_pkg/sql_external.pm b/FS/FS/part_pkg/sql_external.pm
index 9bf107b7d..77b5e4983 100644
--- a/FS/FS/part_pkg/sql_external.pm
+++ b/FS/FS/part_pkg/sql_external.pm
@@ -19,6 +19,10 @@ our @detail_cols = ( qw(amount format duration phonenum accountcode
'shortname' => 'External SQL query',
'inherit_fields' => [ 'prorate_Mixin', 'global_Mixin' ],
'fields' => {
+ 'sync_bill_date' => { 'name' => 'Prorate first month to synchronize '.
+ 'with the customer\'s other packages',
+ 'type' => 'checkbox',
+ },
'cutoff_day' => { 'name' => 'Billing Day (1 - 28) for prorating or '.
'subscription',
'default' => '1',
@@ -50,7 +54,7 @@ our @detail_cols = ( qw(amount format duration phonenum accountcode
},
},
- 'fieldorder' => [qw( recur_method cutoff_day ),
+ 'fieldorder' => [qw( recur_method cutoff_day sync_bill_date),
FS::part_pkg::prorate_Mixin::fieldorder,
qw( datasrc db_username db_password query query_style
)],
@@ -140,6 +144,44 @@ sub calc_recur {
($cust_pkg->quantity || 1) * $self->calc_recur_Common($cust_pkg,$sdate,$details,$param);
}
+sub cutoff_day {
+ my $self = shift;
+ my $cust_pkg = shift;
+ my $cust_main = $cust_pkg->cust_main;
+ # force it to act like a prorate package, is what this means
+ # because we made a distinction once between prorate and flat packages
+ if ( $cust_main->force_prorate_day and $cust_main->prorate_day ) {
+ return ( $cust_main->prorate_day );
+ }
+ if ( $self->option('sync_bill_date',1) ) {
+ my $next_bill = $cust_pkg->cust_main->next_bill_date;
+ if ( $next_bill ) {
+ return (localtime($next_bill))[3];
+ } else {
+ # This is the customer's only active package and hasn't been billed
+ # yet, so set the cutoff day to either today or tomorrow, whichever
+ # would result in a full period after rounding.
+ my $setup = $cust_pkg->setup; # because it's "now"
+ my $rounding_mode = $self->option('prorate_round_day',1);
+ return () if !$setup or !$rounding_mode;
+ my ($sec, $min, $hour, $mday, $mon, $year) = localtime($setup);
+
+ if ( ( $rounding_mode == 1 and $hour >= 12 )
+ or ( $rounding_mode == 3 and ( $sec > 0 or $min > 0 or $hour > 0 ))
+ ) {
+ # then the prorate period will be rounded down to start from
+ # midnight tomorrow, so the cutoff day should be the current day +
+ # 1.
+ $setup = timelocal(59,59,23,$mday,$mon,$year) + 1;
+ $mday = (localtime($setup))[3];
+ }
+ # otherwise, it will be rounded up, so leave the cutoff day at today.
+ return $mday;
+ }
+ }
+ return ();
+}
+
sub can_discount { 1; }
sub is_free { 0; }
--
cgit v1.2.1
From 5a78b285800539935217563c961cd5dc8f084cd0 Mon Sep 17 00:00:00 2001
From: Christopher Burger
Date: Fri, 9 Mar 2018 14:14:14 -0500
Subject: RT# 79239 - updated code to use existing sub routine
---
FS/FS/part_pkg/sql_external.pm | 39 ++++-----------------------------------
1 file changed, 4 insertions(+), 35 deletions(-)
diff --git a/FS/FS/part_pkg/sql_external.pm b/FS/FS/part_pkg/sql_external.pm
index 77b5e4983..75c7e132e 100644
--- a/FS/FS/part_pkg/sql_external.pm
+++ b/FS/FS/part_pkg/sql_external.pm
@@ -145,41 +145,10 @@ sub calc_recur {
}
sub cutoff_day {
- my $self = shift;
- my $cust_pkg = shift;
- my $cust_main = $cust_pkg->cust_main;
- # force it to act like a prorate package, is what this means
- # because we made a distinction once between prorate and flat packages
- if ( $cust_main->force_prorate_day and $cust_main->prorate_day ) {
- return ( $cust_main->prorate_day );
- }
- if ( $self->option('sync_bill_date',1) ) {
- my $next_bill = $cust_pkg->cust_main->next_bill_date;
- if ( $next_bill ) {
- return (localtime($next_bill))[3];
- } else {
- # This is the customer's only active package and hasn't been billed
- # yet, so set the cutoff day to either today or tomorrow, whichever
- # would result in a full period after rounding.
- my $setup = $cust_pkg->setup; # because it's "now"
- my $rounding_mode = $self->option('prorate_round_day',1);
- return () if !$setup or !$rounding_mode;
- my ($sec, $min, $hour, $mday, $mon, $year) = localtime($setup);
-
- if ( ( $rounding_mode == 1 and $hour >= 12 )
- or ( $rounding_mode == 3 and ( $sec > 0 or $min > 0 or $hour > 0 ))
- ) {
- # then the prorate period will be rounded down to start from
- # midnight tomorrow, so the cutoff day should be the current day +
- # 1.
- $setup = timelocal(59,59,23,$mday,$mon,$year) + 1;
- $mday = (localtime($setup))[3];
- }
- # otherwise, it will be rounded up, so leave the cutoff day at today.
- return $mday;
- }
- }
- return ();
+ my( $self, $cust_pkg ) = @_;
+ my $error = SUPER->cutoff_day($cust_pkg);
+ #my $error = FS::part_pkg::flat::cutoff_day( $self, $cust_pkg ));
+ return $error;
}
sub can_discount { 1; }
--
cgit v1.2.1
From d41661cfbdcf4147342c83d3ed992f706abb92da Mon Sep 17 00:00:00 2001
From: Ivan Kohler
Date: Mon, 12 Mar 2018 22:09:48 -0700
Subject: doc
---
FS/FS/svc_dsl.pm | 72 ++++++++++++++++++++++++++++++++++++++++++--------------
1 file changed, 54 insertions(+), 18 deletions(-)
diff --git a/FS/FS/svc_dsl.pm b/FS/FS/svc_dsl.pm
index dcd6d1dbe..c07f1866c 100644
--- a/FS/FS/svc_dsl.pm
+++ b/FS/FS/svc_dsl.pm
@@ -50,15 +50,25 @@ FS::svc_Common. The following fields are currently supported:
=over 4
-=item svcnum - Primary key (assigned automatcially for new DSL))
+=item svcnum
-=item pushed - Time DSL order pushed to vendor/telco, if applicable
+Primary key (assigned automatcially for new DSL))
-=item desired_due_date - Desired Due Date
+=item pushed
-=item due_date - Due Date
+Time DSL order pushed to vendor/telco, if applicable
-=item vendor_order_id - Vendor/telco DSL order #
+=item desired_due_date
+
+Desired Due Date
+
+=item due_date
+
+Due Date
+
+=item vendor_order_id
+
+Vendor/telco DSL order #
=item vendor_order_type
@@ -69,27 +79,45 @@ Vendor/telco DSL order type (e.g. (M)ove, (A)dd, (C)hange, (D)elete, or similar)
Vendor/telco DSL order status (e.g. (N)ew, (A)ssigned, (R)ejected, (M)revised,
(C)ompleted, (X)cancelled, or similar)
-=item first - End-user first name
+=item first
+
+End-user first name
+
+=item last
+
+End-user last name
+
+=item company
-=item last - End-user last name
+End-user company name
-=item company - End-user company name
+=item phonenum
-=item phonenum - DSL Telephone Number
+DSL Telephone Number
-=item gateway_access_number - Gateway access number, if different
+=item gateway_access_number
-=item loop_type - Loop-type - vendor/telco-specific
+Gateway access number, if different
-=item local_voice_provider - Local Voice Provider's name
+=item loop_type
-=item circuitnum - Circuit #
+Loop-type - vendor/telco-specific
+
+=item local_voice_provider
+
+Local Voice Provider's name
+
+=item circuitnum
+
+Circuit #
=item vpi
=item vci
-=item rate_band - Rate Band
+=item rate_band
+
+Rate Band
=item isp_chg
@@ -101,13 +129,21 @@ Vendor/telco DSL order status (e.g. (N)ew, (A)ssigned, (R)ejected, (M)revised,
Ikano-specific fields, do not use otherwise
-=item username - if outsourced PPPoE/RADIUS, username
+=item username
+
+if outsourced PPPoE/RADIUS, username
+
+=item password
+
+if outsourced PPPoE/RADIUS, password
+
+=item monitored
-=item password - if outsourced PPPoE/RADIUS, password
+Order is monitored (auto-pull/sync), either Y or blank
-=item monitored - Order is monitored (auto-pull/sync), either Y or blank
+=item last_pull
-=item last_pull - time of last data pull from vendor/telco
+time of last data pull from vendor/telco
=back
--
cgit v1.2.1
From e46f0ef0da8d0f639bacb293bfdf820e2a68b480 Mon Sep 17 00:00:00 2001
From: Christopher Burger
Date: Tue, 13 Mar 2018 10:20:06 -0400
Subject: RT# 79780 - added a 60 second pause when there is a connection
failure, then retry. Do this for 20 hours
---
FS/bin/freeside-ipifony-download | 38 ++++++++++++++++++++++++++++----------
1 file changed, 28 insertions(+), 10 deletions(-)
diff --git a/FS/bin/freeside-ipifony-download b/FS/bin/freeside-ipifony-download
index 10faa7483..09e83ea10 100644
--- a/FS/bin/freeside-ipifony-download
+++ b/FS/bin/freeside-ipifony-download
@@ -104,16 +104,7 @@ if ( $opt{P} =~ /^(\d+)$/ ) {
}
# for now assume SFTP download as the only method
-print STDERR "Connecting to $sftpuser\@$host...\n" if $opt{v};
-
-my $sftp = Net::SFTP::Foreign->new(
- host => $host,
- user => $sftpuser,
- port => $port,
- # for now we don't support passwords. use authorized_keys.
- timeout => 30,
- #more => ($opt{v} ? '-v' : ''),
-);
+my $sftp = sftp_connect($host, $sftpuser, $port);
die "failed to connect to '$sftpuser\@$host'\n(".$sftp->error.")\n"
if $sftp->error;
@@ -289,6 +280,33 @@ Finished!
";
}
+sub sftp_connect {
+ my ($host, $sftpuser, $port) = @_;
+ my $sftp;
+ my $connection_tries = 1;
+
+ while (1) {
+ print STDERR "Connecting to $sftpuser\@$host try number $connection_tries...\n" if $opt{v};
+ $sftp = Net::SFTP::Foreign->new(
+ host => $host,
+ user => $sftpuser,
+ port => $port,
+ # for now we don't support passwords. use authorized_keys.
+ timeout => 30,
+ #more => ($opt{v} ? '-v' : ''),
+ );
+
+ if ($sftp->error && $connection_tries < 1200) {
+ $connection_tries++;
+ print STDERR "Connection failed to $sftpuser\@$host trying again in 60 sec...\n" if $opt{v};
+ sleep 60;
+ }
+ else { last; }
+ }
+
+ return $sftp
+}
+
=head1 NAME
freeside-ipifony-download - Download and import invoice items from IPifony.
--
cgit v1.2.1
From 5f9edcbe9fb3b3eb905614927aa6120d50c06ff1 Mon Sep 17 00:00:00 2001
From: Christopher Burger
Date: Tue, 13 Mar 2018 14:07:39 -0400
Subject: RT# 78356 - updated documentation and added ability to create access
points as Saisei thru api
---
FS/FS/Schema.pm | 5 +-
FS/FS/part_export/saisei.pm | 190 ++++++++++++++++++++++--------
FS/FS/tower.pm | 10 ++
FS/FS/tower_sector.pm | 14 +++
httemplate/edit/process/tower.html | 2 +-
httemplate/edit/tower.html | 6 +-
httemplate/elements/tr-tower_sectors.html | 16 ++-
7 files changed, 191 insertions(+), 52 deletions(-)
diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm
index 245fb68f8..236622b63 100644
--- a/FS/FS/Schema.pm
+++ b/FS/FS/Schema.pm
@@ -4927,6 +4927,8 @@ sub tables_hashref {
'height', 'decimal', 'NULL', '', '', '',
'veg_height', 'decimal', 'NULL', '', '', '',
'color', 'varchar', 'NULL', 6, '', '',
+ 'up_rate', 'int', 'NULL', '', '', '',
+ 'down_rate', 'int', 'NULL', '', '', '',
],
'primary_key' => 'towernum',
'unique' => [ [ 'towername' ] ], # , 'agentnum' ] ],
@@ -4957,8 +4959,9 @@ sub tables_hashref {
'east', 'decimal', 'NULL', '10,7', '', '',
'south', 'decimal', 'NULL', '10,7', '', '',
'north', 'decimal', 'NULL', '10,7', '', '',
-
'title', 'varchar', 'NULL', $char_d,'', '',
+ 'up_rate', 'int', 'NULL', '', '', '',
+ 'down_rate', 'int', 'NULL', '', '', '',
],
'primary_key' => 'sectornum',
'unique' => [ [ 'towernum', 'sectorname' ], [ 'ip_addr' ], ],
diff --git a/FS/FS/part_export/saisei.pm b/FS/FS/part_export/saisei.pm
index fc0dee5ad..f76051ea3 100644
--- a/FS/FS/part_export/saisei.pm
+++ b/FS/FS/part_export/saisei.pm
@@ -24,18 +24,29 @@ Saisei integration for Freeside
This export offers basic svc_broadband provisioning for Saisei.
-This is a customer integration with Saisei. This will setup a rate plan and tie
-the rate plan to a host via the Saisei API when the broadband service is provisioned.
-It will also untie the rate plan via the API upon unprovisioning of the broadband service.
+This is a customer integration with Saisei. This will setup a rate plan and tie
+the rate plan to a host and access point via the Saisei API when the broadband service is provisioned.
+It will also untie the rate plan via the API upon unprovisioning of the broadband service.
-This export will use the broadband service descriptive label for the Saisei rate plan name and
-will use the email from the first contact for the Saisei username that will be
-attached to this rate plan. It will use the Saisei default Access Point.
+Add a new export and fill out required fields:
+
+
Hostname or IP - Host name to Saisei API
+
Port - Port number to Saisei API
+
User Name - Saisei API user name
+
Password - Saisei API password
+
+Create a broadband service. The broadband service name will become the Saisei rate plan name.
+Set the upload and download speed, and set the modifier to fixed.
+Set IP Address to required.
+Attach Saisei export to service
+
+Create a tower and add a sector to that tower. The sector name will be the name of the access point,
+Make sure you have set an up and down rate for the Tower and Sector.
+
+When you provision the service, enter the ip address associated to this service.
+Select the Tower and Sector for it's access point.
-Hostname or IP - Host name to Saisei API
-Port - Port number to Saisei API
-User Name - Saisei API user name
-Password - Saisei API password
+When the service is provisioned it will auto setup the rate plan.
This module also provides generic methods for working through the L.
@@ -58,27 +69,37 @@ tie my %options, 'Tie::IxHash',
'options' => \%options,
'notes' => <<'END',
This is a customer integration with Saisei. This will setup a rate plan and tie
-the rate plan to a host via the Saisei API when the broadband service is provisioned.
-It will also untie the rate plan via the API upon unprovisioning of the broadband service.
-
This export will use the broadband service descriptive label for the Saisei rate plan name and
-will use the email from the first contact for the Saisei username that will be
-attached to this rate plan. It will use the Saisei default Access Point.
+the rate plan to a host and access point via the Saisei API when the broadband service is provisioned.
+It will also untie the rate plan via the API upon unprovisioning of the broadband service.
-Required Fields:
+Add a new export and fill out required fields:
Hostname or IP - Host name to Saisei API
Port - Port number to Saisei API
User Name - Saisei API user name
Password - Saisei API password
+Create a broadband service. The broadband service name will become the Saisei rate plan name.
+Set the upload and download speed, and set the modifier to fixed.
+Set IP Address to required.
+Attach Saisei export to service
+
+Create a tower and add a sector to that tower. The sector name will be the name of the access point,
+Make sure you have set an up and down rate for the Tower and Sector.
+
+When you provision the service, enter the ip address associated to this service.
+Select the Tower and Sector for it's access point.
+
+When the service is provisioned it will auto setup the rate plan.
END
);
sub _export_insert {
my ($self, $svc_broadband) = @_;
- my $rateplan_name = $svc_broadband->{Hash}->{description};
- $rateplan_name =~ s/\s/_/g;
+ my $service_part = FS::Record::qsearchs( 'part_svc', { 'svcpart' => $svc_broadband->{Hash}->{svcpart} } );
+ my $rateplan_name = $service_part->{Hash}->{svc};
+ $rateplan_name =~ s/\s/_/g;
# load needed info from our end
my $cust_main = $svc_broadband->cust_main;
@@ -99,19 +120,13 @@ sub _export_insert {
# set rateplan to existing one or newly created one.
my $rateplan = $existing_rateplan ? $existing_rateplan : $self->api_get_rateplan($rateplan_name);
- my @email = map { $_->emailaddress } FS::Record::qsearch({
- 'table' => 'cust_contact',
- 'select' => 'emailaddress',
- 'addl_from' => ' JOIN contact_email USING (contactnum)',
- 'hashref' => { 'custnum' => $cust_main->{Hash}->{custnum}, },
- });
- my $username = $email[0];
- my $description = $cust_main->{Hash}->{first}." ".$cust_main->{Hash}->{last};
+ my $username = $svc_broadband->{Hash}->{svcnum};
+ my $description = $svc_broadband->{Hash}->{description};
if (!$username) {
$self->{'__saisei_error'} = 'no username - can not export';
- warn "No email found $username\n" if $self->option('debug');
- return;
+ warn "No user $username\n" if $self->option('debug');
+ return $self->api_error;
}
else {
# check for existing user.
@@ -125,12 +140,65 @@ sub _export_insert {
my $user = $existing_user ? $existing_user : $self->api_get_user($username);
## add access point ?
-
- ## tie host to user
- $self->api_add_host_to_user($user->{collection}->[0]->{name}, $rateplan->{collection}->[0]->{name}, $svc_broadband->{Hash}->{ip_addr}) unless $self->{'__saisei_error'};
+ my $tower_sector = FS::Record::qsearchs({
+ 'table' => 'tower_sector',
+ 'select' => 'tower.towername,
+ tower.up_rate as toweruprate,
+ tower.down_rate as towerdownrate,
+ tower_sector.sectorname,
+ tower_sector.up_rate as sectoruprate,
+ tower_sector.down_rate as sectordownrate ',
+ 'addl_from' => 'LEFT JOIN tower USING ( towernum )',
+ 'hashref' => {
+ 'sectornum' => $svc_broadband->{Hash}->{sectornum},
+ },
+ });
+
+ my $existing_tower_ap;
+ my $tower_name = $tower_sector->{Hash}->{towername};
+ $tower_name =~ s/\s/_/g;
+
+ #check if tower has been set up as an access point.
+ $existing_tower_ap = $self->api_get_accesspoint($tower_name) unless $self->{'__saisei_error'};;
+
+ #if tower does not exist as an access point create it.
+ $self->api_create_accesspoint(
+ $tower_name,
+ $tower_sector->{Hash}->{toweruprate},
+ $tower_sector->{Hash}->{towerdownrate}
+ ) unless $existing_tower_ap;
+
+ my $existing_sector_ap;
+ my $sector_name = $tower_sector->{Hash}->{sectorname};
+ $sector_name =~ s/\s/_/g;
+
+ #check if sector has been set up as an access point.
+ $existing_sector_ap = $self->api_get_accesspoint($sector_name);
+
+ #if sector does not exist as an access point create it.
+ $self->api_create_accesspoint(
+ $sector_name,
+ $tower_sector->{Hash}->{sectoruprate},
+ $tower_sector->{Hash}->{sectordownrate},
+ $tower_name,
+ ) unless $existing_sector_ap;
+
+ # Attach newly created sector to it's tower.
+ $self->api_modify_accesspoint($sector_name, $tower_name) unless ($self->{'__saisei_error'} || $existing_sector_ap);
+
+ # set access point to existing one or newly created one.
+ my $accesspoint = $existing_sector_ap ? $existing_sector_ap : $self->api_get_accesspoint($sector_name);
+
+ ## tie host to user add sector name as access point.
+ $self->api_add_host_to_user(
+ $user->{collection}->[0]->{name},
+ $rateplan->{collection}->[0]->{name},
+ $svc_broadband->{Hash}->{ip_addr},
+ $accesspoint->{collection}->[0]->{name},
+ ) unless $self->{'__saisei_error'};
}
- return '';
+ return $self->api_error;
}
@@ -229,7 +297,7 @@ sub api_call {
=head2 api_error
-Returns the error string set by L methods,
+Returns the error string set by L methods,
or a blank string if most recent call produced no errors.
=cut
@@ -300,14 +368,14 @@ Gets user info for specific access point.
sub api_get_accesspoint {
my $self = shift;
- my $accesspoint;
+ my $accesspoint = shift;
my $get_accesspoint = $self->api_call("GET", "/access_points/$accesspoint");
return if $self->api_error;
- $self->{'__saisei_error'} = "Did not receive any user info"
+ $self->{'__saisei_error'} = "Did not receive any access point info"
unless $get_accesspoint;
- return;
+ return $get_accesspoint;
}
=head2 api_create_rateplan
@@ -397,19 +465,44 @@ Creates a access point.
=cut
sub api_create_accesspoint {
- my ($self,$accesspoint) = @_;
+ my ($self,$accesspoint, $uprate, $downrate) = @_;
# this has not been tested, but should work, if needed.
- #my $new_accesspoint = $self->api_call(
- # "PUT",
- # "/access_points/$accesspoint",
- # {
- # 'description' => 'my description',
- # },
- #);
-
- #$self->{'__saisei_error'} = "Access point not created"
- # unless $new_accesspoint; # should never happen
+ my $new_accesspoint = $self->api_call(
+ "PUT",
+ "/access_points/$accesspoint",
+ {
+ 'downstream_rate_limit' => $downrate,
+ 'upstream_rate_limit' => $uprate,
+ },
+ );
+
+ $self->{'__saisei_error'} = "Access point not created"
+ unless $new_accesspoint; # should never happen
+ return;
+
+}
+
+=head2 api_modify_accesspoint
+
+Modify a access point.
+
+=cut
+
+sub api_modify_accesspoint {
+ my ($self, $accesspoint, $uplink) = @_;
+
+ my $modified_rateplan = $self->api_call(
+ "PUT",
+ "/access_points/$accesspoint",
+ {
+ 'uplink' => $uplink, # name of attached access point
+ },
+ );
+
+ $self->{'__saisei_error'} = "Rate Plan not modified"
+ unless $modified_rateplan; # should never happen
+
return;
}
@@ -421,7 +514,7 @@ ties host to user, rateplan and default access point.
=cut
sub api_add_host_to_user {
- my ($self,$user, $rateplan, $ip) = @_;
+ my ($self,$user, $rateplan, $ip, $accesspoint) = @_;
my $new_host = $self->api_call(
"PUT",
@@ -429,6 +522,7 @@ sub api_add_host_to_user {
{
'user' => $user,
'rate_plan' => $rateplan,
+ 'access_point' => $accesspoint,
},
);
diff --git a/FS/FS/tower.pm b/FS/FS/tower.pm
index 18b43fe7d..2221affb6 100644
--- a/FS/FS/tower.pm
+++ b/FS/FS/tower.pm
@@ -44,6 +44,14 @@ Tower name
Disabled flag, empty or 'Y'
+=item up_rate
+
+Up Rate for towner
+
+=item down_rate
+
+Down Rate for tower
+
=back
=head1 METHODS
@@ -118,6 +126,8 @@ sub check {
|| $self->ut_floatn('height')
|| $self->ut_floatn('veg_height')
|| $self->ut_alphan('color')
+ || $self->ut_numbern('up_rate')
+ || $self->ut_numbern('down_rate')
;
return $error if $error;
diff --git a/FS/FS/tower_sector.pm b/FS/FS/tower_sector.pm
index 2e9232307..6e3104acd 100644
--- a/FS/FS/tower_sector.pm
+++ b/FS/FS/tower_sector.pm
@@ -95,6 +95,18 @@ The coverage map, as a PNG.
The coordinate boundaries of the coverage map.
+=item title
+
+The sector title.
+
+=item up_rate
+
+Up rate for sector.
+
+=item down_rate
+
+down rate for sector.
+
=back
=head1 METHODS
@@ -248,6 +260,8 @@ sub check {
|| $self->ut_decimaln('antenna_gain')
|| $self->ut_numbern('hardware_typenum')
|| $self->ut_textn('title')
+ || $self->ut_numbern('up_rate')
+ || $self->ut_numbern('down_rate')
# all of these might get relocated as part of coverage refactoring
|| $self->ut_anything('image')
|| $self->ut_sfloatn('west')
diff --git a/httemplate/edit/process/tower.html b/httemplate/edit/process/tower.html
index cfbb4ffa3..f870d1237 100644
--- a/httemplate/edit/process/tower.html
+++ b/httemplate/edit/process/tower.html
@@ -6,7 +6,7 @@
sectorname ip_addr height freq_mhz direction width
downtilt v_width db_high db_low power line_loss
antenna_gain hardware_typenum
- sector_range
+ sector_range up_rate down_rate
)],
},
&>
diff --git a/httemplate/edit/tower.html b/httemplate/edit/tower.html
index 946a1405e..14f2dfa18 100644
--- a/httemplate/edit/tower.html
+++ b/httemplate/edit/tower.html
@@ -13,6 +13,8 @@
'altitude',
'height',
'veg_height',
+ 'up_rate',
+ 'down_rate',
# { field => 'sectornum',
# type => 'tower_sector',
# o2m_table => 'tower_sector',
@@ -35,6 +37,8 @@
'height' => 'Tower height (feet)',
'veg_height' => 'Vegetation height (feet)',
'color' => 'Color',
+ 'up_rate' => 'Up Rate (Kbps)',
+ 'down_rate' => 'Down Rate (Kbps)',
},
&>
<%init>
@@ -43,7 +47,7 @@ my $m2_error_callback = sub { # reconstruct the list
my ($cgi, $object) = @_;
my @fields = qw(
- sectorname ip_addr height freq_mhz direction width tilt v_width db_high db_low sector_range
+ sectorname ip_addr height freq_mhz direction width tilt v_width db_high db_low sector_range up_rate down_rate
);
map {
diff --git a/httemplate/elements/tr-tower_sectors.html b/httemplate/elements/tr-tower_sectors.html
index 106fc76f6..6843f4fdc 100644
--- a/httemplate/elements/tr-tower_sectors.html
+++ b/httemplate/elements/tr-tower_sectors.html
@@ -17,7 +17,7 @@ my $tabcounter = 0;
my @fields = qw(
sectorname ip_addr height freq_mhz direction width downtilt v_width
db_high db_low sector_range
- power line_loss antenna_gain hardware_typenum
+ power line_loss antenna_gain hardware_typenum up_rate down_rate
);
my @sectors;
@@ -291,6 +291,20 @@ $(function() {
value="<% $sector->db_low |h %>">
<% emt('dB (low quality)') %>
+
+
+
+
+
+
+
+
%def>
--
cgit v1.2.1
From aba1dd69d21129924d268fae7f24a19a54b80c60 Mon Sep 17 00:00:00 2001
From: Ivan Kohler
Date: Tue, 13 Mar 2018 11:16:20 -0700
Subject: save logging information so we have a historical record of exactly
when problems happened, RT#79780
---
FS/bin/freeside-ipifony-download | 58 ++++++++++++++++++++++++----------------
1 file changed, 35 insertions(+), 23 deletions(-)
diff --git a/FS/bin/freeside-ipifony-download b/FS/bin/freeside-ipifony-download
index 09e83ea10..b01d80641 100644
--- a/FS/bin/freeside-ipifony-download
+++ b/FS/bin/freeside-ipifony-download
@@ -5,14 +5,15 @@ use Getopt::Std;
use Date::Format qw(time2str);
use File::Temp qw(tempdir);
use Net::SFTP::Foreign;
+use File::Copy qw(copy);
+use Text::CSV;
use FS::UID qw(adminsuidsetup);
use FS::Record qw(qsearch qsearchs);
use FS::cust_main;
use FS::Conf;
-use File::Copy qw(copy);
-use Text::CSV;
+use FS::Log;
-my %opt;
+our %opt;
getopts('vqNa:P:C:e:', \%opt);
# Product codes that are subject to flat rate E911 charges. For these
@@ -105,14 +106,18 @@ if ( $opt{P} =~ /^(\d+)$/ ) {
# for now assume SFTP download as the only method
my $sftp = sftp_connect($host, $sftpuser, $port);
-die "failed to connect to '$sftpuser\@$host'\n(".$sftp->error.")\n"
- if $sftp->error;
+if ( $sftp->error ) {
+ my $error = "Connection failed to $sftpuser\@$host: ". $sftp->error.
+ ", giving up.";
+ mylog('critical', $error);
+ die $error;
+}
$sftp->setcwd($path) if $path;
my $files = $sftp->ls('ready', wanted => qr/\.csv$/, names_only => 1);
if (!@$files) {
- print STDERR "No charge files found.\n" if $opt{v};
+ mylog('warning',"No charge files found.");
exit(-1);
}
@@ -122,7 +127,7 @@ my %e911_qty; # custnum => sum of E911-subject quantity
my %is_e911 = map {$_ => 1} @E911_CODES;
FILE: foreach my $filename (@$files) {
- print STDERR "Retrieving $filename\n" if $opt{v};
+ mylog('debug', "Retrieving $filename");
$sftp->get("ready/$filename", "$tmpdir/$filename");
if($sftp->error) {
warn "failed to download $filename\n";
@@ -131,7 +136,7 @@ FILE: foreach my $filename (@$files) {
# make sure server archive dir exists
if ( !$sftp->stat('done') ) {
- print STDERR "Creating $path/done\n" if $opt{v};
+ mylog('debug',"Creating $path/done");
$sftp->mkdir('done');
if($sftp->error) {
# something is seriously wrong
@@ -146,9 +151,9 @@ FILE: foreach my $filename (@$files) {
#copy to local archive dir
if ( $opt{a} ) {
- print STDERR "Copying $tmpdir/$filename to archive dir $opt{a}\n"
- if $opt{v};
+ mylog('debug', "Copying $tmpdir/$filename to archive dir $opt{a}");
copy("$tmpdir/$filename", $opt{a});
+ #log too? what's -a all about anyway?
warn "failed to copy $tmpdir/$filename to $opt{a}: $!" if $!;
}
@@ -163,7 +168,7 @@ FILE: foreach my $filename (@$files) {
@hash{@fields} = $csv->fields();
if ( $hash{custnum} =~ /^cust/ ) {
# there appears to be a header row
- print STDERR "skipping header row\n" if $opt{v};
+ mylog('debug', "skipping header row");
next;
}
my $cust_main =
@@ -172,8 +177,7 @@ FILE: foreach my $filename (@$files) {
warn "customer #$hash{custnum} not found\n";
next;
}
- print STDERR "Found customer #$hash{custnum}: ".$cust_main->name."\n"
- if $opt{v};
+ mylog('debug',"Found customer #$hash{custnum}: ".$cust_main->name);
my $amount = sprintf('%.2f',$hash{quantity} * $hash{unit_price});
@@ -224,8 +228,7 @@ FILE: foreach my $filename (@$files) {
}
$charge_opt{classnum} = $classnum_of{$classname};
}
- print STDERR " Charging $hash{unit_price} * $hash{quantity}\n"
- if $opt{v};
+ mylog('debug', " Charging $hash{unit_price} * $hash{quantity}");
my $error = $cust_main->charge(\%charge_opt);
if ($error) {
warn "Error creating charge: $error" if $error;
@@ -268,8 +271,7 @@ foreach my $custnum ( keys (%e911_qty) ) {
$dbh->commit;
-if ($opt{v}) {
- print STDERR "
+mylog('debug', "
Finished!
Processed files: @$files
Created charges: $num_charges
@@ -277,8 +279,7 @@ Finished!
E911 charges: $num_e911
E911 lines: $num_lines
Errors: $num_errors
-";
-}
+");
sub sftp_connect {
my ($host, $sftpuser, $port) = @_;
@@ -286,7 +287,7 @@ sub sftp_connect {
my $connection_tries = 1;
while (1) {
- print STDERR "Connecting to $sftpuser\@$host try number $connection_tries...\n" if $opt{v};
+ mylog('info', "Connecting to $sftpuser\@$host try number $connection_tries...");
$sftp = Net::SFTP::Foreign->new(
host => $host,
user => $sftpuser,
@@ -298,13 +299,23 @@ sub sftp_connect {
if ($sftp->error && $connection_tries < 1200) {
$connection_tries++;
- print STDERR "Connection failed to $sftpuser\@$host trying again in 60 sec...\n" if $opt{v};
+ mylog('error', "Connection failed to $sftpuser\@$host: ". $sftp->error.
+ ", trying again in 60 sec...");
sleep 60;
}
else { last; }
}
- return $sftp
+ return $sftp;
+}
+
+our $log;
+sub mylog {
+ my( $level, $message ) = @_;
+ #warn "$message\n" if $opt{v};
+ print STDERR "$message\n" if $opt{v};
+ $log ||= FS::Log->new('freeside-ipifony-download');
+ $log->log(@_);
}
=head1 NAME
@@ -338,7 +349,8 @@ directory is the one containing the "ready/" and "done/" subdirectories.
=head1 OPTIONAL PARAMETERS
--v: Be verbose.
+-v: Be verbose; send debugging information to STDERR in addition to the
+internal log..
-q: Include the quantity and unit price in the charge description.
--
cgit v1.2.1
From b20ef760ea09410b688c5bfe2c7e471c09e19427 Mon Sep 17 00:00:00 2001
From: Ivan Kohler
Date: Wed, 21 Mar 2018 18:28:12 -0700
Subject: more room for imported package names, RT#79383
---
FS/FS/Schema.pm | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm
index 236622b63..6d70ede8f 100644
--- a/FS/FS/Schema.pm
+++ b/FS/FS/Schema.pm
@@ -3269,7 +3269,7 @@ sub tables_hashref {
'columns' => [
'pkgpart', 'serial', '', '', '', '',
'pkgpartbatch', 'varchar', 'NULL', $char_d, '', '',
- 'pkg', 'varchar', '', $char_d, '', '',
+ 'pkg', 'varchar', '', 104, '', '',
'comment', 'varchar', 'NULL', 2*$char_d, '', '',
'promo_code', 'varchar', 'NULL', $char_d, '', '',
'freq', 'varchar', '', $char_d, '', '', #billing frequency
--
cgit v1.2.1
From b21f69637bdfe8a83bc8f8f1be8a701a332d714f Mon Sep 17 00:00:00 2001
From: Ivan Kohler
Date: Thu, 22 Mar 2018 16:15:17 -0700
Subject: save logging information so we have a historical record of exactly
when problems happened, RT#79780
---
FS/FS/log_context.pm | 1 +
FS/bin/freeside-ipifony-download | 2 +-
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/FS/FS/log_context.pm b/FS/FS/log_context.pm
index 387883b63..74038fc05 100644
--- a/FS/FS/log_context.pm
+++ b/FS/FS/log_context.pm
@@ -26,6 +26,7 @@ my @contexts = ( qw(
queue
upgrade
upgrade_taxable_billpkgnum
+ freeside-ipifony-download
freeside-paymentech-upload
freeside-paymentech-download
test
diff --git a/FS/bin/freeside-ipifony-download b/FS/bin/freeside-ipifony-download
index b01d80641..1e77c3a75 100644
--- a/FS/bin/freeside-ipifony-download
+++ b/FS/bin/freeside-ipifony-download
@@ -315,7 +315,7 @@ sub mylog {
#warn "$message\n" if $opt{v};
print STDERR "$message\n" if $opt{v};
$log ||= FS::Log->new('freeside-ipifony-download');
- $log->log(@_);
+ $log->log(level=>$level, message=>$message);
}
=head1 NAME
--
cgit v1.2.1
From c38b6699d83ba0d7e3fd582373b9c8e78c9217d2 Mon Sep 17 00:00:00 2001
From: Mitch Jackson
Date: Sun, 25 Mar 2018 18:55:17 -0500
Subject: RT# 79636 Taxes per section when using invoice_sections
---
FS/FS/Conf.pm | 8 ++++-
FS/FS/Template_Mixin.pm | 82 ++++++++++++++++++++++++++++++++++++++++++++++---
FS/FS/cust_bill_pkg.pm | 65 ++++++++++++++++++++++++++++++++++++++-
3 files changed, 149 insertions(+), 6 deletions(-)
diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm
index 9b891879b..721b7bdf9 100644
--- a/FS/FS/Conf.pm
+++ b/FS/FS/Conf.pm
@@ -1598,6 +1598,13 @@ and customer address. Include units.',
'select_enum' => [ qw(category location) ],
},
+ {
+ 'key' => 'invoice_sections_with_taxes',
+ 'section' => 'invoicing',
+ 'description' => 'Include taxes within each section of mutli-section invoices.',
+ 'type' => 'checkbox',
+ },
+
{
'key' => 'summary_subtotals_method',
'section' => 'invoicing',
@@ -5961,4 +5968,3 @@ and customer address. Include units.',
);
1;
-
diff --git a/FS/FS/Template_Mixin.pm b/FS/FS/Template_Mixin.pm
index 88fd4e87f..a1ed4b0c0 100644
--- a/FS/FS/Template_Mixin.pm
+++ b/FS/FS/Template_Mixin.pm
@@ -1196,6 +1196,8 @@ sub print_generic {
my %options = ();
$options{'section'} = $section if $multisection;
+ $options{'section_with_taxes'} = 1
+ if $conf->exists('invoice_sections_with_taxes');
$options{'format'} = $format;
$options{'escape_function'} = $escape_function;
$options{'no_usage'} = 1 unless $unsquelched;
@@ -1208,6 +1210,8 @@ sub print_generic {
warn "$me searching for line items\n"
if $DEBUG > 1;
+ my %section_tax_lines;
+ my %seen_tax_lines;
foreach my $line_item ( $self->_items_pkg(%options),
$self->_items_fee(%options) ) {
@@ -1232,9 +1236,56 @@ sub print_generic {
}
$line_item->{'ext_description'} ||= [];
+ if ( $options{section_with_taxes} && ref $line_item->{pkg_tax} ) {
+ for my $line_tax ( @{$ line_item->{pkg_tax} } ) {
+
+ # It is rarely possible for the same tax record to be presented here
+ # multiple times. See cust_bill_pkg::_pkg_tax_list for more info
+ next if $seen_tax_lines{ $line_tax->{billpkgtaxlocationnum} };
+ $seen_tax_lines{ $line_tax->{billpkgtaxlocationnum} } = 1;
+
+ $section_tax_lines{ $line_tax->{taxname} } += $line_tax->{amount};
+ }
+ }
+
push @detail_items, $line_item;
}
+ # If conf flag invoice_sections_with_taxes:
+ # - Add @detail_items for taxes into each section
+ # - Update section subtotal to include taxes
+ if ( $options{section_with_taxes} && %section_tax_lines ) {
+ for my $taxname ( keys %section_tax_lines ) {
+
+ push @detail_items, {
+ section => $section,
+ amount => sprintf($money_char."%.2f",$section_tax_lines{$taxname}),
+ description => &$escape_function($taxname),
+ };
+
+ # Append taxes to total. If line format resembles "$5.00 to $12.00"
+ # append to the second value.
+
+ # $section->{subtotal} = '$5.00 to 12.00'; # for testing:
+ if ($section->{subtotal} =~ /to/) {
+ my @subtotal = split /\s/, $section->{subtotal};
+ $subtotal[2] =~ s/[^\d\.]//g;
+ $subtotal[2] = sprintf(
+ $money_char."%.2f",
+ ( $subtotal[2] + $section_tax_lines{$taxname} )
+ );
+ $section->{subtotal} = join ' ', @subtotal;
+ } else {
+ $section->{subtotal} =~ s/[^\d\.]//g;
+ $section->{subtotal} = sprintf(
+ $money_char . "%.2f",
+ ( $section->{subtotal} + $section_tax_lines{$taxname} )
+ );
+ }
+
+ }
+ }
+
if ( $section->{'description'} ) {
push @buf, ( ['','-----------'],
[ $section->{'description'}. ' sub-total',
@@ -1335,13 +1386,20 @@ sub print_generic {
$tax_section->{'description'} = $self->mt($tax_description);
$tax_section->{'summarized'} = '';
- # append it if it's not already there
- if ( !grep $tax_section, @sections ) {
+ if ( $conf->exists('invoice_sections_with_taxes')) {
+
+ # remove tax section if taxes are itemized within other sections
+ @sections = grep{ $_ ne $tax_section } @sections;
+
+ } elsif ( !grep $tax_section, @sections ) {
+
+ # append it if it's not already there
push @sections, $tax_section;
push @summary_subtotals, $tax_section;
+
}
- }
+ }
} else {
unshift @total_items, $total;
}
@@ -3086,11 +3144,15 @@ sub _items_fee {
my $desc = $part_fee->itemdesc_locale($self->cust_main->locale);
# but not escape the base description line
+ my @pkg_tax = $cust_bill_pkg->_pkg_tax_list
+ if $options{section_with_taxes};
+
push @items,
{ feepart => $cust_bill_pkg->feepart,
amount => sprintf('%.2f', $cust_bill_pkg->setup + $cust_bill_pkg->recur),
description => $desc,
- ext_description => \@ext_desc
+ pkg_tax => \@pkg_tax,
+ ext_description => \@ext_desc,
# sdate/edate?
};
}
@@ -3188,6 +3250,8 @@ location (whichever is defined).
multisection: a flag indicating that this is a multisection invoice,
which does something complicated.
+section_with_taxes: Look up and include applied taxes for each record
+
Returns a list of hashrefs, each of which may contain:
pkgnum, description, amount, unit_amount, quantity, pkgpart, _is_setup, and
@@ -3347,6 +3411,9 @@ sub _items_cust_bill_pkg {
# not normally used, but pass this to the template anyway
$classname = $part_pkg->classname;
+ my @pkg_tax = $cust_bill_pkg->_pkg_tax_list
+ if $opt{section_with_taxes};
+
if ( (!$type || $type eq 'S')
&& ( $cust_bill_pkg->setup != 0
|| $cust_bill_pkg->setup_show_zero
@@ -3420,6 +3487,7 @@ sub _items_cust_bill_pkg {
push @{ $s->{ext_description} }, @d;
} else {
$s = {
+ billpkgnum => $cust_bill_pkg->billpkgnum,
_is_setup => 1,
description => $description,
pkgpart => $pkgpart,
@@ -3431,6 +3499,7 @@ sub _items_cust_bill_pkg {
ext_description => \@d,
svc_label => ($svc_label || ''),
locationnum => $cust_pkg->locationnum, # sure, why not?
+ pkg_tax => \@pkg_tax,
};
};
@@ -3584,6 +3653,7 @@ sub _items_cust_bill_pkg {
push @{ $r->{ext_description} }, @d;
} else {
$r = {
+ billpkgnum => $cust_bill_pkg->billpkgnum,
description => $description,
pkgpart => $pkgpart,
pkgnum => $cust_bill_pkg->pkgnum,
@@ -3595,6 +3665,7 @@ sub _items_cust_bill_pkg {
ext_description => \@d,
svc_label => ($svc_label || ''),
locationnum => $cust_pkg->locationnum,
+ pkg_tax => \@pkg_tax,
};
$r->{'seconds'} = \@seconds if grep {defined $_} @seconds;
}
@@ -3613,6 +3684,7 @@ sub _items_cust_bill_pkg {
} elsif ( $amount ) {
# create a new usage line
$u = {
+ billpkgnum => $cust_bill_pkg->billpkgnum,
description => $description,
pkgpart => $pkgpart,
pkgnum => $cust_bill_pkg->pkgnum,
@@ -3622,6 +3694,7 @@ sub _items_cust_bill_pkg {
%item_dates,
ext_description => \@d,
locationnum => $cust_pkg->locationnum,
+ pkg_tax => \@pkg_tax,
};
} # else this has no usage, so don't create a usage section
}
@@ -3755,4 +3828,5 @@ sub _items_discounts_avail {
}
+
1;
diff --git a/FS/FS/cust_bill_pkg.pm b/FS/FS/cust_bill_pkg.pm
index 77dce2476..a36520b77 100644
--- a/FS/FS/cust_bill_pkg.pm
+++ b/FS/FS/cust_bill_pkg.pm
@@ -1815,6 +1815,70 @@ sub upgrade_tax_location {
'';
}
+sub _pkg_tax_list {
+ # Return an array of hashrefs for each cust_bill_pkg_tax_location
+ # applied to this bill for this cust_bill_pkg.pkgnum.
+ #
+ # ! Important Note:
+ # In some situations, this list will contain more tax records than the
+ # ones directly related to $self->billpkgnum. The returned list contains
+ # all records, for this bill, charged against this billpkgnum's pkgnum.
+ #
+ # One must keep this in mind when using data returned by this method.
+ #
+ # An unaddressed deficiency in the cust_bill_pkg_tax_location model makes
+ # this necessary: When a linked-hidden package generates a tax/fee as a row
+ # in cust_bill_pkg_tax_location, there is not enough information to surmise
+ # with specificity which billpkgnum row represents the direct parent of the
+ # the linked-hidden package's tax row. The closest we can get to this
+ # backwards reassociation is to use the pkgnum. Therefore, when multiple
+ # billpkgnum's appear with the same pkgnum, this method is going to return
+ # the tax records for ALL of those billpkgnum's, not just $self->billpkgnum.
+ #
+ # This could be addressed with an update to the model, and to the billing
+ # routine that generates rows into cust_bill_pkg_tax_location. Perhaps a
+ # column, link_billpkgnum or parent_billpkgnum, recording the link. I'm not
+ # doing that now, because there would be no possible repair of data stored
+ # historically prior to such a fix. I need _pkg_tax_list() to not be
+ # broken for already-generated bills.
+ #
+ # Any code you write relying on _pkg_tax_list() MUST be aware of, and
+ # account for, the possible return of duplicated tax records returned
+ # when method is called on multiple cust_bill_pkg_tax_location rows.
+ # Duplicates can be identified by billpkgtaxlocationnum column.
+
+ my $self = shift;
+ return unless $self->pkgnum;
+
+ map +{
+ billpkgtaxlocationnum => $_->billpkgtaxlocationnum,
+ billpkgnum => $_->billpkgnum,
+ taxnum => $_->taxnum,
+ amount => $_->amount,
+ taxname => $_->taxname,
+ },
+ qsearch({
+ table => 'cust_bill_pkg_tax_location',
+ addl_from => '
+ LEFT JOIN cust_bill_pkg
+ ON cust_bill_pkg.billpkgnum
+ = cust_bill_pkg_tax_location.taxable_billpkgnum
+ ',
+ select => join( ', ', (qw|
+ cust_bill_pkg.billpkgnum
+ cust_bill_pkg_tax_location.billpkgtaxlocationnum
+ cust_bill_pkg_tax_location.taxnum
+ cust_bill_pkg_tax_location.amount
+ |)),
+ extra_sql =>
+ ' WHERE '.
+ ' cust_bill_pkg.invnum = ' . dbh->quote( $self->invnum ) .
+ ' AND '.
+ ' cust_bill_pkg_tax_location.pkgnum = ' . dbh->quote( $self->pkgnum ),
+ });
+
+}
+
sub _upgrade_data {
# Create a queue job to run upgrade_tax_location from January 1, 2012 to
# the present date.
@@ -1873,4 +1937,3 @@ from the base documentation.
=cut
1;
-
--
cgit v1.2.1
From ec34b8903d969fe8ac4ff6947a92e16e07f71fa0 Mon Sep 17 00:00:00 2001
From: Christopher Burger
Date: Tue, 27 Mar 2018 09:20:05 -0400
Subject: RT# 78356 - added ability to create and modify rateplans and access
point when changed on freeside. cleanded up documentation.
---
FS/FS/Schema.pm | 8 +-
FS/FS/part_export/saisei.pm | 454 ++++++++++++++++++++------
FS/FS/part_svc.pm | 17 +
FS/FS/tower.pm | 12 +-
FS/FS/tower_sector.pm | 27 +-
httemplate/edit/part_export.cgi | 14 +
httemplate/edit/process/elements/process.html | 8 +
httemplate/edit/process/tower.html | 2 +-
httemplate/edit/tower.html | 10 +-
httemplate/elements/tr-tower_sectors.html | 14 +-
10 files changed, 436 insertions(+), 130 deletions(-)
diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm
index 6d70ede8f..5b33ecba9 100644
--- a/FS/FS/Schema.pm
+++ b/FS/FS/Schema.pm
@@ -4927,8 +4927,8 @@ sub tables_hashref {
'height', 'decimal', 'NULL', '', '', '',
'veg_height', 'decimal', 'NULL', '', '', '',
'color', 'varchar', 'NULL', 6, '', '',
- 'up_rate', 'int', 'NULL', '', '', '',
- 'down_rate', 'int', 'NULL', '', '', '',
+ 'up_rate_limit', 'int', 'NULL', '', '', '',
+ 'down_rate_limit', 'int', 'NULL', '', '', '',
],
'primary_key' => 'towernum',
'unique' => [ [ 'towername' ] ], # , 'agentnum' ] ],
@@ -4960,8 +4960,8 @@ sub tables_hashref {
'south', 'decimal', 'NULL', '10,7', '', '',
'north', 'decimal', 'NULL', '10,7', '', '',
'title', 'varchar', 'NULL', $char_d,'', '',
- 'up_rate', 'int', 'NULL', '', '', '',
- 'down_rate', 'int', 'NULL', '', '', '',
+ 'up_rate_limit', 'int', 'NULL', '', '', '',
+ 'down_rate_limit', 'int', 'NULL', '', '', '',
],
'primary_key' => 'sectornum',
'unique' => [ [ 'towernum', 'sectorname' ], [ 'ip_addr' ], ],
diff --git a/FS/FS/part_export/saisei.pm b/FS/FS/part_export/saisei.pm
index f76051ea3..1c95081bd 100644
--- a/FS/FS/part_export/saisei.pm
+++ b/FS/FS/part_export/saisei.pm
@@ -28,30 +28,47 @@ This is a customer integration with Saisei. This will setup a rate plan and tie
the rate plan to a host and access point via the Saisei API when the broadband service is provisioned.
It will also untie the rate plan via the API upon unprovisioning of the broadband service.
+This will create and modify the rate plans at Saisei as soon as the broadband service attached to this export is created or modified.
+This will also create and modify a access point at Saisei as soon as the tower is created or modified.
+
+To use this export, follow the below instructions:
+
Add a new export and fill out required fields:
-
-
Hostname or IP - Host name to Saisei API
-
Port - Port number to Saisei API
-
User Name - Saisei API user name
-
Password - Saisei API password
-
+
+Hostname or IP - Host name to Saisei API
+User Name - Saisei API user name
+Password - Saisei API password
+
Create a broadband service. The broadband service name will become the Saisei rate plan name.
-Set the upload and download speed, and set the modifier to fixed.
-Set IP Address to required.
-Attach Saisei export to service
+Set the upload and download speed for the service. This is required to be able to export the service to Saisei.
+Attach above created Saisei export to this broadband service.
Create a tower and add a sector to that tower. The sector name will be the name of the access point,
-Make sure you have set an up and down rate for the Tower and Sector.
+Make sure you have set the up and down rate limit for the Tower and Sector. This is required to be able to export the access point.
-When you provision the service, enter the ip address associated to this service.
-Select the Tower and Sector for it's access point.
+Create a package for the above created broadband service, and order this package for a customer.
-When the service is provisioned it will auto setup the rate plan.
+When you provision the service, enter the ip address associated to this service and select the Tower and Sector for it's access point.
+This provisioned service will then be exported as a host to Saisei.
+
+when you un provision this service, the host entry at Saisei will be deleted.
+
+When setting this up, if you wish to export your allready provisioned services, make sure the broadband service has this export attached and
+on export edit screen there will be a link to export Provisioned Services attached to this export. Clicking on that will export all services
+not currently exported to Saisei.
This module also provides generic methods for working through the L.
=cut
+tie my %scripts, 'Tie::IxHash',
+ 'export_provisioned_services' => { component => '/elements/popup_link.html',
+ label => 'Export provisioned services',
+ description => 'will export provisioned services of part service with Saisei export attached.',
+ html_label => 'Export Provisioned Services attached to this export.',
+ },
+;
+
tie my %options, 'Tie::IxHash',
'port' => { label => 'Port',
default => 5000 },
@@ -67,11 +84,19 @@ tie my %options, 'Tie::IxHash',
'svc' => 'svc_broadband',
'desc' => 'Export broadband service/account to Saisei',
'options' => \%options,
+ 'scripts' => \%scripts,
'notes' => <<'END',
This is a customer integration with Saisei. This will setup a rate plan and tie
the rate plan to a host and access point via the Saisei API when the broadband service is provisioned.
It will also untie the rate plan via the API upon unprovisioning of the broadband service.
+This will create and modify the rate plans at Saisei as soon as the broadband service attached to this export is created or modified.
+This will also create and modify a access point at Saisei as soon as the tower is created or modified.
+
+To use this export, follow the below instructions:
+
+
+
Add a new export and fill out required fields:
Hostname or IP - Host name to Saisei API
@@ -79,18 +104,34 @@ Add a new export and fill out required fields:
User Name - Saisei API user name
Password - Saisei API password
+
+
+
Create a broadband service. The broadband service name will become the Saisei rate plan name.
-Set the upload and download speed, and set the modifier to fixed.
-Set IP Address to required.
-Attach Saisei export to service
+Set the upload and download speed for the service. This is required to be able to export the service to Saisei.
+Attach above created Saisei export to this broadband service.
+
+
Create a tower and add a sector to that tower. The sector name will be the name of the access point,
-Make sure you have set an up and down rate for the Tower and Sector.
+Make sure you have set the up and down rate limit for the Tower and Sector. This is required to be able to export the access point.
+
+
+
+Create a package for the above created broadband service, and order this package for a customer.
+
+
+
+When you provision the service, enter the ip address associated to this service and select the Tower and Sector for it's access point.
+This provisioned service will then be exported as a host to Saisei.
-When you provision the service, enter the ip address associated to this service.
-Select the Tower and Sector for it's access point.
+when you un provision this service, the host entry at Saisei will be deleted.
+
+
-When the service is provisioned it will auto setup the rate plan.
+When setting this up, if you wish to export your allready provisioned services, make sure the broadband service has this export attached and
+on export edit screen there will be a link to export Provisioned Services attached to this export. Clicking on that will export all services
+not currently exported to Saisei.
END
);
@@ -101,21 +142,14 @@ sub _export_insert {
my $rateplan_name = $service_part->{Hash}->{svc};
$rateplan_name =~ s/\s/_/g;
- # load needed info from our end
- my $cust_main = $svc_broadband->cust_main;
- return "Could not load service customer" unless $cust_main;
- my $conf = new FS::Conf;
-
- # get policy list
- my $policies = $self->api_get_policies();
-
# check for existing rate plan
my $existing_rateplan;
$existing_rateplan = $self->api_get_rateplan($rateplan_name) unless $self->{'__saisei_error'};
# if no existing rate plan create one and modify it.
$self->api_create_rateplan($svc_broadband, $rateplan_name) unless $existing_rateplan;
- $self->api_modify_rateplan($policies->{collection}, $svc_broadband, $rateplan_name) unless ($self->{'__saisei_error'} || $existing_rateplan);
+ $self->api_modify_rateplan($svc_broadband, $rateplan_name) unless ($self->{'__saisei_error'} || $existing_rateplan);
+ return $self->api_error if $self->{'__saisei_error'};
# set rateplan to existing one or newly created one.
my $rateplan = $existing_rateplan ? $existing_rateplan : $self->api_get_rateplan($rateplan_name);
@@ -125,7 +159,6 @@ sub _export_insert {
if (!$username) {
$self->{'__saisei_error'} = 'no username - can not export';
- warn "No user $username\n" if $self->option('debug');
return $self->api_error;
}
else {
@@ -135,59 +168,49 @@ sub _export_insert {
# if no existing user create one.
$self->api_create_user($username, $description) unless $existing_user;
+ return $self->api_error if $self->{'__saisei_error'};
# set user to existing one or newly created one.
my $user = $existing_user ? $existing_user : $self->api_get_user($username);
- ## add access point ?
+ ## add access point
my $tower_sector = FS::Record::qsearchs({
'table' => 'tower_sector',
'select' => 'tower.towername,
- tower.up_rate as toweruprate,
- tower.down_rate as towerdownrate,
+ tower.up_rate_limit as tower_upratelimit,
+ tower.down_rate_limit as tower_downratelimit,
tower_sector.sectorname,
- tower_sector.up_rate as sectoruprate,
- tower_sector.down_rate as sectordownrate ',
+ tower_sector.up_rate_limit as sector_upratelimit,
+ tower_sector.down_rate_limit as sector_downratelimit ',
'addl_from' => 'LEFT JOIN tower USING ( towernum )',
'hashref' => {
'sectornum' => $svc_broadband->{Hash}->{sectornum},
},
});
- my $existing_tower_ap;
my $tower_name = $tower_sector->{Hash}->{towername};
$tower_name =~ s/\s/_/g;
- #check if tower has been set up as an access point.
- $existing_tower_ap = $self->api_get_accesspoint($tower_name) unless $self->{'__saisei_error'};;
+ my $tower_opt = {
+ 'tower_name' => $tower_name,
+ 'tower_uprate_limit' => $tower_sector->{Hash}->{tower_upratelimit},
+ 'tower_downrate_limit' => $tower_sector->{Hash}->{tower_downratelimit},
+ };
- #if tower does not exist as an access point create it.
- $self->api_create_accesspoint(
- $tower_name,
- $tower_sector->{Hash}->{toweruprate},
- $tower_sector->{Hash}->{towerdownrate}
- ) unless $existing_tower_ap;
+ my $tower_ap = process_tower($self, $tower_opt);
+ return $self->api_error if $self->{'__saisei_error'};
- my $existing_sector_ap;
my $sector_name = $tower_sector->{Hash}->{sectorname};
$sector_name =~ s/\s/_/g;
- #check if sector has been set up as an access point.
- $existing_sector_ap = $self->api_get_accesspoint($sector_name);
-
- #if sector does not exist as an access point create it.
- $self->api_create_accesspoint(
- $sector_name,
- $tower_sector->{Hash}->{sectoruprate},
- $tower_sector->{Hash}->{sectordownrate},
- $tower_name,
- ) unless $existing_sector_ap;
-
- # Attach newly created sector to it's tower.
- $self->api_modify_accesspoint($sector_name, $tower_name) unless ($self->{'__saisei_error'} || $existing_sector_ap);
-
- # set access point to existing one or newly created one.
- my $accesspoint = $existing_sector_ap ? $existing_sector_ap : $self->api_get_accesspoint($sector_name);
+ my $sector_opt = {
+ 'tower_name' => $tower_name,
+ 'sector_name' => $sector_name,
+ 'sector_uprate_limit' => $tower_sector->{Hash}->{sector_upratelimit},
+ 'sector_downrate_limit' => $tower_sector->{Hash}->{sector_downratelimit},
+ };
+ my $accesspoint = process_sector($self, $sector_opt);
+ return $self->api_error if $self->{'__saisei_error'};
## tie host to user add sector name as access point.
$self->api_add_host_to_user(
@@ -203,27 +226,17 @@ sub _export_insert {
}
sub _export_replace {
- my ($self, $svc_phone) = @_;
+ my ($self, $svc_broadband) = @_;
return '';
}
sub _export_delete {
my ($self, $svc_broadband) = @_;
- my $cust_main = $svc_broadband->cust_main;
- return "Could not load service customer" unless $cust_main;
- my $conf = new FS::Conf;
-
- my $rateplan_name = $svc_broadband->{Hash}->{description};
+ my $service_part = FS::Record::qsearchs( 'part_svc', { 'svcpart' => $svc_broadband->{Hash}->{svcpart} } );
+ my $rateplan_name = $service_part->{Hash}->{svc};
$rateplan_name =~ s/\s/_/g;
-
- my @email = map { $_->emailaddress } FS::Record::qsearch({
- 'table' => 'cust_contact',
- 'select' => 'emailaddress',
- 'addl_from' => ' JOIN contact_email USING (contactnum)',
- 'hashref' => { 'custnum' => $cust_main->{Hash}->{custnum}, },
- });
- my $username = $email[0];
+ my $username = $svc_broadband->{Hash}->{svcnum};
## tie host to user
$self->api_delete_host_to_user($username, $rateplan_name, $svc_broadband->{Hash}->{ip_addr}) unless $self->{'__saisei_error'};
@@ -232,15 +245,81 @@ sub _export_delete {
}
sub _export_suspend {
- my ($self, $svc_phone) = @_;
+ my ($self, $svc_broadband) = @_;
return '';
}
sub _export_unsuspend {
- my ($self, $svc_phone) = @_;
+ my ($self, $svc_broadband) = @_;
return '';
}
+sub export_partsvc {
+ my ($self, $svc_part) = @_;
+
+ my $rateplan_name = $svc_part->{Hash}->{svc};
+ $rateplan_name =~ s/\s/_/g;
+ my $speeddown = $svc_part->{Hash}->{svc_broadband__speed_down};
+ my $speedup = $svc_part->{Hash}->{svc_broadband__speed_up};
+
+ my $temp_svc = $svc_part->{Hash};
+ my $svc_broadband = {};
+ map { if ($_ =~ /^svc_broadband__(.*)$/) { $svc_broadband->{Hash}->{$1} = $temp_svc->{$_}; } } keys %$temp_svc;
+
+ # check for existing rate plan
+ my $existing_rateplan;
+ $existing_rateplan = $self->api_get_rateplan($rateplan_name) unless $self->{'__saisei_error'};
+
+ # Modify the existing rate plan with new service data.
+ $self->api_modify_existing_rateplan($svc_broadband, $rateplan_name) unless ($self->{'__saisei_error'} || !$existing_rateplan);
+
+ # if no existing rate plan create one and modify it.
+ $self->api_create_rateplan($svc_broadband, $rateplan_name) unless $existing_rateplan;
+ $self->api_modify_rateplan($svc_part, $rateplan_name) unless ($self->{'__saisei_error'} || $existing_rateplan);
+
+ return $self->api_error;
+
+}
+
+sub export_tower_sector {
+ my ($self, $tower) = @_;
+
+ #modify tower or create it.
+ my $tower_name = $tower->{Hash}->{towername};
+ $tower_name =~ s/\s/_/g;
+ my $tower_opt = {
+ 'tower_name' => $tower_name,
+ 'tower_uprate_limit' => $tower->{Hash}->{up_rate_limit},
+ 'tower_downrate_limit' => $tower->{Hash}->{down_rate_limit},
+ 'modify_existing' => '1', # modify an existing access point with this info
+ };
+
+ my $tower_access_point = process_tower($self, $tower_opt);
+
+ #get list of all access points
+ my $hash_opt = {
+ 'table' => 'tower_sector',
+ 'select' => '*',
+ 'hashref' => { 'towernum' => $tower->{Hash}->{towernum}, },
+ };
+
+ #for each one modify or create it.
+ foreach my $tower_sector ( FS::Record::qsearch($hash_opt) ) {
+ my $sector_name = $tower_sector->{Hash}->{sectorname};
+ $sector_name =~ s/\s/_/g;
+ my $sector_opt = {
+ 'tower_name' => $tower_name,
+ 'sector_name' => $sector_name,
+ 'sector_uprate_limit' => $tower_sector->{Hash}->{up_rate_limit},
+ 'sector_downrate_limit' => $tower_sector->{Hash}->{down_rate_limit},
+ 'modify_existing' => '1', # modify an existing access point with this info
+ };
+ my $sector_access_point = process_sector($self, $sector_opt);
+ }
+
+ return $self->api_error;
+}
+
=head1 Saisei API
These methods allow access to the Saisei API using the credentials
@@ -259,6 +338,7 @@ Returns empty on failure; retrieve error messages using L.
sub api_call {
my ($self,$method,$path,$params) = @_;
+
$self->{'__saisei_error'} = '';
my $auth_info = $self->option('username') . ':' . $self->option('password');
$params ||= {};
@@ -286,7 +366,8 @@ sub api_call {
}
}
else {
- $self->{'__saisei_error'} = "Bad response from server during $method: " . $client->responseContent();
+ $self->{'__saisei_error'} = "Bad response from server during $method: " . $client->responseContent()
+ unless ($method eq "GET");
warn "Response Content is\n".$client->responseContent."\n" if $self->option('debug');
return;
}
@@ -321,7 +402,7 @@ sub api_get_policies {
$self->{'__saisei_error'} = "Did not receive any global policies"
unless $get_policies;
- return $get_policies;
+ return $get_policies->{collection};
}
=head2 api_get_rateplan
@@ -336,8 +417,6 @@ sub api_get_rateplan {
my $get_rateplan = $self->api_call("GET", "/rate_plans/$rateplan");
return if $self->api_error;
- $self->{'__saisei_error'} = "Did not receive any rateplan info"
- unless $get_rateplan;
return $get_rateplan;
}
@@ -354,8 +433,6 @@ sub api_get_user {
my $get_user = $self->api_call("GET", "/users/$user");
return if $self->api_error;
- $self->{'__saisei_error'} = "Did not receive any user info"
- unless $get_user;
return $get_user;
}
@@ -372,12 +449,27 @@ sub api_get_accesspoint {
my $get_accesspoint = $self->api_call("GET", "/access_points/$accesspoint");
return if $self->api_error;
- $self->{'__saisei_error'} = "Did not receive any access point info"
- unless $get_accesspoint;
return $get_accesspoint;
}
+=head2 api_get_host
+
+Gets user info for specific host.
+
+=cut
+
+sub api_get_host {
+ my $self = shift;
+ my $ip = shift;
+
+ my $get_host = $self->api_call("GET", "/hosts/$ip");
+
+ return if $self->api_error;
+
+ return $get_host;
+}
+
=head2 api_create_rateplan
Creates a rateplan.
@@ -387,6 +479,9 @@ Creates a rateplan.
sub api_create_rateplan {
my ($self, $svc, $rateplan) = @_;
+ $self->{'__saisei_error'} = "No downrate listed for service $rateplan" if !$svc->{Hash}->{speed_down};
+ $self->{'__saisei_error'} = "No uprate listed for service $rateplan" if !$svc->{Hash}->{speed_up};
+
my $new_rateplan = $self->api_call(
"PUT",
"/rate_plans/$rateplan",
@@ -394,22 +489,26 @@ sub api_create_rateplan {
'downstream_rate' => $svc->{Hash}->{speed_down},
'upstream_rate' => $svc->{Hash}->{speed_up},
},
- );
+ ) unless $self->{'__saisei_error'};
$self->{'__saisei_error'} = "Rate Plan not created"
- unless $new_rateplan; # should never happen
+ unless ($new_rateplan || $self->{'__saisei_error'});
+
return $new_rateplan;
}
=head2 api_modify_rateplan
-Modify a rateplan.
+Modify a new rateplan.
=cut
sub api_modify_rateplan {
- my ($self,$policies,$svc,$rateplan_name) = @_;
+ my ($self,$svc,$rateplan_name) = @_;
+
+ # get policy list
+ my $policies = $self->api_get_policies();
foreach my $policy (@$policies) {
my $policyname = $policy->{name};
@@ -425,8 +524,8 @@ sub api_modify_rateplan {
},
);
- $self->{'__saisei_error'} = "Rate Plan not modified"
- unless $modified_rateplan; # should never happen
+ $self->{'__saisei_error'} = "Rate Plan not modified after create"
+ unless ($modified_rateplan || $self->{'__saisei_error'}); # should never happen
}
@@ -434,6 +533,31 @@ sub api_modify_rateplan {
}
+=head2 api_modify_existing_rateplan
+
+Modify a existing rateplan.
+
+=cut
+
+sub api_modify_existing_rateplan {
+ my ($self,$svc,$rateplan_name) = @_;
+
+ my $modified_rateplan = $self->api_call(
+ "PUT",
+ "/rate_plans/$rateplan_name",
+ {
+ 'downstream_rate' => $svc->{Hash}->{speed_down},
+ 'upstream_rate' => $svc->{Hash}->{speed_up},
+ },
+ );
+
+ $self->{'__saisei_error'} = "Rate Plan not modified"
+ unless ($modified_rateplan || $self->{'__saisei_error'}); # should never happen
+
+ return;
+
+}
+
=head2 api_create_user
Creates a user.
@@ -452,7 +576,7 @@ sub api_create_user {
);
$self->{'__saisei_error'} = "User not created"
- unless $new_user; # should never happen
+ unless ($new_user || $self->{'__saisei_error'}); # should never happen
return $new_user;
@@ -465,34 +589,34 @@ Creates a access point.
=cut
sub api_create_accesspoint {
- my ($self,$accesspoint, $uprate, $downrate) = @_;
+ my ($self,$accesspoint, $upratelimit, $downratelimit) = @_;
# this has not been tested, but should work, if needed.
my $new_accesspoint = $self->api_call(
"PUT",
"/access_points/$accesspoint",
{
- 'downstream_rate_limit' => $downrate,
- 'upstream_rate_limit' => $uprate,
+ 'downstream_rate_limit' => $downratelimit,
+ 'upstream_rate_limit' => $upratelimit,
},
);
$self->{'__saisei_error'} = "Access point not created"
- unless $new_accesspoint; # should never happen
+ unless ($new_accesspoint || $self->{'__saisei_error'}); # should never happen
return;
}
=head2 api_modify_accesspoint
-Modify a access point.
+Modify a new access point.
=cut
sub api_modify_accesspoint {
my ($self, $accesspoint, $uplink) = @_;
- my $modified_rateplan = $self->api_call(
+ my $modified_accesspoint = $self->api_call(
"PUT",
"/access_points/$accesspoint",
{
@@ -501,7 +625,33 @@ sub api_modify_accesspoint {
);
$self->{'__saisei_error'} = "Rate Plan not modified"
- unless $modified_rateplan; # should never happen
+ unless ($modified_accesspoint || $self->{'__saisei_error'}); # should never happen
+
+ return;
+
+}
+
+=head2 api_modify_existing_accesspoint
+
+Modify a existing accesspoint.
+
+=cut
+
+sub api_modify_existing_accesspoint {
+ my ($self, $accesspoint, $uplink, $upratelimit, $downratelimit) = @_;
+
+ my $modified_accesspoint = $self->api_call(
+ "PUT",
+ "/access_points/$accesspoint",
+ {
+ 'downstream_rate_limit' => $downratelimit,
+ 'upstream_rate_limit' => $upratelimit,
+# 'uplink' => $uplink, # name of attached access point
+ },
+ );
+
+ $self->{'__saisei_error'} = "Access point not modified"
+ unless ($modified_accesspoint || $self->{'__saisei_error'}); # should never happen
return;
@@ -527,7 +677,7 @@ sub api_add_host_to_user {
);
$self->{'__saisei_error'} = "Host not created"
- unless $new_host; # should never happen
+ unless ($new_host || $self->{'__saisei_error'}); # should never happen
return $new_host;
@@ -560,12 +710,114 @@ sub api_delete_host_to_user {
);
$self->{'__saisei_error'} = "Host not created"
- unless $delete_host; # should never happen
+ unless ($delete_host || $self->{'__saisei_error'}); # should never happen
return $delete_host;
}
+sub process_tower {
+ my ($self, $opt) = @_;
+
+ my $existing_tower_ap;
+ my $tower_name = $opt->{tower_name};
+
+ #check if tower has been set up as an access point.
+ $existing_tower_ap = $self->api_get_accesspoint($tower_name) unless $self->{'__saisei_error'};
+
+ # modify the existing accesspoint if changing tower .
+ $self->api_modify_existing_accesspoint (
+ $tower_name,
+ '', # tower does not have a uplink on sectors.
+ $opt->{tower_uprate_limit},
+ $opt->{tower_downrate_limit},
+ ) if $existing_tower_ap && $opt->{modify_existing};
+
+ #if tower does not exist as an access point create it.
+ $self->api_create_accesspoint(
+ $tower_name,
+ $opt->{tower_uprate_limit},
+ $opt->{tower_downrate_limit}
+ ) unless $existing_tower_ap;
+
+ my $accesspoint = $self->api_get_accesspoint($tower_name);
+
+ return $accesspoint;
+}
+
+sub process_sector {
+ my ($self, $opt) = @_;
+
+ my $existing_sector_ap;
+ my $sector_name = $opt->{sector_name};
+
+ #check if sector has been set up as an access point.
+ $existing_sector_ap = $self->api_get_accesspoint($sector_name);
+
+ # modify the existing accesspoint if changing sector .
+ $self->api_modify_existing_accesspoint (
+ $sector_name,
+ $opt->{tower_name},
+ $opt->{sector_uprate_limit},
+ $opt->{sector_downrate_limit},
+ ) if $existing_sector_ap && $opt->{modify_existing};
+
+ #if sector does not exist as an access point create it.
+ $self->api_create_accesspoint(
+ $sector_name,
+ $opt->{sector_uprate_limit},
+ $opt->{sector_downrate_limit},
+ ) unless $existing_sector_ap;
+
+ # Attach newly created sector to it's tower.
+ $self->api_modify_accesspoint($sector_name, $opt->{tower_name}) unless ($self->{'__saisei_error'} || $existing_sector_ap);
+
+ # set access point to existing one or newly created one.
+ my $accesspoint = $existing_sector_ap ? $existing_sector_ap : $self->api_get_accesspoint($sector_name);
+
+ return $accesspoint;
+}
+
+sub export_provisioned_services {
+ my $job = shift;
+ my $param = shift;
+
+ my $part_export = FS::Record::qsearchs('part_export', { 'exportnum' => $param->{export_provisioned_services_exportnum}, } )
+ or die "unknown exportnum $param->{export_provisioned_services_exportnum}";
+ bless $part_export;
+
+ my @svcparts = FS::Record::qsearch({
+ 'table' => 'export_svc',
+ 'addl_from' => 'LEFT JOIN part_svc USING ( svcpart ) ',
+ 'hashref' => { 'exportnum' => $param->{export_provisioned_services_exportnum}, },
+ });
+ my $part_count = scalar @svcparts;
+
+ my $parts = join "', '", map { $_->{Hash}->{svcpart} } @svcparts;
+
+ my @svcs = FS::Record::qsearch({
+ 'table' => 'cust_svc',
+ 'addl_from' => 'LEFT JOIN svc_broadband USING ( svcnum ) ',
+ 'extra_sql' => " WHERE svcpart in ('".$parts."')",
+ });
+
+ my $svc_count = scalar @svcs;
+
+ my %status = {};
+ for (my $c=10; $c <=100; $c=$c+10) { $status{int($svc_count * ($c/100))} = $c; }
+
+ my $process_count=0;
+ foreach my $svc (@svcs) {
+ if ($status{$process_count}) { my $s = $status{$process_count}; $job->update_statustext($s); }
+ ## check if service exists as host if not export it.
+ _export_insert($part_export,$svc) unless api_get_host($part_export, $svc->{Hash}->{ip_addr});
+ $process_count++;
+ }
+
+ return;
+
+}
+
=head1 SEE ALSO
L
diff --git a/FS/FS/part_svc.pm b/FS/FS/part_svc.pm
index dcc78435b..341559594 100644
--- a/FS/FS/part_svc.pm
+++ b/FS/FS/part_svc.pm
@@ -519,6 +519,18 @@ sub part_export_dsl_pull {
grep $_->can('dsl_pull'), $self->part_export;
}
+=item part_export_partsvc
+
+Returns a list of any exports (see L) for this service that
+are capable of pushing a change after part svc is changed.
+
+=cut
+
+sub part_export_partsvc {
+ my $self = shift;
+ grep $_->can('export_partsvc'), $self->part_export;
+}
+
=item cust_svc [ PKGPART ]
Returns a list of associated customer services (FS::cust_svc records).
@@ -909,6 +921,11 @@ sub process {
);
die "$error\n" if $error;
+
+ foreach my $part_svc_export ( $new->part_export_partsvc ) {
+ $error = $part_svc_export->export_partsvc($new);
+ }
+ return $error if $error;
}
=item process_bulk_cust_svc
diff --git a/FS/FS/tower.pm b/FS/FS/tower.pm
index 2221affb6..8a93d8f23 100644
--- a/FS/FS/tower.pm
+++ b/FS/FS/tower.pm
@@ -44,13 +44,13 @@ Tower name
Disabled flag, empty or 'Y'
-=item up_rate
+=item up_rate_limit
-Up Rate for towner
+Up Rate limit for towner
-=item down_rate
+=item down_rate_limit
-Down Rate for tower
+Down Rate limit for tower
=back
@@ -126,8 +126,8 @@ sub check {
|| $self->ut_floatn('height')
|| $self->ut_floatn('veg_height')
|| $self->ut_alphan('color')
- || $self->ut_numbern('up_rate')
- || $self->ut_numbern('down_rate')
+ || $self->ut_numbern('up_rate_limit')
+ || $self->ut_numbern('down_rate_limit')
;
return $error if $error;
diff --git a/FS/FS/tower_sector.pm b/FS/FS/tower_sector.pm
index 6e3104acd..800d4989a 100644
--- a/FS/FS/tower_sector.pm
+++ b/FS/FS/tower_sector.pm
@@ -99,13 +99,13 @@ The coordinate boundaries of the coverage map.
The sector title.
-=item up_rate
+=item up_rate_limit
-Up rate for sector.
+Up rate limit for sector.
-=item down_rate
+=item down_rate_limit
-down rate for sector.
+down rate limit for sector.
=back
@@ -260,8 +260,8 @@ sub check {
|| $self->ut_decimaln('antenna_gain')
|| $self->ut_numbern('hardware_typenum')
|| $self->ut_textn('title')
- || $self->ut_numbern('up_rate')
- || $self->ut_numbern('down_rate')
+ || $self->ut_numbern('up_rate_limit')
+ || $self->ut_numbern('down_rate_limit')
# all of these might get relocated as part of coverage refactoring
|| $self->ut_anything('image')
|| $self->ut_sfloatn('west')
@@ -379,6 +379,21 @@ sub part_export {
});
}
+=item part_export_svc_broadband
+
+Returns all svc_broadband exports.
+
+=cut
+
+sub part_export_svc_broadband {
+ my $info = $FS::part_export::exports{'svc_broadband'} or return;
+ my @exporttypes = map { dbh->quote($_) } keys %$info or return;
+ qsearch({
+ 'table' => 'part_export',
+ 'extra_sql' => 'WHERE exporttype IN(' . join(',', @exporttypes) . ')'
+ });
+}
+
=back
=head1 SUBROUTINES
diff --git a/httemplate/edit/part_export.cgi b/httemplate/edit/part_export.cgi
index 5411feb5f..381fbcaf8 100644
--- a/httemplate/edit/part_export.cgi
+++ b/httemplate/edit/part_export.cgi
@@ -290,6 +290,20 @@ my $widget = new HTML::Widgets::SelectLayers(
$html .= ' CHECKED' if $part_export->no_suspend eq 'Y';
$html .= '>';
+ foreach my $script ( keys %{$exports->{$layer}{scripts}} ) {
+ $html .= '
+ A credit card surcharge of <% $money_char. sprintf('%.2f', ($amount * $surcharge_percentage) + $surcharge_flatfee) %> is included in this payment
% }
-% if ( $fee ) {
+% if ($fee || $surcharge) {
% my @cust_payby = ();
% if ( $payby eq 'CARD' ) {
% @cust_payby = $cust_main->cust_payby('CARD','DCRD');
@@ -123,10 +138,30 @@
'onchange' => 'cust_payby_changed(this)',
&>
+
+
<% $cust_main->masked('stateid') || ' ' %>
-% if ( $FS::CurrentUser::CurrentUser->access_right('Unmask customer DL')) {
+% if (
+% $cust_main->stateid
+% && $FS::CurrentUser::CurrentUser->access_right('Unmask customer DL')
+% ) {
<& /elements/link-replace_element_text.html, {
target_id => 'stateid_span',
replace_text => $cust_main->stateid,
--
cgit v1.2.1
From 7b52b31205b01810da69c7e64cf8ad3806988757 Mon Sep 17 00:00:00 2001
From: Christopher Burger
Date: Mon, 18 Jun 2018 10:16:37 -0400
Subject: RT# 80175 - changed gateway selection to select either ACH or NULL
for echeck payments
---
FS/FS/agent.pm | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/FS/FS/agent.pm b/FS/FS/agent.pm
index e1d9ccf1d..8aff96a8d 100644
--- a/FS/FS/agent.pm
+++ b/FS/FS/agent.pm
@@ -295,7 +295,7 @@ sub payment_gateway {
}
my $cardtype_search = "AND ( cardtype IS NULL OR cardtype <> 'ACH')";
- $cardtype_search = "AND cardtype = 'ACH'" if $options{method} eq 'ECHECK';
+ $cardtype_search = "AND ( cardtype IS NULL OR cardtype = 'ACH' )" if $options{method} eq 'ECHECK';
my $override =
qsearchs({
--
cgit v1.2.1
From 7f751940088ed6dabcf6cbcd67993313296b21bc Mon Sep 17 00:00:00 2001
From: Christopher Burger
Date: Tue, 19 Jun 2018 13:08:59 -0400
Subject: RT# 34134 - readded config option and move to deprecated section
---
FS/FS/Conf.pm | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm
index 5c6c411b3..35932b8bd 100644
--- a/FS/FS/Conf.pm
+++ b/FS/FS/Conf.pm
@@ -2787,6 +2787,13 @@ and customer address. Include units.',
'type' => 'checkbox',
},
+ {
+ 'key' => 'manual_process-single_invoice_amount',
+ 'section' => 'deprecated',
+ 'description' => 'When entering manual credit card and ACH payments, amount will not autofill if the customer has more than one open invoice',
+ 'type' => 'checkbox',
+ },
+
{
'key' => 'manual_process-pkgpart',
'section' => 'payments',
--
cgit v1.2.1
From 5506c3aea73686da65f7caf2acbdca715cc6c1a5 Mon Sep 17 00:00:00 2001
From: Mitch Jackson
Date: Mon, 25 Jun 2018 14:07:52 -0500
Subject: RT# 30783 Add network block enumerating utils
---
FS/FS/IP_Mixin.pm | 28 ++++++++++++++++++---
FS/FS/addr_block.pm | 18 +++++++++++++-
FS/FS/svc_IP_Mixin.pm | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++-
3 files changed, 110 insertions(+), 5 deletions(-)
diff --git a/FS/FS/IP_Mixin.pm b/FS/FS/IP_Mixin.pm
index 3ec769313..2d69d9cc1 100644
--- a/FS/FS/IP_Mixin.pm
+++ b/FS/FS/IP_Mixin.pm
@@ -266,9 +266,10 @@ sub router {
=item used_addresses [ BLOCK ]
-Returns a list of all addresses (in BLOCK, or in all blocks)
-that are in use. If called as an instance method, excludes
-that instance from the search.
+Returns a list of all addresses that are in use by a service. If called as an
+instance method, excludes that instance from the search.
+
+Does not filter by block, will return ALL used addresses. ref:f197bdbaa1
=cut
@@ -283,6 +284,27 @@ sub _used_addresses {
die "$class->_used_addresses not implemented";
}
+=item used_addresses_in_block [ FS::addr_block ]
+
+Returns a list of all addresses in use within the given L
+
+=cut
+
+sub used_addresses_in_block {
+ my ($self, $block) = @_;
+
+ (
+ $block->ip_gateway ? $block->ip_gateway : (),
+ $block->NetAddr->broadcast->addr,
+ map { $_->_used_addresses_in_block($block, $self ) } @subclasses
+ );
+}
+
+sub _used_addresses_in_block {
+ my $class = shift;
+ die "$class->_used_addresses_in_block not implemented";
+}
+
=item is_used ADDRESS
Returns a string describing what object is using ADDRESS, or
diff --git a/FS/FS/addr_block.pm b/FS/FS/addr_block.pm
index ba0f61db1..fa0e42f62 100755
--- a/FS/FS/addr_block.pm
+++ b/FS/FS/addr_block.pm
@@ -207,6 +207,23 @@ sub cidr {
$self->NetAddr->cidr;
}
+=item free_addrs
+
+Returns a sorted list of free addresses in the block.
+
+=cut
+
+sub free_addrs {
+ my $self = shift;
+
+ my %used_addr_map =
+ map {$_ => 1}
+ FS::IP_Mixin->used_addresses_in_block($self),
+ FS::Conf->new()->config('exclude_ip_addr');
+
+ grep { !exists $used_addr_map{$_} } map { $_->addr } $self->NetAddr->hostenum;
+}
+
=item next_free_addr
Returns a NetAddr::IP object corresponding to the first unassigned address
@@ -416,4 +433,3 @@ now because that's the smallest block that makes any sense at all.
=cut
1;
-
diff --git a/FS/FS/svc_IP_Mixin.pm b/FS/FS/svc_IP_Mixin.pm
index c89245fe2..4266a2a5e 100644
--- a/FS/FS/svc_IP_Mixin.pm
+++ b/FS/FS/svc_IP_Mixin.pm
@@ -3,7 +3,8 @@ use base 'FS::IP_Mixin';
use strict;
use NEXT;
-use FS::Record qw(qsearchs qsearch);
+use Carp qw(croak carp);
+use FS::Record qw(qsearchs qsearch dbh);
use FS::Conf;
use FS::router;
use FS::part_svc_router;
@@ -90,6 +91,9 @@ sub svc_ip_check {
}
sub _used_addresses {
+
+ # Returns all addresses in use. Does not filter with $block. ref:f197bdbaa1
+
my ($class, $block, $exclude) = @_;
my $ip_field = $class->table_info->{'ip_field'}
or return ();
@@ -107,6 +111,69 @@ sub _used_addresses {
});
}
+sub _used_addresses_in_block {
+ my ($class, $block) = @_;
+
+ croak "_used_addresses_in_block() requires an FS::addr_block parameter"
+ unless ref $block && $block->isa('FS::addr_block');
+
+ my $ip_field = $class->table_info->{'ip_field'};
+ if ( !$ip_field ) {
+ carp "_used_addresses_in_block() skipped, no ip_field";
+ return;
+ }
+
+ my $block_na = $block->NetAddr;
+
+ my $octets;
+ if ($block->ip_netmask >= 24) {
+ $octets = 3;
+ } elsif ($block->ip_netmask >= 16) {
+ $octets = 2;
+ } elsif ($block->ip_netmask >= 8) {
+ $octets = 1;
+ }
+
+ # e.g.
+ # SELECT ip_addr
+ # FROM svc_broadband
+ # WHERE ip_addr != ''
+ # AND ip_addr != '0e0'
+ # AND ip_addr LIKE '10.0.2.%';
+ #
+ # For /24, /16 and /8 this approach is fast, even when svc_broadband table
+ # contains 650,000+ ip records. For other allocations, this approach is
+ # not speedy, but usable.
+ #
+ # Note: A use case like this would could greatly benefit from a qsearch()
+ # parameter to bypass FS::Record objects creation and just
+ # return hashrefs from DBI. 200,000 hashrefs are many seconds faster
+ # than 200,000 FS::Record objects
+ my %qsearch = (
+ table => $class->table,
+ select => $ip_field,
+ hashref => { $ip_field => { op => '!=', value => '' }},
+ extra_sql => " AND $ip_field != '0e0' ",
+ );
+ if ( $octets ) {
+ my $block_str = join('.', (split(/\D/, $block_na->first))[0..$octets-1]);
+ $qsearch{extra_sql} .= " AND $ip_field LIKE ".dbh->quote("${block_str}.%");
+ }
+
+ if ( $block->ip_netmask % 8 ) {
+ # Some addresses returned by qsearch may be outside the network block,
+ # so each ip address is tested to be in the block before it's returned.
+ return
+ grep { $block_na->contains( NetAddr::IP->new( $_ ) ) }
+ map { $_->$ip_field }
+ qsearch( \%qsearch );
+ }
+
+ return
+ map { $_->$ip_field }
+ qsearch( \%qsearch );
+}
+
sub _is_used {
my ($class, $addr, $exclude) = @_;
my $ip_field = $class->table_info->{'ip_field'}
--
cgit v1.2.1
From b79b0ebca0c15ad527de3d589cda36da63b0601e Mon Sep 17 00:00:00 2001
From: Mitch Jackson
Date: Mon, 25 Jun 2018 14:09:42 -0500
Subject: RT# 30783 Improve speed of ip address auto-assignment
---
FS/FS/addr_block.pm | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/FS/FS/addr_block.pm b/FS/FS/addr_block.pm
index fa0e42f62..eb84dafc3 100755
--- a/FS/FS/addr_block.pm
+++ b/FS/FS/addr_block.pm
@@ -250,7 +250,7 @@ sub next_free_addr {
$selfaddr->addr,
$selfaddr->network->addr,
$selfaddr->broadcast->addr,
- FS::IP_Mixin->used_addresses($self)
+ FS::IP_Mixin->used_addresses_in_block($self)
);
# just do a linear search of the block
--
cgit v1.2.1
From 7b56ddf363cbd15acab7e7098a7769e7a33640a3 Mon Sep 17 00:00:00 2001
From: Christopher Burger
Date: Tue, 26 Jun 2018 09:51:37 -0400
Subject: RT# 80175 - restored ability to set override to ACH only.
---
httemplate/edit/agent_payment_gateway.html | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/httemplate/edit/agent_payment_gateway.html b/httemplate/edit/agent_payment_gateway.html
index 6d15164ac..38411f12e 100644
--- a/httemplate/edit/agent_payment_gateway.html
+++ b/httemplate/edit/agent_payment_gateway.html
@@ -18,9 +18,12 @@ Use gateway
-
+
+
+ for ACH only.
+
+
--
cgit v1.2.1
From 211865a049969b1b539988cc8558ee35e8cb5945 Mon Sep 17 00:00:00 2001
From: Mitch Jackson
Date: Tue, 26 Jun 2018 17:34:50 -0500
Subject: RT# 30783 Selectbox of available IPs when provisioning
---
FS/FS/addr_block.pm | 8 +-
httemplate/elements/tr-select-router_block_ip.html | 128 +++++++++++++++++----
httemplate/json/free_addresses_in_block.json.html | 18 +++
3 files changed, 129 insertions(+), 25 deletions(-)
create mode 100644 httemplate/json/free_addresses_in_block.json.html
diff --git a/FS/FS/addr_block.pm b/FS/FS/addr_block.pm
index eb84dafc3..74c372e29 100755
--- a/FS/FS/addr_block.pm
+++ b/FS/FS/addr_block.pm
@@ -209,7 +209,7 @@ sub cidr {
=item free_addrs
-Returns a sorted list of free addresses in the block.
+Returns an aref sorted list of free addresses in the block.
=cut
@@ -221,7 +221,11 @@ sub free_addrs {
FS::IP_Mixin->used_addresses_in_block($self),
FS::Conf->new()->config('exclude_ip_addr');
- grep { !exists $used_addr_map{$_} } map { $_->addr } $self->NetAddr->hostenum;
+ [
+ grep { !exists $used_addr_map{$_} }
+ map { $_->addr }
+ $self->NetAddr->hostenum
+ ];
}
=item next_free_addr
diff --git a/httemplate/elements/tr-select-router_block_ip.html b/httemplate/elements/tr-select-router_block_ip.html
index 2aa715e29..535e953c4 100644
--- a/httemplate/elements/tr-select-router_block_ip.html
+++ b/httemplate/elements/tr-select-router_block_ip.html
@@ -2,34 +2,110 @@
var manual_addr_routernum = <% encode_json(\%manual_addr_routernum) %>;
var ip_addr_curr_value = <% $opt{'ip_addr'} |js_string %>;
var blocknum_curr_value = <% $opt{'blocknum'} |js_string %>;
-function update_ip_addr(obj, i) {
- var routernum = document.getElementById('router_select_0').value;
- var select_blocknum = document.getElementById('router_select_1');
- var blocknum = select_blocknum.value;
- var input_ip_addr = document.getElementById('input_ip_addr');
+
+function update_ip_addr() {
+ var routernum = $('#router_select_0').val();
+ var blocknum = $('#router_select_1').val();
+ var e_input_ip_addr = $('#input_ip_addr');
+ var e_router_select_1 = $('#router_select_1');
+
+ <% # Is block is automatically selected for this router? %>
if ( manual_addr_routernum[routernum] == 'Y' ) {
-%# hide block selection and default ip address to its previous value
- select_blocknum.style.display = 'none';
- input_ip_addr.value = ip_addr_curr_value;
- }
- else {
-%# the reverse
- select_blocknum.style.display = '';
-%# default ip address to null, unless the router/block are set to the
-%# previous value, in which case default it to current value
+ show_ip_input();
+ hide_ip_select();
+ e_router_select_1.hide();
+ e_input_ip_addr.val( ip_addr_curr_value );
+ } else {
+ e_router_select_1.show();
+ e_input_ip_addr.attr('placeholder', <% mt('(automatic)') | js_string %> );
if ( routernum == router_curr_values[0] &&
- blocknum == router_curr_values[1] ) {
- input_ip_addr.value = ip_addr_curr_value;
+ blocknum == router_curr_values[1] ) {
+ e_input_ip_addr.val( ip_addr_curr_value );
} else {
- input_ip_addr.value = <% mt('(automatic)') |js_string %>;
+ e_input_ip_addr.val('');
}
}
+ show_or_hide_toggle_ip();
+ populate_ip_select();
+}
+
+function toggle_ip_input() {
+ if ( $('#input_ip_addr').is(':hidden') ) {
+ show_ip_input();
+ } else {
+ show_ip_select();
+ }
+}
+
+function show_ip_input() {
+ $('#input_ip_addr').show();
+ $('#select_ip_addr').hide();
+ depopulate_ip_select();
+}
+
+function show_ip_select() {
+ var e_input_ip_addr = $('#input_ip_addr');
+ var e_select_ip_addr = $('#select_ip_addr');
+
+ e_select_ip_addr.width( e_input_ip_addr.width() );
+ e_input_ip_addr.hide();
+ e_select_ip_addr.show();
+ populate_ip_select();
+}
+
+function populate_ip_select() {
+ depopulate_ip_select();
+ var e = $('#select_ip_addr');
+ var blocknum = $('#router_select_1').val();
+
+ var opts = [ '' ];
+ e.html(opts.join(''));
+
+% if ( $opt{ip_addr} ) {
+ opts = [
+ '',
+ ''
+ ];
+% } else {
+ opts = [ '' ];
+% }
+ if ( blocknum && $.isNumeric(blocknum) && ! e.is(':hidden')) {
+ $.getJSON(
+ '<% $p %>json/free_addresses_in_block.json.html',
+ {blocknum: blocknum},
+ function(ip_json) {
+ $.each( ip_json, function(idx, val) {
+ opts.push(
+ ''
+ );
+ });
+ e.html(opts.join(''));
+ }
+ );
+ }
}
-function clearhint_ip_addr (what) {
- if ( what.value == <% mt('(automatic)') |js_string %> )
- what.value = '';
+
+function depopulate_ip_select() {
+ $('#select_ip_addr').children().remove();
}
+
+function propogate_ip_select() {
+ $('#input_ip_addr').val( $('#select_ip_addr').val() );
+}
+
+function show_or_hide_toggle_ip() {
+ if ( $('#router_select_1').val() ) {
+ $('#toggle_ip').show();
+ } else {
+ show_ip_input();
+ $('#toggle_ip').hide();
+ }
+}
+
+
<& /elements/tr-td-label.html, label => ($opt{'label'} || 'Router'), required => $opt{'required'} &>
+ Please ensure your version of Windows is up to date.
+
+
+ You are using the Microsoft Edge browser. In July, Microsoft fixed a
+ bug in Edge. The bug causes Edge to rarely send different data to
+ the server than the user selected.
+
+
+ Only Windows 10 computers that have not received Microsoft's
+ July RS4 Windows 10 Update
+
+ [*]
+ are affected.
+