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)') %> +

+ + +

+

+ + +

-- 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: +

+

    +
  1. 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
    +
  2. +

    +

  3. 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. +
  4. +

  5. 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. +
  6. +

    +

  7. +Create a package for the above created broadband service, and order this package for a customer. +
  8. +

    +

  9. +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. +

  10. +

-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 .= '' . + include('/elements/progress-init.html', + $part_export->exportname, + [ $script.'_exportnum', $script.'_script' ], + rooturl().'view/svc_export/run_script.cgi', + rooturl().'edit/part_export.cgi?'.$part_export->{Hash}->{exportnum}, + $script, + ) . + ' + + '.$exports->{$layer}{scripts}{$script}->{html_label}.''; + } + $html .= ''; # false laziness with config_element above diff --git a/httemplate/edit/process/elements/process.html b/httemplate/edit/process/elements/process.html index 76722c960..8c307f0b6 100644 --- a/httemplate/edit/process/elements/process.html +++ b/httemplate/edit/process/elements/process.html @@ -459,6 +459,14 @@ foreach my $value ( @values ) { } +if ($class eq "FS::tower") { + foreach my $part_svc_broadband_export ( FS::tower_sector->part_export_svc_broadband ) { + if ($part_svc_broadband_export and $part_svc_broadband_export->can('export_tower_sector')) { + $error = $part_svc_broadband_export->export_tower_sector($new); + } + } +} + # set up redirect URLs my $redirect; diff --git a/httemplate/edit/process/tower.html b/httemplate/edit/process/tower.html index f870d1237..8f62c4bec 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 up_rate down_rate + sector_range up_rate_limit down_rate_limit )], }, &> diff --git a/httemplate/edit/tower.html b/httemplate/edit/tower.html index 14f2dfa18..dfebc0031 100644 --- a/httemplate/edit/tower.html +++ b/httemplate/edit/tower.html @@ -13,8 +13,8 @@ 'altitude', 'height', 'veg_height', - 'up_rate', - 'down_rate', + 'up_rate_limit', + 'down_rate_limit', # { field => 'sectornum', # type => 'tower_sector', # o2m_table => 'tower_sector', @@ -37,8 +37,8 @@ 'height' => 'Tower height (feet)', 'veg_height' => 'Vegetation height (feet)', 'color' => 'Color', - 'up_rate' => 'Up Rate (Kbps)', - 'down_rate' => 'Down Rate (Kbps)', + 'up_rate_limit' => 'Up Rate Limit(Kbps)', + 'down_rate_limit' => 'Down Rate Limit(Kbps)', }, &> <%init> @@ -47,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 up_rate down_rate + sectorname ip_addr height freq_mhz direction width tilt v_width db_high db_low sector_range up_rate_limit down_rate_limit ); map { diff --git a/httemplate/elements/tr-tower_sectors.html b/httemplate/elements/tr-tower_sectors.html index 6843f4fdc..8acedb84b 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 up_rate down_rate + power line_loss antenna_gain hardware_typenum up_rate_limit down_rate_limit ); my @sectors; @@ -294,16 +294,16 @@ $(function() {

+ id="<% $id %>_up_rate_limit" + name="<% $id %>_up_rate_limit" + value="<% $sector->up_rate_limit |h %>">

+ id="<% $id %>_down_rate_limit" + name="<% $id %>_down_rate_limit" + value="<% $sector->down_rate_limit |h %>">

-- cgit v1.2.1 From fc7ed3676b8723339b3e756220d7af024c6c6191 Mon Sep 17 00:00:00 2001 From: Christopher Burger Date: Tue, 27 Mar 2018 11:44:56 -0400 Subject: RT# 79239 - cleaned up code --- FS/FS/part_pkg/sql_external.pm | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/FS/FS/part_pkg/sql_external.pm b/FS/FS/part_pkg/sql_external.pm index 75c7e132e..a3866f34e 100644 --- a/FS/FS/part_pkg/sql_external.pm +++ b/FS/FS/part_pkg/sql_external.pm @@ -146,8 +146,7 @@ sub calc_recur { sub cutoff_day { my( $self, $cust_pkg ) = @_; - my $error = SUPER->cutoff_day($cust_pkg); - #my $error = FS::part_pkg::flat::cutoff_day( $self, $cust_pkg )); + my $error = FS::part_pkg::flat::cutoff_day( $self, $cust_pkg ); return $error; } -- cgit v1.2.1 From e75717bd49c68a49940ebd668368a6542097a234 Mon Sep 17 00:00:00 2001 From: Christopher Burger Date: Tue, 27 Mar 2018 14:20:11 -0400 Subject: RT# 33362 - fixed Argument 1d is not numeric when trying to discount daily recuring packages --- FS/FS/part_pkg/discount_Mixin.pm | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/FS/FS/part_pkg/discount_Mixin.pm b/FS/FS/part_pkg/discount_Mixin.pm index 1e4653639..ec37624f2 100644 --- a/FS/FS/part_pkg/discount_Mixin.pm +++ b/FS/FS/part_pkg/discount_Mixin.pm @@ -102,7 +102,10 @@ sub calc_discount { # $chg_months: the number of months we are charging recur for # $months: $chg_months or the months left on the discount, whchever is less - my $chg_months = $cust_pkg->part_pkg->freq || 1; + my $chg_months = 1; + unless ($cust_pkg->part_pkg->freq !~ /^\d+$/) { + $chg_months = $cust_pkg->part_pkg->freq || 1; + } if ( defined($param->{'months'}) ) { # then override $chg_months = $param->{'months'}; } -- cgit v1.2.1 From 5dea8988e7a25387bb3a3fedff67b62851280016 Mon Sep 17 00:00:00 2001 From: Mitch Jackson Date: Wed, 28 Mar 2018 00:42:24 -0500 Subject: RT# 79636 Add conf flag invoice_sections_multilocation --- FS/FS/Conf.pm | 9 +++++++ FS/FS/Template_Mixin.pm | 67 ++++++++++++++++++++++++++++++++++++++++++++++--- FS/FS/cust_bill.pm | 9 ------- FS/FS/cust_bill_void.pm | 10 -------- 4 files changed, 73 insertions(+), 22 deletions(-) diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm index 721b7bdf9..9dd10a4c7 100644 --- a/FS/FS/Conf.pm +++ b/FS/FS/Conf.pm @@ -1583,6 +1583,15 @@ and customer address. Include units.', 'per_agent' => 1, }, + { + 'key' => 'invoice_sections_multilocation', + 'section' => 'invoicing', + 'description' => 'Enable invoice_sections for for any bill with at least this many locations on the bill.', + 'type' => 'text', + 'per_agent' => 1, + 'validate' => sub { shift =~ /^\d+$/ ? undef : 'Please enter a number' }, + }, + { 'key' => 'invoice_include_aging', 'section' => 'invoice_balances', diff --git a/FS/FS/Template_Mixin.pm b/FS/FS/Template_Mixin.pm index a1ed4b0c0..3d206db2c 100644 --- a/FS/FS/Template_Mixin.pm +++ b/FS/FS/Template_Mixin.pm @@ -19,7 +19,7 @@ use HTML::Entities; use Cwd; use FS::UID; use FS::Misc qw( send_email ); -use FS::Record qw( qsearch qsearchs ); +use FS::Record qw( qsearch qsearchs dbh ); use FS::Conf; use FS::Misc qw( generate_ps generate_pdf ); use FS::pkg_category; @@ -933,8 +933,6 @@ sub print_generic { my $unsquelched = $params{unsquelch_cdr} || $cust_main->squelch_cdr ne 'Y'; my $multisection = $self->has_sections; - $conf->exists($tc.'sections', $cust_main->agentnum) || - $conf->exists($tc.'sections_by_location', $cust_main->agentnum); $invoice_data{'multisection'} = $multisection; my $late_sections; my $extra_sections = []; @@ -3828,5 +3826,68 @@ sub _items_discounts_avail { } +=item has_sections AGENTNUM + +Return true if invoice_sections should be enabled for this bill. + (Inherited by both cust_bill and cust_bill_void) + +Determination: +* False if not an invoice +* True always if conf invoice_sections is enabled +* True always if sections_by_location is enabled +* True if conf invoice_sections_multilocation > 1, + and location_count >= invoice_sections_multilocation +* Else, False + +=cut + +sub has_sections { + my ($self, $agentnum) = @_; + + return 0 unless $self->invnum > 0; + + $agentnum ||= $self->cust_main->agentnum; + return 1 if $self->conf->exists('invoice_sections', $agentnum); + return 1 if $self->conf->exists('sections_by_location', $agentnum); + + my $location_min = $self->conf->config( + 'invoice_sections_multilocation', $agentnum, + ); + + return 1 + if $location_min + && $self->location_count >= $location_min; + + 0; +} + + +=item location_count + +Return the number of locations billed on this invoice + +=cut + +sub location_count { + my ($self) = @_; + return 0 unless $self->invnum; + + # SELECT COUNT( DISTINCT cust_pkg.locationnum ) + # FROM cust_bill_pkg + # LEFT JOIN cust_pkg USING (pkgnum) + # WHERE invnum = 278 + # AND cust_bill_pkg.pkgnum > 0 + + my $result = qsearchs({ + select => 'COUNT(DISTINCT cust_pkg.locationnum) as location_count', + table => 'cust_bill_pkg', + addl_from => 'LEFT JOIN cust_pkg USING (pkgnum)', + extra_sql => 'WHERE invnum = '.dbh->quote( $self->invnum ) + . ' AND cust_bill_pkg.pkgnum > 0' + }); + ref $result ? $result->location_count : 0; +} + + 1; diff --git a/FS/FS/cust_bill.pm b/FS/FS/cust_bill.pm index bd1b8bbec..942715b20 100644 --- a/FS/FS/cust_bill.pm +++ b/FS/FS/cust_bill.pm @@ -148,15 +148,6 @@ Invoices are normally created by calling the bill method of a customer object sub table { 'cust_bill'; } sub template_conf { 'invoice_'; } -sub has_sections { - my $self = shift; - my $agentnum = $self->cust_main->agentnum; - my $tc = $self->template_conf; - - $self->conf->exists($tc.'sections', $agentnum) || - $self->conf->exists($tc.'sections_by_location', $agentnum); -} - # should be the ONLY occurrence of "Invoice" in invoice rendering code. # (except email_subject and invnum_date_pretty) sub notice_name { diff --git a/FS/FS/cust_bill_void.pm b/FS/FS/cust_bill_void.pm index 50f69c9fa..43b295014 100644 --- a/FS/FS/cust_bill_void.pm +++ b/FS/FS/cust_bill_void.pm @@ -119,15 +119,6 @@ sub table { 'cust_bill_void'; } sub notice_name { 'VOIDED Invoice'; } sub template_conf { 'invoice_'; } -sub has_sections { - my $self = shift; - my $agentnum = $self->cust_main->agentnum; - my $tc = $self->template_conf; - - $self->conf->exists($tc.'sections', $agentnum) || - $self->conf->exists($tc.'sections_by_location', $agentnum); -} - =item insert @@ -375,4 +366,3 @@ L, schema.html from the base documentation. =cut 1; - -- cgit v1.2.1 From c128c075054a208e97a6815d5920892a0b75d33b Mon Sep 17 00:00:00 2001 From: Mitch Jackson Date: Wed, 28 Mar 2018 00:59:46 -0500 Subject: RT# 79636 Update label for conf flag invoice_sections --- FS/FS/Conf.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm index 9dd10a4c7..f3e2890a1 100644 --- a/FS/FS/Conf.pm +++ b/FS/FS/Conf.pm @@ -1578,7 +1578,7 @@ and customer address. Include units.', { 'key' => 'invoice_sections', 'section' => 'invoicing', - 'description' => 'Split invoice into sections and label according to package category when enabled.', + 'description' => 'Split invoice into sections and label according to either package category or location when enabled.', 'type' => 'checkbox', 'per_agent' => 1, }, -- cgit v1.2.1 From 983bf2cafbc04efe2368c67d88610ebfbbbae00a Mon Sep 17 00:00:00 2001 From: Christopher Burger Date: Thu, 29 Mar 2018 11:16:09 -0400 Subject: RT78356 - fixed exportname error added missing file --- httemplate/edit/part_export.cgi | 2 +- httemplate/view/svc_export/run_script.cgi | 31 +++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 httemplate/view/svc_export/run_script.cgi diff --git a/httemplate/edit/part_export.cgi b/httemplate/edit/part_export.cgi index 381fbcaf8..f6ec208be 100644 --- a/httemplate/edit/part_export.cgi +++ b/httemplate/edit/part_export.cgi @@ -293,7 +293,7 @@ my $widget = new HTML::Widgets::SelectLayers( foreach my $script ( keys %{$exports->{$layer}{scripts}} ) { $html .= '' . include('/elements/progress-init.html', - $part_export->exportname, + $part_export->exporttype, [ $script.'_exportnum', $script.'_script' ], rooturl().'view/svc_export/run_script.cgi', rooturl().'edit/part_export.cgi?'.$part_export->{Hash}->{exportnum}, diff --git a/httemplate/view/svc_export/run_script.cgi b/httemplate/view/svc_export/run_script.cgi new file mode 100644 index 000000000..ba58bbdd7 --- /dev/null +++ b/httemplate/view/svc_export/run_script.cgi @@ -0,0 +1,31 @@ +<% $server->process %> +<%init> + +my @args = $cgi->param('arg'); +my %param = (); + while ( @args ) { + my( $field, $value ) = splice(@args, 0, 2); + unless ( exists( $param{$field} ) ) { + $param{$field} = $value; + } elsif ( ! ref($param{$field}) ) { + $param{$field} = [ $param{$field}, $value ]; + } else { + push @{$param{$field}}, $value; + } + } + +my $exportnum; +my $method; +for (grep /^*_script$/, keys %param) { + $exportnum = $param{$param{$_}.'_exportnum'}; + $method = $param{$param{$_}.'_script'}; +} + +my $part_export = qsearchs('part_export', { 'exportnum'=> $exportnum, } ) + or die "unknown exportnum $exportnum"; + +my $class = 'FS::part_export::'.$part_export->{Hash}->{exporttype}.'::'.$method; + +my $server = new FS::UI::Web::JSRPC $class, $cgi; + + \ No newline at end of file -- cgit v1.2.1 From 1586961f12c742e45a38bc9cea0fcc49d5b1dd2a Mon Sep 17 00:00:00 2001 From: Christopher Burger Date: Thu, 29 Mar 2018 11:54:44 -0400 Subject: RT# 78356 - fixed error where no export existed. --- FS/FS/part_export/saisei.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FS/FS/part_export/saisei.pm b/FS/FS/part_export/saisei.pm index 1c95081bd..922a347b6 100644 --- a/FS/FS/part_export/saisei.pm +++ b/FS/FS/part_export/saisei.pm @@ -799,7 +799,7 @@ sub export_provisioned_services { 'table' => 'cust_svc', 'addl_from' => 'LEFT JOIN svc_broadband USING ( svcnum ) ', 'extra_sql' => " WHERE svcpart in ('".$parts."')", - }); + }) unless !$parts; my $svc_count = scalar @svcs; -- cgit v1.2.1 From 6981e02e3ba3c08a39faffd09f4a93d680b916ee Mon Sep 17 00:00:00 2001 From: Christopher Burger Date: Tue, 3 Apr 2018 12:04:43 -0400 Subject: RT 37817 - created new billing event condition, invoice has not been sent. --- .../Condition/invoice_has_not_been_sent.pm | 41 ++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 FS/FS/part_event/Condition/invoice_has_not_been_sent.pm diff --git a/FS/FS/part_event/Condition/invoice_has_not_been_sent.pm b/FS/FS/part_event/Condition/invoice_has_not_been_sent.pm new file mode 100644 index 000000000..882762dfe --- /dev/null +++ b/FS/FS/part_event/Condition/invoice_has_not_been_sent.pm @@ -0,0 +1,41 @@ +package FS::part_event::Condition::invoice_has_not_been_sent; + +use strict; +use FS::Record qw( qsearchs ); +use FS::cust_bill; +use Time::Local 'timelocal'; + +use base qw( FS::part_event::Condition ); + +sub description { + 'Invoice has not been sent previously'; +} + +sub eventtable_hashref { + { 'cust_main' => 0, + 'cust_bill' => 1, + 'cust_pkg' => 0, + }; +} + +sub condition { + my($self, $cust_bill, %opt) = @_; + + my $event = qsearchs( { + 'table' => 'cust_event', + 'addl_from' => 'LEFT JOIN part_event USING ( eventpart )', + 'hashref' => { + 'tablenum' => $cust_bill->{Hash}->{invnum}, + 'eventtable' => 'cust_bill', + 'status' => 'done', + }, + 'order_by' => " LIMIT 1", + } ); + + return 0 if $event; + + 1; + +} + +1; \ No newline at end of file -- cgit v1.2.1 From 11bbf29de447fe39e9d7155fe280a0df70fa8c3c Mon Sep 17 00:00:00 2001 From: Ivan Kohler Date: Wed, 4 Apr 2018 12:49:49 -0700 Subject: invoice_sections_with_taxes per-agent, RT#79636 --- FS/FS/Conf.pm | 3 +++ FS/FS/Template_Mixin.pm | 12 ++++++------ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm index f3e2890a1..0a29075d2 100644 --- a/FS/FS/Conf.pm +++ b/FS/FS/Conf.pm @@ -1581,6 +1581,7 @@ and customer address. Include units.', 'description' => 'Split invoice into sections and label according to either package category or location when enabled.', 'type' => 'checkbox', 'per_agent' => 1, + 'config_bool' => 1, }, { @@ -1612,6 +1613,8 @@ and customer address. Include units.', 'section' => 'invoicing', 'description' => 'Include taxes within each section of mutli-section invoices.', 'type' => 'checkbox', + 'per_agent' => 1, + 'agent_bool' => 1, }, { diff --git a/FS/FS/Template_Mixin.pm b/FS/FS/Template_Mixin.pm index 3d206db2c..f1db477f2 100644 --- a/FS/FS/Template_Mixin.pm +++ b/FS/FS/Template_Mixin.pm @@ -299,7 +299,7 @@ before that line item (quotations only) =item template -Dprecated. Used as a suffix for a configuration template. Please +Deprecated. Used as a suffix for a configuration template. Please don't use this, it deprecated in favor of more flexible alternatives. =back @@ -1195,7 +1195,7 @@ sub print_generic { my %options = (); $options{'section'} = $section if $multisection; $options{'section_with_taxes'} = 1 - if $conf->exists('invoice_sections_with_taxes'); + if $conf->config_bool('invoice_sections_with_taxes', $cust_main->agentnum); $options{'format'} = $format; $options{'escape_function'} = $escape_function; $options{'no_usage'} = 1 unless $unsquelched; @@ -1384,7 +1384,7 @@ sub print_generic { $tax_section->{'description'} = $self->mt($tax_description); $tax_section->{'summarized'} = ''; - if ( $conf->exists('invoice_sections_with_taxes')) { + if ( $conf->config_bool('invoice_sections_with_taxes', $cust_main->agentnum) ) { # remove tax section if taxes are itemized within other sections @sections = grep{ $_ ne $tax_section } @sections; @@ -2038,7 +2038,7 @@ sub balance_due_msg { my $msg = $self->mt('Balance Due'); return $msg unless $self->terms; # huh? if ( !$self->conf->exists('invoice_show_prior_due_date') - or $self->conf->exists('invoice_sections') ) { + || $self->has_sections ) { # if enabled, the due date is shown with Total New Charges (see # _items_total) and not here # (yes, or if invoice_sections is enabled; this is just for compatibility) @@ -3846,8 +3846,8 @@ sub has_sections { return 0 unless $self->invnum > 0; - $agentnum ||= $self->cust_main->agentnum; - return 1 if $self->conf->exists('invoice_sections', $agentnum); + $agentnum ||= $self->agentnum; + return 1 if $self->conf->config_bool('invoice_sections', $agentnum); return 1 if $self->conf->exists('sections_by_location', $agentnum); my $location_min = $self->conf->config( -- cgit v1.2.1 From f586fedb42ee93caf7923147c9bc70e0331421dd Mon Sep 17 00:00:00 2001 From: Christopher Burger Date: Thu, 5 Apr 2018 09:52:42 -0400 Subject: RT# 80114 - added the population of startdate and billsec from ani cdr --- FS/FS/cdr/ani_networks.pm | 62 +++++++++++++++++++++++------------------------ 1 file changed, 30 insertions(+), 32 deletions(-) diff --git a/FS/FS/cdr/ani_networks.pm b/FS/FS/cdr/ani_networks.pm index cac30c488..b00ea1360 100644 --- a/FS/FS/cdr/ani_networks.pm +++ b/FS/FS/cdr/ani_networks.pm @@ -37,42 +37,40 @@ use Time::Local; terminating_ocn:4:208:211 )], 'import_fields' => [ - - sub { #call_date and time + sub { #call_date and time my($cdr, $data, $conf, $param) = @_; $data =~ /^(\d\d\d\d)(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)$/ or die "unparsable record_date: $data"; $cdr->set('calldate', "$2/$3/$1 $4:$5:$6"); + $cdr->set('startdate', "$2/$3/$1 $4:$5:$6"); }, - - 'charged_party', #bill to number - '', #translate number - - 'src', #originating number - - '', #originating lata - '', #originating city - '', #originating state - '', #originating country - - 'dst', #terminating number - - '', #terminating lata - '', #terminating city - '', #terminating state - '', #terminating city code - '', #terminating country - - '', #call type - '', #call transport - 'accountcode', #account code - '', #info digits - 'duration', #duration - '', #wholesale amount - '', #cic - 'src_lrn', #originating lrn - 'dst_lrn', #terminating lrn - '', #originating ocn - '', #terminating ocn + 'charged_party', #bill to number + '', #translate number + 'src', #originating number + '', #originating lata + '', #originating city + '', #originating state + '', #originating country + 'dst', #terminating number + '', #terminating lata + '', #terminating city + '', #terminating state + '', #terminating city code + '', #terminating country + '', #call type + '', #call transport + 'accountcode', #account code + '', #info digits + sub { #duration + my($cdr, $field) = @_; + $cdr->set(duration => $field); + $cdr->set(billsec => $field); + }, + '', #wholesale amount + '', #cic + 'src_lrn', #originating lrn + 'dst_lrn', #terminating lrn + '', #originating ocn + '', #terminating ocn ], -- cgit v1.2.1 From 49ff577b9fbb4b017f0652cdfe567b023d7ea4de Mon Sep 17 00:00:00 2001 From: Christopher Burger Date: Tue, 10 Apr 2018 09:18:29 -0400 Subject: RT#39115 - added a optional display name for oid --- FS/FS/part_export/broadband_snmp_get.pm | 7 ++++++- .../edit/elements/part_export/broadband_snmp_get.html | 13 +++++++++---- httemplate/elements/broadband_snmp_get.html | 5 ++++- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/FS/FS/part_export/broadband_snmp_get.pm b/FS/FS/part_export/broadband_snmp_get.pm index 1a8661286..35dcd3154 100644 --- a/FS/FS/part_export/broadband_snmp_get.pm +++ b/FS/FS/part_export/broadband_snmp_get.pm @@ -21,6 +21,7 @@ tie my %options, 'Tie::IxHash', 'snmp_community' => { 'label'=>'Community', 'default'=>'public' }, 'snmp_timeout' => { label=>'Timeout (seconds)', 'default'=>1 }, 'snmp_oid' => { label=>'Object ID', multiple=>1 }, + 'snmp_oid_name' => { label=>'Object Name', multiple=>1 }, ; %info = ( @@ -80,6 +81,7 @@ sub snmp_results { my $vers = $self->option('snmp_version'); my $time = ($self->option('snmp_timeout') || 1) * 1000000; my @oids = split("\n", $self->option('snmp_oid')); + my @oidnames = split("\n", $self->option('snmp_oid_name')); my %connect = ( 'DestHost' => $host, 'Community' => $comm, @@ -90,7 +92,9 @@ sub snmp_results { return { 'error' => 'Error creating SNMP session' } unless $snmp; return { 'error' => $snmp->{'ErrorStr'} } if $snmp->{'ErrorStr'}; my @out; - foreach my $oid (@oids) { + for (my $i=0; $i <= $#oids; $i++) { + my $oid = $oids[$i]; + my $oidname = $oidnames[$i]; $oid = $SNMP::MIB{$oid}->{'objectID'} if $SNMP::MIB{$oid}; my @values; if ($vers eq '1') { @@ -115,6 +119,7 @@ sub snmp_results { next; } my %result = map { $_ => $SNMP::MIB{$oid}{$_} } qw( objectID label ); + $result{'name'} = $oidname; # unbless @values, for ease of JSON encoding $result{'values'} = []; foreach my $value (@values) { diff --git a/httemplate/edit/elements/part_export/broadband_snmp_get.html b/httemplate/edit/elements/part_export/broadband_snmp_get.html index faf179acc..a426e617d 100644 --- a/httemplate/edit/elements/part_export/broadband_snmp_get.html +++ b/httemplate/edit/elements/part_export/broadband_snmp_get.html @@ -45,8 +45,11 @@ function receive_mib_get(obj, rownum) { - + + <& /elements/auto-table.html, template_row => 'broadband_snmp_get_template', - fieldorder => ['oid'], + fieldorder => ['oid_name','oid'], data => \@data, table => 'snmp', &> - + <& foot.html, %opt &> <%init> my %opt = @_; @@ -73,11 +76,13 @@ foreach my $field ( qw(snmp_version snmp_community snmp_timeout) ) { } my @oids = split("\n", $part_export->option('snmp_oid')); +my @oid_names = split("\n", $part_export->option('snmp_oid_name')); my @data; while (@oids) { my @thisrow = (shift(@oids)); - push @data, \@thisrow if grep length($_), @thisrow; + my $name = shift(@oid_names); + push @data, [$name, \@thisrow] if grep length($_), @thisrow; } my $popup_name = 'popup-'.time."-$$-".rand() * 2**32; diff --git a/httemplate/elements/broadband_snmp_get.html b/httemplate/elements/broadband_snmp_get.html index 213bc4460..1164504ee 100644 --- a/httemplate/elements/broadband_snmp_get.html +++ b/httemplate/elements/broadband_snmp_get.html @@ -26,7 +26,7 @@ function broadband_snmp_get (svcnum) { if (obj.error) { var row = document.createElement('tr'); var cell = document.createElement('td'); - cell.colSpan = '2'; + cell.colSpan = '3'; cell.innerHTML = obj['error']; row.appendChild(cell); table.appendChild(row); @@ -36,6 +36,9 @@ function broadband_snmp_get (svcnum) { var value = obj['values'][j]; var label = (obj['values'].length > 1) ? (value[0] + '.' + value[1]) : obj['label']; var cell = document.createElement('td'); + cell.innerHTML = obj['name']; + row.appendChild(cell); + cell = document.createElement('td'); cell.innerHTML = label; row.appendChild(cell); cell = document.createElement('td'); -- cgit v1.2.1 From edfb47cb5040a910c81329c33d02356933dae8fb Mon Sep 17 00:00:00 2001 From: Ivan Kohler Date: Tue, 10 Apr 2018 16:03:00 -0700 Subject: invoice_usesummary is already agent virt, just need to be able to set it, RT#79636 --- FS/FS/Conf.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm index 0a29075d2..9f234da11 100644 --- a/FS/FS/Conf.pm +++ b/FS/FS/Conf.pm @@ -1215,6 +1215,7 @@ my $validate_email = sub { $_[0] =~ 'section' => 'invoicing', 'description' => 'Indicates that html and latex invoices should be in summary style and make use of invoice_latexsummary.', 'type' => 'checkbox', + 'per_agent' => 1, }, { -- cgit v1.2.1 From 35ff521568f17c86d58c19c2cd945cf4b93d46a1 Mon Sep 17 00:00:00 2001 From: Ivan Kohler Date: Tue, 10 Apr 2018 16:07:11 -0700 Subject: and turn it back off as an agent override, RT#79636 --- FS/FS/Template_Mixin.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FS/FS/Template_Mixin.pm b/FS/FS/Template_Mixin.pm index f1db477f2..8bfc51cf1 100644 --- a/FS/FS/Template_Mixin.pm +++ b/FS/FS/Template_Mixin.pm @@ -825,7 +825,7 @@ sub print_generic { ); } - if ( $conf->exists('invoice_usesummary', $agentnum) ) { + if ( $conf->config_bool('invoice_usesummary', $agentnum) ) { $invoice_data{'summarypage'} = $summarypage = 1; } -- cgit v1.2.1 From 50434de151ec3d79f6b397d0f0714b58885e80ec Mon Sep 17 00:00:00 2001 From: Ivan Kohler Date: Tue, 10 Apr 2018 17:54:43 -0700 Subject: rendering time with lots of locations, RT#80177 --- FS/FS/Template_Mixin.pm | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/FS/FS/Template_Mixin.pm b/FS/FS/Template_Mixin.pm index 8bfc51cf1..2194a3fa5 100644 --- a/FS/FS/Template_Mixin.pm +++ b/FS/FS/Template_Mixin.pm @@ -3284,6 +3284,8 @@ sub _items_cust_bill_pkg { my $cust_main = $self->cust_main;#for per-agent cust_bill-line_item-ate_style + my $agentnum = $self->agentnum; + # for location labels: use default location on the invoice date my $default_locationnum; if ( $conf->exists('invoice-all_pkg_addresses') ) { @@ -3433,7 +3435,7 @@ sub _items_cust_bill_pkg { || $cust_bill_pkg->recur_show_zero; $description .= $cust_bill_pkg->time_period_pretty( $part_pkg, - $self->agentnum ) + $agentnum ) if $part_pkg->is_prepaid #for prepaid, "display the validity period # triggered by the recurring charge freq # (RT#26274) @@ -3530,10 +3532,8 @@ sub _items_cust_bill_pkg { $description = $self->mt('Usage charges'); } - my $part_pkg = $cust_pkg->part_pkg; - $description .= $cust_bill_pkg->time_period_pretty( $part_pkg, - $self->agentnum ); + $agentnum ); my @d = (); my @seconds = (); # for display of usage info -- cgit v1.2.1 From c6af881c09662e932da6d965887f5be27fbcc158 Mon Sep 17 00:00:00 2001 From: Ivan Kohler Date: Tue, 10 Apr 2018 18:11:00 -0700 Subject: optimize invoice rendering, RT#80177 --- FS/FS/TemplateItem_Mixin.pm | 5 ++--- FS/FS/Template_Mixin.pm | 24 ++++++++++++++++++++---- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/FS/FS/TemplateItem_Mixin.pm b/FS/FS/TemplateItem_Mixin.pm index 28fbd591d..28ef845c9 100644 --- a/FS/FS/TemplateItem_Mixin.pm +++ b/FS/FS/TemplateItem_Mixin.pm @@ -107,14 +107,13 @@ Returns a formatted time period for this line item. =cut sub time_period_pretty { - my( $self, $part_pkg, $agentnum ) = @_; + my( $self, $part_pkg, $agentnum, %opt ) = @_; #more efficient to look some of this conf stuff up outside the # invoice/template display loop we're called from # (Template_Mixin::_invoice_cust_bill_pkg) and pass them in as options - return '' if $conf->exists('disable_line_item_date_ranges') - || $part_pkg->option('disable_line_item_date_ranges',1) + return '' if $opt{'disable_line_item_date_ranges'} || ! $self->sdate || ! $self->edate; diff --git a/FS/FS/Template_Mixin.pm b/FS/FS/Template_Mixin.pm index 2194a3fa5..551d53fc4 100644 --- a/FS/FS/Template_Mixin.pm +++ b/FS/FS/Template_Mixin.pm @@ -1204,6 +1204,8 @@ sub print_generic { $options{'skip_usage'} = scalar(@$extra_sections) && !grep{$section == $_} @$extra_sections; $options{'preref_callback'} = $params{'preref_callback'}; + $options{'disable_line_item_date_ranges'} = + $conf->exists('disable_line_item_date_ranges'); warn "$me searching for line items\n" if $DEBUG > 1; @@ -3434,8 +3436,15 @@ sub _items_cust_bill_pkg { || ($discount_show_always and $cust_bill_pkg->unitrecur > 0) || $cust_bill_pkg->recur_show_zero; - $description .= $cust_bill_pkg->time_period_pretty( $part_pkg, - $agentnum ) + my $disable_date_ranges = + $opt{disable_line_item_date_ranges} + || $part_pkg->option('disable_line_item_date_ranges', 1); + + $description .= $cust_bill_pkg->time_period_pretty( + $part_pkg, + $agentnum, + disable_date_ranges => $disable_date_ranges, + ) if $part_pkg->is_prepaid #for prepaid, "display the validity period # triggered by the recurring charge freq # (RT#26274) @@ -3532,8 +3541,15 @@ sub _items_cust_bill_pkg { $description = $self->mt('Usage charges'); } - $description .= $cust_bill_pkg->time_period_pretty( $part_pkg, - $agentnum ); + my $disable_date_ranges = + $opt{disable_line_item_date_ranges} + || $part_pkg->option('disable_line_item_date_ranges', 1); + + $description .= $cust_bill_pkg->time_period_pretty( + $part_pkg, + $agentnum, + disable_date_ranges => $disable_date_ranges, + ); my @d = (); my @seconds = (); # for display of usage info -- cgit v1.2.1 From c0f84de8067f9cd98bdd3cfd593427c90123b494 Mon Sep 17 00:00:00 2001 From: Christopher Burger Date: Wed, 11 Apr 2018 07:51:23 -0400 Subject: RT# 75680 - Added date parse, and display insert error. --- FS/FS/cdr/telapi_voip.pm | 6 ++++-- FS/bin/freeside-cdr-telapi-import | 9 ++++++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/FS/FS/cdr/telapi_voip.pm b/FS/FS/cdr/telapi_voip.pm index 65aed7666..abc7d5bd2 100644 --- a/FS/FS/cdr/telapi_voip.pm +++ b/FS/FS/cdr/telapi_voip.pm @@ -26,8 +26,10 @@ use FS::cdr qw( _cdr_date_parser_maker _cdr_min_parser_maker ); my($cdr, $cdrtypename, $conf, $param) = @_; return unless length($cdrtypename); _init_cdr_types(); - die "no matching cdrtypenum for $cdrtypename" - unless defined $CDR_TYPES->{$cdrtypename}; + unless (defined $CDR_TYPES->{$cdrtypename}) { + warn "Skipping Record: CDR type name $cdrtypename does not exist!"; + $param->{skiprow} = 1; + } $cdr->cdrtypenum($CDR_TYPES->{$cdrtypename}); }, # type _cdr_min_parser_maker('billsec'), #PriceDurationMins diff --git a/FS/bin/freeside-cdr-telapi-import b/FS/bin/freeside-cdr-telapi-import index 4a637f57f..6bb3e4a36 100755 --- a/FS/bin/freeside-cdr-telapi-import +++ b/FS/bin/freeside-cdr-telapi-import @@ -35,6 +35,11 @@ GetOptions( "enddate=s" => \$enddate, ); +$startdate = str2time($startdate) or die "can't parse start date $startdate\n"; + $startdate = time2str('%m-%d-%Y', $startdate); +$enddate = str2time($enddate) or die "can't parse start date $enddate\n"; + $enddate = time2str('%m-%d-%Y', $enddate); + my $fsuser = $ARGV[-1]; die usage() unless $fsuser; @@ -65,11 +70,13 @@ print $cfh $page; seek($cfh,0,0); - print "Importing batch $cdrbatch\n"; + warn "Importing batch $cdrbatch\n"; my $error = FS::cdr::batch_import({ 'batch_namevalue' => $cdrbatch, 'file' => $cfh->filename, 'format' => 'telapi_'.$type }); + warn "Error importing CDR's\n".$error if $error; + exit; \ No newline at end of file -- cgit v1.2.1 From 1d966768ea430d102aac2dfbedc7cc6b503b7e1a Mon Sep 17 00:00:00 2001 From: Ivan Kohler Date: Wed, 11 Apr 2018 13:26:07 -0700 Subject: optimize invoice display with sections, RT#80177 --- FS/FS/Template_Mixin.pm | 12 ++++++++++-- FS/FS/cust_bill.pm | 8 +++++++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/FS/FS/Template_Mixin.pm b/FS/FS/Template_Mixin.pm index 551d53fc4..6c1e30e4a 100644 --- a/FS/FS/Template_Mixin.pm +++ b/FS/FS/Template_Mixin.pm @@ -2676,7 +2676,13 @@ sub _items_sections { foreach my $display ($cust_bill_pkg->cust_bill_pkg_display) { next if ( $display->summary && $opt{summary} ); - my $section = $display->section; + #my $section = $display->section; + #false laziness with the method, but for efficiency inside this loop + my $section = $display->get('section'); + if ( !$section && !$cust_bill_pkg->hidden ) { + $section = $cust_bill_pkg->get('categoryname'); #cust_bill->cust_bill_pkg added it (XXX quotations / quotation_section) + } + my $type = $display->type; # Set $section = undef if we're sectioning by location and this # line item _has_ a location (i.e. isn't a fee). @@ -3101,6 +3107,8 @@ sub _items_fee { my @cust_bill_pkg = grep { $_->feepart } $self->cust_bill_pkg; my $escape_function = $options{escape_function}; + my $locale = $self->cust_main->locale; + my @items; foreach my $cust_bill_pkg (@cust_bill_pkg) { # cache this, so we don't look it up again in every section @@ -3141,7 +3149,7 @@ sub _items_fee { $self->mt('from invoice #[_1] on [_2]', $_, $base_invnums{$_}) ); } - my $desc = $part_fee->itemdesc_locale($self->cust_main->locale); + my $desc = $part_fee->itemdesc_locale($locale); # but not escape the base description line my @pkg_tax = $cust_bill_pkg->_pkg_tax_list diff --git a/FS/FS/cust_bill.pm b/FS/FS/cust_bill.pm index 942715b20..38b1d5c6d 100644 --- a/FS/FS/cust_bill.pm +++ b/FS/FS/cust_bill.pm @@ -521,7 +521,13 @@ Returns the line items (see L) for this invoice. sub cust_bill_pkg { my $self = shift; qsearch( - { 'table' => 'cust_bill_pkg', + { + 'select' => 'cust_bill_pkg.*, pkg_category.categoryname', + 'table' => 'cust_bill_pkg', + 'addl_from' => ' LEFT JOIN cust_pkg USING ( pkgnum ) '. + ' LEFT JOIN part_pkg USING ( pkgpart ) '. + ' LEFT JOIN pkg_class USING ( classnum ) '. + ' LEFT JOIN pkg_category USING ( categorynum ) ', 'hashref' => { 'invnum' => $self->invnum }, 'order_by' => 'ORDER BY billpkgnum', #important? otherwise we could use # the AUTLOADED FK search. or should -- cgit v1.2.1 From 574262f14977b5acab2fc09fa2aaaa74eefb210b Mon Sep 17 00:00:00 2001 From: Ivan Kohler Date: Wed, 11 Apr 2018 16:54:54 -0700 Subject: fix deployment zone error when there are no blocks yet, RT#78339, github-pr#66, thanks to sushrutp for the patch --- FS/FS/deploy_zone.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/FS/FS/deploy_zone.pm b/FS/FS/deploy_zone.pm index efa36610c..306b4fb44 100644 --- a/FS/FS/deploy_zone.pm +++ b/FS/FS/deploy_zone.pm @@ -418,6 +418,7 @@ sub process_block_lookup { die $response->status_line unless $response->is_success; $data = decode_json($response->content); die $data->{error}{message} if $data->{error}; + last unless scalar @{$data->{features}}; #Nothing to insert foreach my $feature (@{ $data->{features} }) { my $geoid = $feature->{attributes}{GEOID}; # the prize -- cgit v1.2.1 From 56501725dadae47e0592a7d0652afce526784b60 Mon Sep 17 00:00:00 2001 From: Christopher Burger Date: Thu, 12 Apr 2018 14:08:26 -0400 Subject: RT# 80175 - readded the ability for payment gateway overrides to have an option to be for just ACH --- FS/FS/agent.pm | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/FS/FS/agent.pm b/FS/FS/agent.pm index e70b9716a..bf389deac 100644 --- a/FS/FS/agent.pm +++ b/FS/FS/agent.pm @@ -294,7 +294,13 @@ sub payment_gateway { } } - my $override = qsearchs('agent_payment_gateway', { agentnum => $self->agentnum } ); + my $cardtype = ''; + if ( $options{method} eq 'ECHECK' ) { $cardtype = 'ACH'; } + + my $override = + qsearchs('agent_payment_gateway', { agentnum => $self->agentnum, + cardtype => $cardtype, } ) + || qsearchs('agent_payment_gateway', { agentnum => $self->agentnum } ); my $payment_gateway = FS::payment_gateway->by_key_or_default( gatewaynum => $override ? $override->gatewaynum : '', -- cgit v1.2.1 From 304cf07be79b45930dcd5cb49cdf24aceaa3ebd9 Mon Sep 17 00:00:00 2001 From: Mitch Jackson Date: Sat, 14 Apr 2018 12:25:26 -0500 Subject: RT# 78190 Remove debug statements --- FS/FS/cust_bill.pm | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/FS/FS/cust_bill.pm b/FS/FS/cust_bill.pm index 38b1d5c6d..fe3867449 100644 --- a/FS/FS/cust_bill.pm +++ b/FS/FS/cust_bill.pm @@ -3243,7 +3243,6 @@ sub _items_payments { if ($self->conf->exists('previous_balance-payments_since')) { if ($template eq 'statement') { -print "\nCASE 3\n"; # Case 3 (see above) # Return payments timestamped between the previous and following bills @@ -3267,7 +3266,7 @@ print "\nCASE 3\n"; } else { # Case 2 (see above) # Return payments timestamped between this and the previous bill -print "\nCASE 2\n"; + my $date_start = 0; my $date_end = $self->_date; -- cgit v1.2.1 From 302e2522922019c8a1c6251188066ad10ae07bdc Mon Sep 17 00:00:00 2001 From: Mitch Jackson Date: Sat, 14 Apr 2018 12:41:30 -0500 Subject: RT# 78190 Update totals for latex summary --- conf/invoice_latexsummary | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/conf/invoice_latexsummary b/conf/invoice_latexsummary index bd4ea6975..52868419b 100644 --- a/conf/invoice_latexsummary +++ b/conf/invoice_latexsummary @@ -14,7 +14,7 @@ \textbf{Previous Balance}&\textbf{\dollar[@-- $true_previous_balance --@]}\\ \textbf{Payments}&\textbf{\dollar[@-- $balance_adjustments --@]}\\ \cline{2-2} -\textbf{Balance Outstanding}&\textbf{\dollar[@-- sprintf('%.2f', $true_previous_balance -$balance_adjustments) --@]}\\ +\textbf{Balance Outstanding}&\textbf{\dollar[@-- sprintf('%.2f', $true_previous_balance) --@]}\\ &\\ \hline &\\ @@ -33,7 +33,7 @@ &\\ \textbf{\underline{Invoice Summary}} & \\ & \\ -\textbf{Previous Past Due Charges}&\textbf{\dollar[@-- sprintf('%.2f', $true_previous_balance - $balance_adjustments) --@]}\\ +\textbf{Previous Past Due Charges}&\textbf{\dollar[@-- sprintf('%.2f', $true_previous_balance) --@]}\\ \textbf{Finance charges on overdue amount}&\textbf{\dollar[@-- $finance_amount --@]}\\ \textbf{New Charges}&\textbf{\dollar[@-- $current_less_finance --@]}\\ -- cgit v1.2.1 From 24504360a41565105569992bf4ff5273b5f88bf7 Mon Sep 17 00:00:00 2001 From: Mitch Jackson Date: Sat, 14 Apr 2018 12:56:14 -0500 Subject: POD Documentation --- FS/FS/cust_bill.pm | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/FS/FS/cust_bill.pm b/FS/FS/cust_bill.pm index fe3867449..0a735f7ec 100644 --- a/FS/FS/cust_bill.pm +++ b/FS/FS/cust_bill.pm @@ -2716,7 +2716,7 @@ sub _items_svc_phone_sections { } -=sub _items_usage_class_summary OPTIONS +=item _items_usage_class_summary OPTIONS Returns a list of detail items summarizing the usage charges on this invoice. Each one will have 'amount', 'description' (the usage charge name), @@ -2765,7 +2765,7 @@ sub _items_usage_class_summary { return @l; } -=sub _items_previous() +=item _items_previous() Returns an array of hashrefs, each hashref representing a line-item on the current bill for previous unpaid invoices. @@ -2899,7 +2899,7 @@ sub _items_previous { } -=sub _items_previous_total +=item _items_previous_total Return sum of amounts from all items returned by _items_previous Results will vary based on invoicing conf flags @@ -2949,7 +2949,7 @@ sub __items_previous_map_invoice { } } -=sub _items_credits() +=item _items_credits() Return array of hashrefs containing credits to be shown as line-items when rendering this bill. @@ -3088,7 +3088,7 @@ sub _items_credits { @return; } -=sub _items_credits_total +=item _items_credits_total Return the total of al items from _items_credits Will vary based on invoice display conf flag @@ -3104,7 +3104,7 @@ sub _items_credits_total { -=sub _items_credits_postbill() +=item _items_credits_postbill() Returns an array of hashrefs for credits where - Credit issued after this invoice @@ -3146,7 +3146,7 @@ sub _items_credits_postbill { }} @cust_credit_bill; } -=sub _items_payments_postbill() +=item _items_payments_postbill() Returns an array of hashrefs for payments where - Payment occured after this invoice @@ -3182,7 +3182,7 @@ sub _items_payments_postbill { }} @cust_bill_pay; } -=sub _items_payments() +=item _items_payments() Return array of hashrefs containing payments to be shown as line-items when rendering this bill. @@ -3300,7 +3300,7 @@ sub _items_payments { return @{ $self->get('_items_payments') }; } -=sub _items_payments_total +=item _items_payments_total Return a total of all records returned by _items_payments Results vary based on invoicing conf flags @@ -3357,7 +3357,7 @@ sub __items_payments_make_hashref { return @return; } -=sub _items_total() +=item _items_total() Generate the line-items to be shown on the bill in the "Totals" section -- cgit v1.2.1 From 49bedb2bc744edc47f5e189e449c7272da5510db Mon Sep 17 00:00:00 2001 From: Mitch Jackson Date: Sat, 14 Apr 2018 17:29:03 -0500 Subject: RT# 79636,42357 Suppress $0.00 summary line items --- FS/FS/Template_Mixin.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FS/FS/Template_Mixin.pm b/FS/FS/Template_Mixin.pm index 6c1e30e4a..04ef4aab9 100644 --- a/FS/FS/Template_Mixin.pm +++ b/FS/FS/Template_Mixin.pm @@ -1090,7 +1090,7 @@ sub print_generic { } } else { # subtotal sectioning is the same as for the actual invoice sections - @summary_subtotals = @sections; + @summary_subtotals = grep $_->{subtotal}, @sections; } # Hereafter, push sections to both @sections and @summary_subtotals -- cgit v1.2.1 From fe2fad859d8444565c0bb35f438accfdf4a240af Mon Sep 17 00:00:00 2001 From: Mitch Jackson Date: Sat, 14 Apr 2018 19:32:03 -0500 Subject: RT# 79636,42357 invoice_sections_with_taxes bugfix --- FS/FS/Template_Mixin.pm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/FS/FS/Template_Mixin.pm b/FS/FS/Template_Mixin.pm index 04ef4aab9..4f5501d15 100644 --- a/FS/FS/Template_Mixin.pm +++ b/FS/FS/Template_Mixin.pm @@ -1195,7 +1195,8 @@ sub print_generic { my %options = (); $options{'section'} = $section if $multisection; $options{'section_with_taxes'} = 1 - if $conf->config_bool('invoice_sections_with_taxes', $cust_main->agentnum); + if $multisection + && $conf->config_bool('invoice_sections_with_taxes', $cust_main->agentnum); $options{'format'} = $format; $options{'escape_function'} = $escape_function; $options{'no_usage'} = 1 unless $unsquelched; -- cgit v1.2.1 From 5828f9ef8d7c9e424423ba2063840060d2da8b6e Mon Sep 17 00:00:00 2001 From: Mitch Jackson Date: Sat, 14 Apr 2018 19:35:12 -0500 Subject: RT# 42357,78190 Fix Fees appearing twice within sectioned invoices --- FS/FS/Template_Mixin.pm | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/FS/FS/Template_Mixin.pm b/FS/FS/Template_Mixin.pm index 4f5501d15..7ea22b55d 100644 --- a/FS/FS/Template_Mixin.pm +++ b/FS/FS/Template_Mixin.pm @@ -1216,6 +1216,17 @@ sub print_generic { foreach my $line_item ( $self->_items_pkg(%options), $self->_items_fee(%options) ) { + # When bill is sectioned by location, fees may be displayed within the + # appropriate location section. Suppress this fee from the taxes/fees + # end section, so it doesn't appear to be charged twice and make the + # subtotals seem incorrect + next + if $line_item->{locationnum} + && ref $options{section} + && !exists $options{section}->{locationnum} + && $self->has_sections + && $conf->config($tc.'sections_method') eq 'location'; + warn "$me adding line item ". join(', ', map "$_=>".$line_item->{$_}, keys %$line_item). "\n" if $DEBUG > 1; -- cgit v1.2.1 From 8441edfbe769a7086f8062913ad840255cdf4b63 Mon Sep 17 00:00:00 2001 From: Mitch Jackson Date: Sun, 15 Apr 2018 16:13:21 -0500 Subject: RT# 78190 Fix format bug for invoices sectioned by location --- conf/invoice_html | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/conf/invoice_html b/conf/invoice_html index d8a2d0a97..935c94346 100644 --- a/conf/invoice_html +++ b/conf/invoice_html @@ -134,7 +134,7 @@ my $columncount = $unitprices ? 5 : 3; foreach my $section ( grep { !$summary || $_->{description} ne $finance_section } @sections ) { if ($section->{'pretotal'} && !$summary) { - $OUT .= '
Object ID
Object NameObject ID
+ + @@ -54,11 +57,11 @@ function receive_mib_get(obj, rownum) {
' if $notfirst; + $OUT .= '' if $notfirst++; $OUT .= ''; } unless ($section->{'summarized'}) { - $OUT .= '
'. '

'. @@ -145,7 +145,10 @@ '

' if ( $notfirst || $section->{'pretotal'} && !$summary ); + if ( $notfirst || $section->{'pretotal'} && !$summary ) { + $OUT .= ''; + $notfirst = 1; + } $OUT .= ''; } - - $notfirst++; - } my $style = 'border-top: 3px solid #000000;'; -- cgit v1.2.1 From adfc623691f9de2feacdb62b5b596112509e2ce9 Mon Sep 17 00:00:00 2001 From: Mitch Jackson Date: Sun, 15 Apr 2018 16:41:32 -0500 Subject: RT# 78190,42357 Correct layout discrepancies for bill summary --- conf/invoice_htmlsummary | 27 ++++++++------------------- conf/invoice_latexsummary | 15 ++++----------- 2 files changed, 12 insertions(+), 30 deletions(-) diff --git a/conf/invoice_htmlsummary b/conf/invoice_htmlsummary index 249db9b07..c07d7e229 100644 --- a/conf/invoice_htmlsummary +++ b/conf/invoice_htmlsummary @@ -35,15 +35,6 @@ - - - - - - - - - @@ -61,16 +52,14 @@ - <%= - foreach my $section ( grep $_->{adjust_section}, @sections) { - $OUT .= ''; - $OUT .= qq("; - } - %> - - - - + <%= if ( $balance_adjustments > 0 ) { + $OUT .= " + + + + + "; + } %> diff --git a/conf/invoice_latexsummary b/conf/invoice_latexsummary index 52868419b..3b13327dc 100644 --- a/conf/invoice_latexsummary +++ b/conf/invoice_latexsummary @@ -9,12 +9,9 @@ \begin{tabular}{lr} \hline &\\ -\textbf{\underline{Summary of Previous Balance and Payments}} & \\ +\textbf{\underline{Summary of Previous Balance}} & \\ &\\ -\textbf{Previous Balance}&\textbf{\dollar[@-- $true_previous_balance --@]}\\ -\textbf{Payments}&\textbf{\dollar[@-- $balance_adjustments --@]}\\ -\cline{2-2} -\textbf{Balance Outstanding}&\textbf{\dollar[@-- sprintf('%.2f', $true_previous_balance) --@]}\\ +\textbf{Previous Balance}&\textbf{\dollar[@-- sprintf('%.2f', $true_previous_balance) --@]}\\ &\\ \hline &\\ @@ -36,15 +33,11 @@ \textbf{Previous Past Due Charges}&\textbf{\dollar[@-- sprintf('%.2f', $true_previous_balance) --@]}\\ \textbf{Finance charges on overdue amount}&\textbf{\dollar[@-- $finance_amount --@]}\\ \textbf{New Charges}&\textbf{\dollar[@-- $current_less_finance --@]}\\ - [@-- - #false laziness w/invoice_htmlsummary and above - foreach my $section ( grep $_->{adjust_section}, @sections ) { - $OUT .= '\textbf{'. ($section->{'description'} ? $section->{'description'} : 'Charges' ). '}'; - $OUT .= '&\textbf{'. $section->{'subtotal'}. '}\\\\'; + if ( $balance_adjustments > 0 ) { + $OUT .= '\textbf{Payments and Credits}&\textbf{-\dollar'.$balance_adjustments.'}\\\\' } --@] - \cline{2-2} \textbf{Total Amount Due}&\textbf{\dollar[@-- sprintf('%.2f', $balance) --@]}\\ &\\ -- cgit v1.2.1 From c5386b4378bcad35867beabec1c344c0a596aecb Mon Sep 17 00:00:00 2001 From: Mitch Jackson Date: Sun, 15 Apr 2018 22:48:58 -0500 Subject: RT# 79353 Update discount graph - include waived setup fees --- FS/FS/Report/Table.pm | 60 ++++++++++++++++++++++ httemplate/graph/cust_bill_pkg_discount.html | 11 +++- .../graph/report_cust_bill_pkg_discount.html | 6 +++ httemplate/search/cust_bill_pkg_discount.html | 6 ++- 4 files changed, 80 insertions(+), 3 deletions(-) diff --git a/FS/FS/Report/Table.pm b/FS/FS/Report/Table.pm index 0c4d9bfa6..cef7813af 100644 --- a/FS/FS/Report/Table.pm +++ b/FS/FS/Report/Table.pm @@ -745,6 +745,12 @@ sub cust_bill_pkg_detail { } +=item cust_bill_pkg_discount: Discounts issued + +Arguments: agentnum, refnum, cust_classnum + +=cut + sub cust_bill_pkg_discount { my $self = shift; my ($speriod, $eperiod, $agentnum, %opt) = @_; @@ -770,6 +776,60 @@ sub cust_bill_pkg_discount { $self->scalar_sql($total_sql); } +=item cust_bill_pkg_discount_or_waived: Discounts and waived fees issued + +Arguments: agentnum, refnum, cust_classnum + +=cut + +sub cust_bill_pkg_discount_or_waived { + + my $self = shift; + my ($speriod, $eperiod, $agentnum, %opt) = @_; + + $agentnum ||= $opt{'agentnum'}; + + my $total_sql = " + SELECT + COALESCE( + SUM( + COALESCE( + cust_bill_pkg_discount.amount, + CAST(( SELECT optionvalue + FROM part_pkg_option + WHERE + part_pkg_option.pkgpart = cust_pkg.pkgpart + AND optionname = 'setup_fee' + ) AS NUMERIC ) + ) + ), + 0 + ) + FROM cust_bill_pkg + LEFT JOIN cust_bill_pkg_discount USING (billpkgnum) + LEFT JOIN cust_pkg ON cust_bill_pkg.pkgnum = cust_pkg.pkgnum + LEFT JOIN part_pkg USING (pkgpart) + LEFT JOIN cust_bill USING ( invnum ) + LEFT JOIN cust_main ON cust_pkg.custnum = cust_main.custnum + WHERE + ( + cust_bill_pkg_discount.billpkgdiscountnum IS NOT NULL + OR ( + cust_pkg.setup = cust_bill_pkg.sdate + AND cust_pkg.waive_setup = 'Y' + ) + ) + AND cust_bill_pkg.pkgpart_override IS NULL + " . join "\n", + map { " AND ( $_ ) " } + grep { $_ } + $self->with_classnum($opt{'classnum'}, $opt{'use_override'}), + $self->with_report_option(%opt), + $self->in_time_period_and_agent($speriod, $eperiod, $agentnum); + + $self->scalar_sql($total_sql); +} + sub cust_bill_pkg_taxes { my $self = shift; my ($speriod, $eperiod, $agentnum, %opt) = @_; diff --git a/httemplate/graph/cust_bill_pkg_discount.html b/httemplate/graph/cust_bill_pkg_discount.html index 5074aa35c..22327e05b 100644 --- a/httemplate/graph/cust_bill_pkg_discount.html +++ b/httemplate/graph/cust_bill_pkg_discount.html @@ -20,7 +20,10 @@ die "access denied" unless $FS::CurrentUser::CurrentUser->access_right('Financial reports'); +my $include_waived_setup = $cgi->param('include_waived_setup') || 0; + my $link = "${p}search/cust_bill_pkg_discount.html?"; +$link .= "include_waived_setup=Y&" if $include_waived_setup; my $bottom_link = $link; #XXX or virtual @@ -35,7 +38,8 @@ my $title = $sel_agent ? $sel_agent->agent.' ' : ''; $title .= 'Discount Overview'; -my $hue = 0; +#my $hue = 0; # Start with illegible yellow-on-white +my $hue = 255; # Start with red-on-white #my $hue_increment = 170; #my $hue_increment = 145; my $hue_increment = 125; @@ -56,7 +60,10 @@ foreach my $agent ( $sel_agent || qsearch('agent', { 'disabled' => '' } ) ) { #foreach my $pkg_class ( @pkg_class ) { - push @items, 'cust_bill_pkg_discount'; + push @items, + $include_waived_setup + ? 'cust_bill_pkg_discount_or_waived' + : 'cust_bill_pkg_discount'; push @labels, ( $sel_agent ? '' : $agent->agent.' ' ); diff --git a/httemplate/graph/report_cust_bill_pkg_discount.html b/httemplate/graph/report_cust_bill_pkg_discount.html index 36ad78252..094c652c7 100644 --- a/httemplate/graph/report_cust_bill_pkg_discount.html +++ b/httemplate/graph/report_cust_bill_pkg_discount.html @@ -19,6 +19,12 @@ 'disable_empty' => 0, &> + <& /elements/tr-checkbox.html, + label => 'Include waived setup fees:', + field => 'include_waived_setup', + value => 'Y', + &> + % # anything about line items, discounts or packages really % # otaker? % # package class? diff --git a/httemplate/search/cust_bill_pkg_discount.html b/httemplate/search/cust_bill_pkg_discount.html index f4fbd561b..d31614af3 100644 --- a/httemplate/search/cust_bill_pkg_discount.html +++ b/httemplate/search/cust_bill_pkg_discount.html @@ -228,7 +228,11 @@ if ( $cgi->param('usernum') =~ /^(\d+)$/ ) { } # Filter: Include waived setup fees -if ( !$cgi->param('include_waived_setup') ) { +if ( $cgi->param('include_waived_setup') ) { + # Filter a hidden fee attached to a package with a waived setup fee from + # causing the waived-fee for that package to be double-counted + push @where, 'cust_bill_pkg.pkgpart_override IS NULL'; +} else { push @where, "cust_bill_pkg_discount.pkgdiscountnum IS NOT NULL"; } -- cgit v1.2.1 From ce6449308e58d9d7692f2af3e322e9d552f56544 Mon Sep 17 00:00:00 2001 From: Mitch Jackson Date: Mon, 16 Apr 2018 18:05:34 +0000 Subject: RT# 79353 Update discount reports --- httemplate/search/cust_bill_pkg_discount.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/httemplate/search/cust_bill_pkg_discount.html b/httemplate/search/cust_bill_pkg_discount.html index d31614af3..ba1b82083 100644 --- a/httemplate/search/cust_bill_pkg_discount.html +++ b/httemplate/search/cust_bill_pkg_discount.html @@ -38,7 +38,7 @@ Parameters: if ( $_[0]->pkgdiscountnum ) { # Standard discount, not a waived setup fee my $discount = qsearchs('discount',{ - pkgdiscountnum => $_[0]->pkgdiscountnum + discountnum => $_[0]->pkgdiscountnum }); return $discount->description; } else { -- cgit v1.2.1 From bfd03694e81b9bc09b24d288adde0a038fc93288 Mon Sep 17 00:00:00 2001 From: Mitch Jackson Date: Mon, 16 Apr 2018 15:21:42 -0500 Subject: RT# 79353 Update discount reports --- httemplate/search/cust_bill_pkg_discount.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/httemplate/search/cust_bill_pkg_discount.html b/httemplate/search/cust_bill_pkg_discount.html index ba1b82083..eb39dea8f 100644 --- a/httemplate/search/cust_bill_pkg_discount.html +++ b/httemplate/search/cust_bill_pkg_discount.html @@ -38,7 +38,7 @@ Parameters: if ( $_[0]->pkgdiscountnum ) { # Standard discount, not a waived setup fee my $discount = qsearchs('discount',{ - discountnum => $_[0]->pkgdiscountnum + discountnum => $_[0]->discountnum }); return $discount->description; } else { -- cgit v1.2.1 From fb747f8161b7ec9d3092c5796f373c1b6a373764 Mon Sep 17 00:00:00 2001 From: Christopher Burger Date: Mon, 16 Apr 2018 16:44:16 -0400 Subject: RT# 80175 - fixed error in payment gateway where ACH cardtype was always selected. --- FS/FS/agent.pm | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/FS/FS/agent.pm b/FS/FS/agent.pm index bf389deac..810709357 100644 --- a/FS/FS/agent.pm +++ b/FS/FS/agent.pm @@ -294,13 +294,15 @@ sub payment_gateway { } } - my $cardtype = ''; - if ( $options{method} eq 'ECHECK' ) { $cardtype = 'ACH'; } + my $cardtype_search = "AND cardtype != 'ACH'"; + $cardtype_search = "AND cardtype = 'ACH'" if $options{method} eq 'ECHECK'; my $override = - qsearchs('agent_payment_gateway', { agentnum => $self->agentnum, - cardtype => $cardtype, } ) - || qsearchs('agent_payment_gateway', { agentnum => $self->agentnum } ); + qsearchs({ + "table" => 'agent_payment_gateway', + "hashref" => { agentnum => $self->agentnum, }, + "extra_sql" => $cardtype_search, + }); my $payment_gateway = FS::payment_gateway->by_key_or_default( gatewaynum => $override ? $override->gatewaynum : '', -- cgit v1.2.1 From 69c7d00baa39ccc2fbee0f12d03f49403f04c3cf Mon Sep 17 00:00:00 2001 From: Ivan Kohler Date: Tue, 17 Apr 2018 11:23:22 -0700 Subject: log pid in our log, RT#80211 --- FS/FS/Schema.pm | 1 + FS/FS/access_user_log.pm | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm index 5b33ecba9..e2c575eb5 100644 --- a/FS/FS/Schema.pm +++ b/FS/FS/Schema.pm @@ -5968,6 +5968,7 @@ sub tables_hashref { 'path', 'varchar', '', 2*$char_d, '', '', '_date', @date_type, '', '', 'render_seconds', 'int', 'NULL', '', '', '', + 'pid', 'int', 'NULL', '', '', '', ], 'primary_key' => 'lognum', 'unique' => [], diff --git a/FS/FS/access_user_log.pm b/FS/FS/access_user_log.pm index 026670caf..552dd2ad8 100644 --- a/FS/FS/access_user_log.pm +++ b/FS/FS/access_user_log.pm @@ -53,6 +53,10 @@ _date =back +=item pid + +=back + =head1 METHODS =over 4 @@ -84,6 +88,7 @@ sub insert_new_path { 'path' => $path, '_date' => time, 'render_seconds' => $render_seconds, + 'pid' => $$, } ); #so we can still log pages after a transaction-aborting SQL error (and then @@ -127,6 +132,7 @@ sub check { || $self->ut_text('path') || $self->ut_number('_date') || $self->ut_numbern('render_seconds') + || $self->ut_numbern('pid') ; return $error if $error; -- cgit v1.2.1 From 7738617402d4f0a6fd6b75fa8c56b48c715a3c51 Mon Sep 17 00:00:00 2001 From: Ivan Kohler Date: Wed, 18 Apr 2018 18:03:07 -0700 Subject: ip allocation mutex for high-traffic implementations, RT#79825 --- FS/FS/IP_Mixin.pm | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/FS/FS/IP_Mixin.pm b/FS/FS/IP_Mixin.pm index beb41d290..3ec769313 100644 --- a/FS/FS/IP_Mixin.pm +++ b/FS/FS/IP_Mixin.pm @@ -130,6 +130,10 @@ sub assign_ip_addr { my $self = shift; my %opt = @_; + #otherwise we'll get the same assignment for concurrent identical calls + # this will serialize them + $_->lock_table foreach @subclasses; + my @blocks; my $na = $self->NetAddr; -- cgit v1.2.1 From 977188119859438f213f1c35cafdbf5a28a258a6 Mon Sep 17 00:00:00 2001 From: Ivan Kohler Date: Thu, 19 Apr 2018 14:47:39 -0700 Subject: restore masked card info on batched payment report, RT#78166 --- httemplate/search/cust_pay_batch.cgi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/httemplate/search/cust_pay_batch.cgi b/httemplate/search/cust_pay_batch.cgi index cc958b96d..6b175adb7 100755 --- a/httemplate/search/cust_pay_batch.cgi +++ b/httemplate/search/cust_pay_batch.cgi @@ -114,7 +114,7 @@ $count_query = 'SELECT COUNT(*) FROM cust_pay_batch ' . $sql_query = { 'table' => 'cust_pay_batch', - 'select' => 'cust_pay_batch.*, cust_main.*, cust_pay.paynum', + 'select' => 'cust_pay_batch.*, cust_pay.paynum', 'hashref' => {}, 'addl_from' => 'LEFT JOIN pay_batch USING ( batchnum ) '. 'LEFT JOIN cust_main USING ( custnum ) '. -- cgit v1.2.1 From b1ff95e68f17199e2289e1515e3a3b836cd195c8 Mon Sep 17 00:00:00 2001 From: Mitch Jackson Date: Thu, 19 Apr 2018 20:02:26 -0500 Subject: RT# 79636 Location Summary Invoice Footer --- FS/FS/Template_Mixin.pm | 4 +++- conf/invoice_html | 55 ++++++++++++++++++++++++++++++++++++++++++++++ conf/invoice_latex | 58 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 116 insertions(+), 1 deletion(-) diff --git a/FS/FS/Template_Mixin.pm b/FS/FS/Template_Mixin.pm index 7ea22b55d..646312502 100644 --- a/FS/FS/Template_Mixin.pm +++ b/FS/FS/Template_Mixin.pm @@ -933,7 +933,9 @@ sub print_generic { my $unsquelched = $params{unsquelch_cdr} || $cust_main->squelch_cdr ne 'Y'; my $multisection = $self->has_sections; - $invoice_data{'multisection'} = $multisection; + if ( $multisection ) { + $invoice_data{multisection} = $conf->config($tc.'sections_method') || 1; + } my $late_sections; my $extra_sections = []; my $extra_lines = (); diff --git a/conf/invoice_html b/conf/invoice_html index 935c94346..1241f870e 100644 --- a/conf/invoice_html +++ b/conf/invoice_html @@ -327,6 +327,61 @@
'; $OUT .= '

'; my $sectionhead; @@ -292,9 +295,6 @@ '

'; $OUT .= '

<%= $dollar.$current_less_finance %>


Summary of Payments and Credits
Payments and Credits-<%= $dollar.$balance_adjustments %>


Invoice SummaryNew Charges <%= $dollar.$current_less_finance %>
'. ($section->{'description'} ? $section->{'description'} : 'Charges' ). '). $section->{'subtotal'}. "
Payments and Credits-<%= $dollar.sprintf('%.2f', $balance_adjustments) %>
Payments and Credits-$dollar" . sprintf('%.2f', $balance_adjustments). "
Total Amount Due <%= $dollar.sprintf('%.2f', $balance) %>


+<%= + + my @location_summary_sections = + grep { + ref $_->{location} + && $_->{locationnum} + && $_->{description} + && $_->{description} ne $finance_section + } @sections; + + if ( $multisection eq 'location' && scalar(@location_summary_sections) > 1 ) { + + $OUT .= ' +
+ + + + +
+

+ '.emt('Summary Of New Charges By Location').' +

+

+ + + + + + + + + + + '; + + for my $section (@location_summary_sections) { + next unless $section->{description}; + $OUT .= ' + + + + + + '; + } + + $OUT .= ' + + +
'.emt('Location').''.emt('Amount').'
'.$section->{description}.''. $section->{subtotal} .'
   
+

+ '; + +} %> + <%= length($summary) ? '' : ( $smallernotes diff --git a/conf/invoice_latex b/conf/invoice_latex index a710cddec..478405ebd 100644 --- a/conf/invoice_latex +++ b/conf/invoice_latex @@ -447,6 +447,64 @@ } --@] +[@-- + + my @location_summary_sections = + grep { + ref $_->{location} + && $_->{locationnum} + && $_->{description} + && $_->{description} ne $finance_section + } @sections; + if ( $multisection eq 'location' && scalar(@location_summary_sections) > 1 ) { + +$OUT .= ' + \hline + \section*{} + \captionsetup{singlelinecheck=false,justification=raggedright,font={Large,sc,bf}} + \ifthenelse{\equal{\thepage}{1}}{\setlength{\LTextracouponspace}{\extracouponspace}}{\setlength{\LTextracouponspace}{0pt}} + + \begin{longtable}{cllllllr} + \caption*{ '. emt('Summary of New Charges by Location') .' } + \\\\ + + \hline + \rule{0pt}{2.5ex} + \makebox[1.4cm]{} & + \multicolumn{6}{l}{ + \truncate{13.0cm}{\textbf{'. emt('Location') .'}} + } & + \makebox[1.6cm][r]{\textbf{'. emt('Amount') .'}} \\\\ + \hline + + \endfirsthead + \multicolumn{7}{r}{\rule{0pt}{2.5ex}'. emt('Continued from previous page') .'} + \\ + \FShead + \endhead + \multicolumn{7}{r}{\rule{0pt}{2.5ex}'. emt('Continued on next page...') .'} + \\ + \endfoot + \hline + \endlastfoot + \hline + '; + + for my $section (@location_summary_sections) { + $OUT.= ' + \rule{0pt}{2.5ex} + \makebox[1.4cm]{} & + \multicolumn{6}{l}{ + \truncate{12.0cm}{\textbf{'. $section->{description} .'}} + } & + \makebox[1.6cm][r]{\textbf{'. $section->{subtotal} .'}} \\\\ + '; + } + + $OUT .= '\end{longtable}'; + } +--@] + \vfill \begin{minipage}[t]{\textwidth} [@-- length($summary) -- cgit v1.2.1 From babcdb7e84d1d9ee1537b47e16c4e182f426f07b Mon Sep 17 00:00:00 2001 From: Christopher Burger Date: Fri, 20 Apr 2018 12:08:43 -0400 Subject: RT# 77964 - fixed so deferring date now works when waive late fee is set --- FS/FS/cust_main/Billing.pm | 2 -- FS/FS/part_pkg/flat.pm | 23 ++++++++++++++--------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/FS/FS/cust_main/Billing.pm b/FS/FS/cust_main/Billing.pm index 08b10c1ff..44a4dbb04 100644 --- a/FS/FS/cust_main/Billing.pm +++ b/FS/FS/cust_main/Billing.pm @@ -1030,7 +1030,6 @@ sub _make_lines { warn " bill setup\n" if $DEBUG > 1; - unless ( $cust_pkg->waive_setup ) { $lineitems++; $setup = eval { $cust_pkg->calc_setup( $time, \@details, \%setup_param ) }; @@ -1050,7 +1049,6 @@ sub _make_lines { $setup_billed_currency = delete $setup_param{'billed_currency'}; $setup_billed_amount = delete $setup_param{'billed_amount'}; } - } if ( $cust_pkg->get('setup') ) { # don't change it diff --git a/FS/FS/part_pkg/flat.pm b/FS/FS/part_pkg/flat.pm index 6fd9c7d08..0bc3860a3 100644 --- a/FS/FS/part_pkg/flat.pm +++ b/FS/FS/part_pkg/flat.pm @@ -118,22 +118,27 @@ sub calc_setup { return 0 if $self->prorate_setup($cust_pkg, $sdate); - my $i = 0; - my $count = $self->option( 'additional_count', 'quiet' ) || 0; - while ($i < $count) { - push @$details, $self->option( 'additional_info' . $i++ ); - } + if (!$cust_pkg->waive_setup) { + my $i = 0; + my $count = $self->option( 'additional_count', 'quiet' ) || 0; + while ($i < $count) { + push @$details, $self->option( 'additional_info' . $i++ ); + } - my $charge = $self->base_setup($cust_pkg, $sdate, $details); + my $charge = $self->base_setup($cust_pkg, $sdate, $details); - my $discount = 0; - if ( $charge > 0 ) { + my $discount = 0; + if ( $charge > 0 ) { $param->{'setup_charge'} = $charge; $discount = $self->calc_discount($cust_pkg, $sdate, $details, $param); delete $param->{'setup_charge'}; + } + + return sprintf( '%.2f', ($cust_pkg->quantity || 1) * ($charge - $discount) ); } - sprintf( '%.2f', ($cust_pkg->quantity || 1) * ($charge - $discount) ); + return; + } sub base_setup { -- cgit v1.2.1 From 9cdd008acdaa19127409188b51e25fe8c8b0b04b Mon Sep 17 00:00:00 2001 From: Christopher Burger Date: Fri, 20 Apr 2018 12:08:43 -0400 Subject: RT# 77964 - fixed so deferring date now works when waive setup fee is set --- FS/FS/cust_main/Billing.pm | 2 -- FS/FS/part_pkg/flat.pm | 23 ++++++++++++++--------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/FS/FS/cust_main/Billing.pm b/FS/FS/cust_main/Billing.pm index 08b10c1ff..44a4dbb04 100644 --- a/FS/FS/cust_main/Billing.pm +++ b/FS/FS/cust_main/Billing.pm @@ -1030,7 +1030,6 @@ sub _make_lines { warn " bill setup\n" if $DEBUG > 1; - unless ( $cust_pkg->waive_setup ) { $lineitems++; $setup = eval { $cust_pkg->calc_setup( $time, \@details, \%setup_param ) }; @@ -1050,7 +1049,6 @@ sub _make_lines { $setup_billed_currency = delete $setup_param{'billed_currency'}; $setup_billed_amount = delete $setup_param{'billed_amount'}; } - } if ( $cust_pkg->get('setup') ) { # don't change it diff --git a/FS/FS/part_pkg/flat.pm b/FS/FS/part_pkg/flat.pm index 6fd9c7d08..0bc3860a3 100644 --- a/FS/FS/part_pkg/flat.pm +++ b/FS/FS/part_pkg/flat.pm @@ -118,22 +118,27 @@ sub calc_setup { return 0 if $self->prorate_setup($cust_pkg, $sdate); - my $i = 0; - my $count = $self->option( 'additional_count', 'quiet' ) || 0; - while ($i < $count) { - push @$details, $self->option( 'additional_info' . $i++ ); - } + if (!$cust_pkg->waive_setup) { + my $i = 0; + my $count = $self->option( 'additional_count', 'quiet' ) || 0; + while ($i < $count) { + push @$details, $self->option( 'additional_info' . $i++ ); + } - my $charge = $self->base_setup($cust_pkg, $sdate, $details); + my $charge = $self->base_setup($cust_pkg, $sdate, $details); - my $discount = 0; - if ( $charge > 0 ) { + my $discount = 0; + if ( $charge > 0 ) { $param->{'setup_charge'} = $charge; $discount = $self->calc_discount($cust_pkg, $sdate, $details, $param); delete $param->{'setup_charge'}; + } + + return sprintf( '%.2f', ($cust_pkg->quantity || 1) * ($charge - $discount) ); } - sprintf( '%.2f', ($cust_pkg->quantity || 1) * ($charge - $discount) ); + return; + } sub base_setup { -- cgit v1.2.1 From e884af8c6997d97753598ccec16a3156e0dcbf3f Mon Sep 17 00:00:00 2001 From: Ivan Kohler Date: Mon, 23 Apr 2018 17:08:58 -0700 Subject: bulk customer package edit from multiple source package definitions, RT#79885 --- FS/FS/cust_pkg.pm | 14 ++++++++---- httemplate/browse/part_pkg.cgi | 20 ++++++++++++++--- httemplate/edit/bulk-cust_pkg.html | 32 +++++++++++++++++---------- httemplate/search/elements/checkbox-foot.html | 4 ++-- 4 files changed, 49 insertions(+), 21 deletions(-) diff --git a/FS/FS/cust_pkg.pm b/FS/FS/cust_pkg.pm index b24b3abe3..07c5a4756 100644 --- a/FS/FS/cust_pkg.pm +++ b/FS/FS/cust_pkg.pm @@ -3329,11 +3329,10 @@ sub process_bulk_cust_pkg { my $param = shift; warn Dumper($param) if $DEBUG; - my $old_part_pkg = qsearchs('part_pkg', - { pkgpart => $param->{'old_pkgpart'} }); my $new_part_pkg = qsearchs('part_pkg', { pkgpart => $param->{'new_pkgpart'} }); - die "Must select a new package type\n" unless $new_part_pkg; + die "Must select a new package definition\n" unless $new_part_pkg; + #my $keep_dates = $param->{'keep_dates'} || 0; my $keep_dates = 1; # there is no good reason to turn this off @@ -3341,7 +3340,14 @@ sub process_bulk_cust_pkg { local $FS::UID::AutoCommit = 0; my $dbh = dbh; - my @cust_pkgs = qsearch('cust_pkg', { 'pkgpart' => $param->{'old_pkgpart'} } ); + my @old_pkgpart = ref($param->{'old_pkgpart'}) ? @{ $param->{'old_pkgpart'} } + : $param->{'old_pkgpart'}; + + my @cust_pkgs = qsearch({ + 'table' => 'cust_pkg', + 'extra_sql' => ' WHERE pkgpart IN ('. + join(',', @old_pkgpart). ')', + }); my $i = 0; foreach my $old_cust_pkg ( @cust_pkgs ) { diff --git a/httemplate/browse/part_pkg.cgi b/httemplate/browse/part_pkg.cgi index 8c51b35f4..f25c00ef2 100755 --- a/httemplate/browse/part_pkg.cgi +++ b/httemplate/browse/part_pkg.cgi @@ -562,7 +562,7 @@ if ( $acl_edit_global ) { 'action' => "${p}edit/bulk-cust_pkg.html?". 'pkgpart='.$part_pkg->pkgpart, 'actionlabel' => 'Change Packages', - 'width' => 569, + 'width' => 960, 'height' => 210, ).' ]', 'align' => 'left', @@ -796,8 +796,22 @@ if ( $acl_edit_bulk ) { $align .= 'c'; $html_form = qq!
!; $html_foot = include('/search/elements/checkbox-foot.html', - submit => 'edit report classes', # for now it's only report classes - ) . '
'; + actions => [ + { submit => 'edit report classes', }, + { label => 'change customer packages', + onclick=> include('/elements/popup_link_onclick.html', + 'label' => 'change', + 'js_action' => qq{ + '${p}edit/bulk-cust_pkg.html?' + \$('input[name=pkgpart]').serialize() + }, + 'actionlabel' => 'Change customer packages', + 'width' => 960, + 'height' => 420, + ) + }, + ], + ). + ''; } my @menubar; diff --git a/httemplate/edit/bulk-cust_pkg.html b/httemplate/edit/bulk-cust_pkg.html index 2ff38ca53..8a082f47f 100644 --- a/httemplate/edit/bulk-cust_pkg.html +++ b/httemplate/edit/bulk-cust_pkg.html @@ -19,24 +19,18 @@ function areyousure() { }
-% #false laziness with bulk-cust_svc.html -% $cgi->param('pkgpart') =~ /^(\d+)$/ -% or die "illegal pkgpart: ". $cgi->param('pkgpart'); -% -% my $old_pkgpart = $1; -% my $src_part_pkg = qsearchs('part_pkg', { 'pkgpart' => $old_pkgpart } ) -% or die "unknown pkgpart: $old_pkgpart"; -% +% foreach my $src_part_pkg (@src_part_pkg) { + + Change <% $src_part_pkg->pkg_comment |h %>
+% } - -Change <% $src_part_pkg->pkg_comment %>
- +
to new package definition @@ -57,4 +51,18 @@ to new package definition die "access denied" unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); +my @src_part_pkg = (); +foreach my $pkgpart ( $cgi->multi_param('pkgpart') ) { + + $pkgpart =~ /^(\d+)$/ + or die "illegal pkgpart: $pkgpart"; + + my $old_pkgpart = $1; + my $src_part_pkg = qsearchs('part_pkg', { 'pkgpart' => $old_pkgpart } ) + or die "unknown pkgpart: $old_pkgpart"; + + push @src_part_pkg, $src_part_pkg; + +} + diff --git a/httemplate/search/elements/checkbox-foot.html b/httemplate/search/elements/checkbox-foot.html index ae8b79470..f33a87467 100644 --- a/httemplate/search/elements/checkbox-foot.html +++ b/httemplate/search/elements/checkbox-foot.html @@ -4,7 +4,7 @@ html_foot => include('elements/checkbox-foot.html', actions => [ { label => 'Edit selected packages', - action => 'popup_package_edit()', + onclick => 'popup_package_edit()', }, { submit => 'Delete selected packages', confirm => 'Really delete these packages?' @@ -50,7 +50,7 @@ false.
% foreach my $action (@$actions) { % if ( $action->{onclick} ) { -{name} %> onclick="<% $opt{onclick} %>"\ +{name} %> onclick="<% $action->{onclick} %>"\ VALUE="<% $action->{label} |h%>"> % } elsif ( $action->{submit} ) { {name} %> <% $action->{confirm} %>\ -- cgit v1.2.1 From 8309e857e05c33d65edee6bfe18a60db5300e310 Mon Sep 17 00:00:00 2001 From: Christopher Burger Date: Wed, 25 Apr 2018 11:56:14 -0400 Subject: RT# 78356 - Updated documentation --- FS/FS/part_export/saisei.pm | 85 ++++++++++++++++++++------------------------- 1 file changed, 38 insertions(+), 47 deletions(-) diff --git a/FS/FS/part_export/saisei.pm b/FS/FS/part_export/saisei.pm index 922a347b6..2fc112744 100644 --- a/FS/FS/part_export/saisei.pm +++ b/FS/FS/part_export/saisei.pm @@ -24,38 +24,33 @@ 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 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 is a customer integration with Saisei. This will set up a rate plan and tie +the rate plan to a host and the access point via the Saisei API when the broadband service is provisioned. +It will also untie the host from the rate plan, setting it to the default 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. +This will also create and modify an 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 -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. +Create a new service definition and set the table to svc_broadband. The service name will become the Saisei rate plan name. 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. +Attach this Saisei export to this 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 the up and down rate limit for the Tower and Sector. This is required to be able to export the access point. +Make sure you have set the up and down rate limit for the tower and the sector. This is required to be able to export the access point. +The tower and sector will be set up as access points at Saisei upon the creation of the tower or sector. They will be modified at Saisei when modified in freeside. +Each sector will be attached to its tower access point using the Saisei uplink field. -Create a package for the above created broadband service, and order this package for a customer. +Create a package for the above created 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. +Provision the service, making sure to enter the IP address associated with 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. +Unprovisioning this service will set the host entry at Saisei to the default rate plan with the user and access point set to . -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. +After this export is set up and attached to a service, you can export the already provisioned services by clicking the link Export provisioned services attached to this export. +Clicking on this link will export all services attached to this export not currently exported to Saisei. This module also provides generic methods for working through the L. @@ -65,16 +60,16 @@ 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.', + html_label => 'Export provisioned services attached to this export.', }, ; tie my %options, 'Tie::IxHash', 'port' => { label => 'Port', default => 5000 }, - 'username' => { label => 'User Name', + 'username' => { label => 'Saisei API User Name', default => '' }, - 'password' => { label => 'Password', + 'password' => { label => 'Saisei API Password', default => '' }, 'debug' => { type => 'checkbox', label => 'Enable debug warnings' }, @@ -86,52 +81,47 @@ tie my %options, 'Tie::IxHash', '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 is a customer integration with Saisei. This will set up a rate plan and tie +the rate plan to a host and the access point via the Saisei API when the broadband service is provisioned. +It will also untie the host from the rate plan, setting it to the default 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. +This will also create and modify an access point at Saisei as soon as the tower is created or modified.

To use this export, follow the below instructions:

  1. -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
    • -
    -
  2. -

    -

  3. -Create a broadband service. The broadband service name will become the Saisei rate plan name. +Create a new service definition and set the table to svc_broadband. The service name will become the Saisei rate plan name. 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. +Attach this Saisei export to this service.
  4. 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 the up and down rate limit for the Tower and Sector. This is required to be able to export the access point. +Make sure you have set the up and down rate limit for the tower and the sector. This is required to be able to export the access point. +The tower and sector will be set up as access points at Saisei upon the creation of the tower or sector. They will be modified at Saisei when modified in freeside. +Each sector will be attached to its tower access point using the Saisei uplink field.
  5. -Create a package for the above created broadband service, and order this package for a customer. +Create a package for the above created service, and order this package for a customer.
  6. -When you provision the service, enter the ip address associated to this service and select the Tower and Sector for it's access point. +Provision the service, making sure to enter the IP address associated with 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. +Unprovisioning this service will set the host entry at Saisei to the default rate plan with the user and access point set to none. +

  7. +

    +

  8. +After this export is set up and attached to a service, you can export the already provisioned services by clicking the link Export provisioned services attached to this export. +Clicking on this link will export all services attached to this export not currently exported to Saisei.

-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 ); @@ -238,7 +228,7 @@ sub _export_delete { $rateplan_name =~ s/\s/_/g; my $username = $svc_broadband->{Hash}->{svcnum}; - ## tie host to user + ## untie host to user $self->api_delete_host_to_user($username, $rateplan_name, $svc_broadband->{Hash}->{ip_addr}) unless $self->{'__saisei_error'}; return ''; @@ -685,7 +675,8 @@ sub api_add_host_to_user { =head2 api_delete_host_to_user -unties host to user and rateplan. +unties host from user and rateplan. +this will set the host entry at Saisei to the default rate plan with the user and access point set to . =cut -- cgit v1.2.1 From ae9a208fa96fca49f1eef01519353a4c9f54d15f Mon Sep 17 00:00:00 2001 From: Christopher Burger Date: Fri, 27 Apr 2018 13:48:58 -0400 Subject: RT# 77160 - fixed report error when multiple canceled packages had they same datetime --- httemplate/search/cust_timespan.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/httemplate/search/cust_timespan.html b/httemplate/search/cust_timespan.html index a380b78ab..f5684251a 100644 --- a/httemplate/search/cust_timespan.html +++ b/httemplate/search/cust_timespan.html @@ -88,7 +88,7 @@ my $cancel_date = 'select max(cancel) from cust_pkg where cust_pkg.custnum = cus my $cancel_reason = 'select reason.reason from cust_pkg left join cust_pkg_reason on (cust_pkg.pkgnum = cust_pkg_reason.pkgnum) left join reason on (cust_pkg_reason.reasonnum = reason.reasonnum) - where cust_pkg.custnum = cust_main.custnum and cust_pkg_reason.date = ('.$cancel_date.') + where cust_pkg.custnum = cust_main.custnum and cust_pkg_reason.date = ('.$cancel_date.') limit 1 '; my @header = ( '#', 'Name', 'Address', 'Phone', 'Email', 'Active Date', 'Cancelled Date', 'Reason', 'Active Days' ); -- cgit v1.2.1 From d263897b2b9247340901f93c57f44c7f85d87d37 Mon Sep 17 00:00:00 2001 From: Christopher Burger Date: Tue, 1 May 2018 10:07:54 -0400 Subject: RT# 77964 - Placed waive setup fee check back to billing.pm, and added check for prorate package in billing.pm --- FS/FS/cust_main/Billing.pm | 9 ++++++++- FS/FS/part_pkg/flat.pm | 28 ++++++++++++---------------- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/FS/FS/cust_main/Billing.pm b/FS/FS/cust_main/Billing.pm index 44a4dbb04..a5a38fdc2 100644 --- a/FS/FS/cust_main/Billing.pm +++ b/FS/FS/cust_main/Billing.pm @@ -1027,9 +1027,10 @@ sub _make_lines { || $cust_pkg->expire > $cmp_time ) ) { - + warn " bill setup\n" if $DEBUG > 1; + unless ($cust_pkg->waive_setup) { $lineitems++; $setup = eval { $cust_pkg->calc_setup( $time, \@details, \%setup_param ) }; @@ -1049,6 +1050,12 @@ sub _make_lines { $setup_billed_currency = delete $setup_param{'billed_currency'}; $setup_billed_amount = delete $setup_param{'billed_amount'}; } + } + + if ($cust_pkg->waive_setup && $part_pkg->plan eq "prorate") { + $lineitems++; + $setup = 0 if $part_pkg->prorate_setup($cust_pkg, $time); + } if ( $cust_pkg->get('setup') ) { # don't change it diff --git a/FS/FS/part_pkg/flat.pm b/FS/FS/part_pkg/flat.pm index 0bc3860a3..cfee58465 100644 --- a/FS/FS/part_pkg/flat.pm +++ b/FS/FS/part_pkg/flat.pm @@ -118,26 +118,22 @@ sub calc_setup { return 0 if $self->prorate_setup($cust_pkg, $sdate); - if (!$cust_pkg->waive_setup) { - my $i = 0; - my $count = $self->option( 'additional_count', 'quiet' ) || 0; - while ($i < $count) { - push @$details, $self->option( 'additional_info' . $i++ ); - } + my $i = 0; + my $count = $self->option( 'additional_count', 'quiet' ) || 0; + while ($i < $count) { + push @$details, $self->option( 'additional_info' . $i++ ); + } - my $charge = $self->base_setup($cust_pkg, $sdate, $details); + my $charge = $self->base_setup($cust_pkg, $sdate, $details); - my $discount = 0; - if ( $charge > 0 ) { - $param->{'setup_charge'} = $charge; - $discount = $self->calc_discount($cust_pkg, $sdate, $details, $param); - delete $param->{'setup_charge'}; - } - - return sprintf( '%.2f', ($cust_pkg->quantity || 1) * ($charge - $discount) ); + my $discount = 0; + if ( $charge > 0 ) { + $param->{'setup_charge'} = $charge; + $discount = $self->calc_discount($cust_pkg, $sdate, $details, $param); + delete $param->{'setup_charge'}; } - return; + sprintf( '%.2f', ($cust_pkg->quantity || 1) * ($charge - $discount) ); } -- cgit v1.2.1 From 35d34f06298d62aac3d4d9a2b486a2759d6d4952 Mon Sep 17 00:00:00 2001 From: Christopher Burger Date: Thu, 3 May 2018 15:40:42 -0400 Subject: RT# 78131 - added ability to use message template for auto payment receipt and fixed NaN error when selecting nothing for config items with type select. --- FS/FS/Conf.pm | 7 ++++ FS/FS/cust_pay.pm | 72 +++++++++++++++++++++++++++--------- httemplate/config/config-process.cgi | 4 +- 3 files changed, 64 insertions(+), 19 deletions(-) diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm index 9f234da11..2f8ac243d 100644 --- a/FS/FS/Conf.pm +++ b/FS/FS/Conf.pm @@ -1699,6 +1699,13 @@ and customer address. Include units.', 'description' => 'Template to use for manual payment receipts.', %msg_template_options, }, + + { + 'key' => 'payment_receipt_msgnum_auto', + 'section' => 'notification', + 'description' => 'Automatic payments will cause a post-payment to use a message template for automatic payment receipts rather than a post payment statement.', + %msg_template_options, + }, { 'key' => 'payment_receipt_from', diff --git a/FS/FS/cust_pay.pm b/FS/FS/cust_pay.pm index 8b5e06db2..5031793ff 100644 --- a/FS/FS/cust_pay.pm +++ b/FS/FS/cust_pay.pm @@ -703,27 +703,65 @@ sub send_receipt { #not manual and no noemail flag (here or on the customer) } elsif ( ! $opt->{'noemail'} && ! $cust_main->invoice_noemail ) { - my $queue = new FS::queue { - 'job' => 'FS::cust_bill::queueable_email', - 'paynum' => $self->paynum, - 'custnum' => $cust_main->custnum, - }; + # check to see if they want to send specific message template as receipt for auto payments + my $msgnum = $conf->config('payment_receipt_msgnum_auto', $cust_main->agentnum); + if ( $msgnum ) { - my %opt = ( - 'invnum' => $cust_bill->invnum, - 'no_coupon' => 1, - ); + my %substitutions = (); + $substitutions{invnum} = $opt->{cust_bill}->invnum if $opt->{cust_bill}; - if ( my $mode = $conf->config('payment_receipt_statement_mode') ) { - $opt{'mode'} = $mode; - } else { - # backward compatibility, no good fix for this yet as some people may - # still have "invoice_latex_statement" and such options - $opt{'template'} = 'statement'; - $opt{'notice_name'} = 'Statement'; + my $msg_template = qsearchs('msg_template',{ msgnum => $msgnum}); + unless ($msg_template) { + warn "send_receipt could not load msg_template"; + return; + } + + my $cust_msg = $msg_template->prepare( + 'cust_main' => $cust_main, + 'object' => $self, + 'from_config' => 'payment_receipt_from', + 'substitutions' => \%substitutions, + 'msgtype' => 'receipt', + ); + $error = $cust_msg ? $cust_msg->insert : 'error preparing msg_template'; + if ($error) { + warn "send_receipt: $error"; + return; + } + + my $queue = new FS::queue { + 'job' => 'FS::cust_msg::process_send', + 'paynum' => $self->paynum, + 'custnum' => $cust_main->custnum, + }; + $error = $queue->insert( $cust_msg->custmsgnum ); + + } + else { + my $queue = new FS::queue { + 'job' => 'FS::cust_bill::queueable_email', + 'paynum' => $self->paynum, + 'custnum' => $cust_main->custnum, + }; + + my %opt = ( + 'invnum' => $cust_bill->invnum, + 'no_coupon' => 1, + ); + + if ( my $mode = $conf->config('payment_receipt_statement_mode') ) { + $opt{'mode'} = $mode; + } else { + # backward compatibility, no good fix for this yet as some people may + # still have "invoice_latex_statement" and such options + $opt{'template'} = 'statement'; + $opt{'notice_name'} = 'Statement'; + } + + $error = $queue->insert(%opt); } - $error = $queue->insert(%opt); + } diff --git a/httemplate/config/config-process.cgi b/httemplate/config/config-process.cgi index 3d57b310c..d84edce00 100644 --- a/httemplate/config/config-process.cgi +++ b/httemplate/config/config-process.cgi @@ -75,7 +75,7 @@ configCell.innerHTML = <% $value |js_string %>; % } elsif ( $type eq 'select-sub' && ! $i->multiple ) { configCell.innerHTML = - <% $conf->config($i->key, $agentnum) |js_string %> + ': ' + + <% $conf->exists($i->key, $agentnum) ? $conf->config($i->key, $agentnum) : '' |js_string %> + ': ' + <% &{ $i->option_sub }( $conf->config($i->key, $agentnum) ) |js_string %>; % } else { //alert('unknown type <% $type %>'); @@ -164,7 +164,7 @@ foreach my $type ( ref($i->type) ? @{$i->type} : $i->type ) { or ( $type =~ /^select(-(sub|part_svc|part_pkg|pkg_class|agent))?$/ || $i->multiple ) ) { - if ( scalar(@{[ $cgi->param($i->key.$n) ]}) ) { + if ( scalar(@{[ $cgi->param($i->key.$n) ]}) && $cgi->param($i->key.$n) ne '' ) { my $error = &{$i->validate}([ $cgi->param($i->key.$n) ], $n) if $i->validate; push @error, $error if $error; $conf->set($i->key, join("\n", @{[ $cgi->param($i->key.$n) ]} ), $agentnum); -- cgit v1.2.1 From 4f1f53a0b57e501794eb5262f284c6b88ae7e677 Mon Sep 17 00:00:00 2001 From: Ivan Kohler Date: Tue, 8 May 2018 12:50:11 -0700 Subject: termination fields --- httemplate/search/report_cdr.html | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/httemplate/search/report_cdr.html b/httemplate/search/report_cdr.html index ef5447838..4c16f74c0 100644 --- a/httemplate/search/report_cdr.html +++ b/httemplate/search/report_cdr.html @@ -189,8 +189,16 @@ die "access denied" my @fields = fields('cdr'); push @fields, 'ratename'; +push @fields, map "cdr_termination.$_", qw( rated_price rated_seconds rated_minutes rated_granularity status svcnum ); + my $labels = FS::cdr->table_info->{'fields'}; $labels->{ratename} = 'Rate plan'; +$labels->{'cdr_termination.rated_price'} = 'Termination rated price'; +$labels->{'cdr_termination.rated_seconds'} = 'Termination rated seconds'; +$labels->{'cdr_termination.rated_minutes'} = 'Termination rated minutes'; +$labels->{'cdr_termination.rated_granularity'} = 'Termination rated granularity'; +$labels->{'cdr_termination.status'} = 'Termination status'; +$labels->{'cdr_termination.svcnum'} = 'Termination service'; my $conf = new FS::Conf; my $default_phone_countrycode = -- cgit v1.2.1 From d2cc614910c251801564615a268c7ca3bdb2c954 Mon Sep 17 00:00:00 2001 From: Ivan Kohler Date: Tue, 8 May 2018 15:43:40 -0700 Subject: show account services in address block list too, RT#79825 --- httemplate/browse/addr_block.cgi | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/httemplate/browse/addr_block.cgi b/httemplate/browse/addr_block.cgi index ba40bfd43..9e7ef2af2 100644 --- a/httemplate/browse/addr_block.cgi +++ b/httemplate/browse/addr_block.cgi @@ -17,12 +17,13 @@ '', ], 'fields' => [ 'NetAddr', - sub { my $block = shift; - my $router = $block->router; + sub { my $b = shift; + my $router = $b->router; my $result = ''; if ($router) { - $result .= $router->routername. ' ('; - $result .= scalar($block->svc_broadband). ' services)'; + $result .= $router->routername. ' ('. + scalar($b->svc_broadband). ' broadband, '. + scalar($b->svc_acct). ' account services)'; } $result; }, -- cgit v1.2.1 From 01c9143a54bf5e1513537547fd362822f58d1e2a Mon Sep 17 00:00:00 2001 From: Christopher Burger Date: Fri, 11 May 2018 11:29:36 -0400 Subject: RT# 79737 - Added ability to us a cc surcharge of a flat fee. --- FS/FS/ClientAPI/MasonComponent.pm | 1 + FS/FS/ClientAPI/MyAccount.pm | 1 + FS/FS/Conf.pm | 8 ++++++++ FS/FS/cust_main/Billing_Realtime.pm | 22 ++++++++++++++++------ httemplate/elements/tr-amount_fee.html | 3 +++ httemplate/elements/tr-select-payment_options.html | 5 +++++ httemplate/misc/payment.cgi | 5 +++++ 7 files changed, 39 insertions(+), 6 deletions(-) diff --git a/FS/FS/ClientAPI/MasonComponent.pm b/FS/FS/ClientAPI/MasonComponent.pm index 3a4bfe133..d615c271c 100644 --- a/FS/FS/ClientAPI/MasonComponent.pm +++ b/FS/FS/ClientAPI/MasonComponent.pm @@ -63,6 +63,7 @@ my %session_callbacks = ( 'process-skip_first' => $conf->exists('selfservice_process-skip_first'), 'num_payments' => scalar($cust_main->cust_pay), 'surcharge_percentage' => scalar($conf->config('credit-card-surcharge-percentage', $cust_main->agentnum)), + 'surcharge_flatfee' => scalar($conf->config('credit-card-surcharge-flatfee', $cust_main->agentnum)), ); @$argsref = ( %args ); diff --git a/FS/FS/ClientAPI/MyAccount.pm b/FS/FS/ClientAPI/MyAccount.pm index 66697efb5..e4fef9554 100644 --- a/FS/FS/ClientAPI/MyAccount.pm +++ b/FS/FS/ClientAPI/MyAccount.pm @@ -921,6 +921,7 @@ sub payment_info { $return{paybatch} = $return{payunique}; #back compat $return{credit_card_surcharge_percentage} = $conf->config('credit-card-surcharge-percentage', $cust_main->agentnum); + $return{credit_card_surcharge_flatfee} = $conf->config('credit-card-surcharge-flatfee', $cust_main->agentnum); return { 'error' => '', %return, diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm index 2f8ac243d..5c6c411b3 100644 --- a/FS/FS/Conf.pm +++ b/FS/FS/Conf.pm @@ -780,6 +780,14 @@ my $validate_email = sub { $_[0] =~ 'per_agent' => 1, }, + { + 'key' => 'credit-card-surcharge-flatfee', + 'section' => 'credit_cards', + 'description' => 'Add a credit card surcharge to invoices, as a flat fee.', + 'type' => 'text', + 'per_agent' => 1, + }, + { 'key' => 'discount-show-always', 'section' => 'invoicing', diff --git a/FS/FS/cust_main/Billing_Realtime.pm b/FS/FS/cust_main/Billing_Realtime.pm index f16752ba4..75c310e7a 100644 --- a/FS/FS/cust_main/Billing_Realtime.pm +++ b/FS/FS/cust_main/Billing_Realtime.pm @@ -421,6 +421,8 @@ sub realtime_bop { $options{amount} = $amount; } + return '' unless $options{amount} > 0; + # set fields from passed cust_payby _bop_cust_payby_options(\%options); @@ -454,16 +456,24 @@ sub realtime_bop { if $conf->config('credit-card-surcharge-percentage', $self->agentnum) && $options{method} eq 'CC'; + my $cc_surcharge_flat = 0; + $cc_surcharge_flat = $conf->config('credit-card-surcharge-flatfee', $self->agentnum) + if $conf->config('credit-card-surcharge-flatfee', $self->agentnum) + && $options{method} eq 'CC'; + # always add cc surcharge if called from event - if($options{'cc_surcharge_from_event'} && $cc_surcharge_pct > 0) { - $cc_surcharge = $options{'amount'} * $cc_surcharge_pct / 100; + if($options{'cc_surcharge_from_event'} && ($cc_surcharge_pct > 0 || $cc_surcharge_flat > 0)) { + if ($options{'amount'} > 0) { + $cc_surcharge = ($options{'amount'} * ($cc_surcharge_pct / 100)) + $cc_surcharge_flat; $options{'amount'} += $cc_surcharge; $options{'amount'} = sprintf("%.2f", $options{'amount'}); # round (again)? + } } - elsif($cc_surcharge_pct > 0) { # we're called not from event (i.e. from a - # payment screen), so consider the given - # amount as post-surcharge - $cc_surcharge = $options{'amount'} - ($options{'amount'} / ( 1 + $cc_surcharge_pct/100 )); + elsif($cc_surcharge_pct > 0 || $cc_surcharge_flat > 0) { + # we're called not from event (i.e. from a + # payment screen), so consider the given + # amount as post-surcharge + $cc_surcharge = $options{'amount'} - (($options{'amount'} - $cc_surcharge_flat) / ( 1 + $cc_surcharge_pct/100 )) if $options{'amount'} > 0; } $cc_surcharge = sprintf("%.2f",$cc_surcharge) if $cc_surcharge > 0; diff --git a/httemplate/elements/tr-amount_fee.html b/httemplate/elements/tr-amount_fee.html index 9c13f5952..9e6d9e96d 100644 --- a/httemplate/elements/tr-amount_fee.html +++ b/httemplate/elements/tr-amount_fee.html @@ -94,6 +94,9 @@ if ( $amount > 0 ) { $amount += $amount * $opt{'surcharge_percentage'}/100 if $opt{'surcharge_percentage'} > 0; + $amount += $opt{'surcharge_flatfee'} + if $opt{'surcharge_flatfee'} > 0; + $amount = sprintf("%.2f", $amount); } diff --git a/httemplate/elements/tr-select-payment_options.html b/httemplate/elements/tr-select-payment_options.html index 2304c22d0..8859b9b36 100644 --- a/httemplate/elements/tr-select-payment_options.html +++ b/httemplate/elements/tr-select-payment_options.html @@ -17,6 +17,11 @@ Example: ? scalar($conf->config('credit-card-surcharge-percentage', $cust_main->agentnum)) : 0 ), + 'surcharge_flatfee' => + ( $payby eq 'CARD' + ? scalar($conf->config('credit-card-surcharge-flatfee', $cust_main->agentnum)) + : 0 + ), ) diff --git a/httemplate/misc/payment.cgi b/httemplate/misc/payment.cgi index 5bfa29d70..de060b010 100644 --- a/httemplate/misc/payment.cgi +++ b/httemplate/misc/payment.cgi @@ -25,6 +25,11 @@ ? scalar($conf->config('credit-card-surcharge-percentage', $cust_main->agentnum)) : 0 ), + 'surcharge_flatfee:Q' => + ( $payby eq 'CARD' + ? scalar($conf->config('credit-card-surcharge-flatfee', $cust_main->agentnum)) + : 0 + ), &> % if ( $conf->exists('part_pkg-term_discounts') ) { -- cgit v1.2.1 From 715bfdf1b5b98a5431112d95b4382730e817674c Mon Sep 17 00:00:00 2001 From: Ivan Kohler Date: Fri, 11 May 2018 11:11:57 -0700 Subject: should log to our db --- FS/FS/cust_main/Billing_Realtime.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/FS/FS/cust_main/Billing_Realtime.pm b/FS/FS/cust_main/Billing_Realtime.pm index f16752ba4..dcf5b7b5d 100644 --- a/FS/FS/cust_main/Billing_Realtime.pm +++ b/FS/FS/cust_main/Billing_Realtime.pm @@ -1193,6 +1193,7 @@ sub _realtime_bop_result { "resolved - error updating status for paypendingnum ". $cust_pay_pending->paypendingnum. ": $cpp_done_err \n"; warn $e; + #XXX internal system log $e (what's going on?) $perror = "$e ($perror)"; } -- cgit v1.2.1 From 18bc864bfbf806ea49323094f2f571e9cefd0498 Mon Sep 17 00:00:00 2001 From: Christopher Burger Date: Fri, 11 May 2018 20:43:48 -0400 Subject: RT# 79737 - fixed error in code --- httemplate/misc/payment.cgi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/httemplate/misc/payment.cgi b/httemplate/misc/payment.cgi index de060b010..4f6f7ef75 100644 --- a/httemplate/misc/payment.cgi +++ b/httemplate/misc/payment.cgi @@ -25,7 +25,7 @@ ? scalar($conf->config('credit-card-surcharge-percentage', $cust_main->agentnum)) : 0 ), - 'surcharge_flatfee:Q' => + 'surcharge_flatfee' => ( $payby eq 'CARD' ? scalar($conf->config('credit-card-surcharge-flatfee', $cust_main->agentnum)) : 0 -- cgit v1.2.1 From 88371c24e0b727646f0b01288f01439a06967fe7 Mon Sep 17 00:00:00 2001 From: Christopher Burger Date: Mon, 14 May 2018 10:21:15 -0400 Subject: RT# 34134 - fixed error with credit surchage not attached to total --- httemplate/elements/tr-amount_fee.html | 32 +++++++++++++---- httemplate/elements/tr-select-payment_options.html | 41 ++++++++++++++++++++-- 2 files changed, 63 insertions(+), 10 deletions(-) diff --git a/httemplate/elements/tr-amount_fee.html b/httemplate/elements/tr-amount_fee.html index 9e6d9e96d..3ad2cdf06 100644 --- a/httemplate/elements/tr-amount_fee.html +++ b/httemplate/elements/tr-amount_fee.html @@ -8,7 +8,7 @@ VALUE = "<% $amount %>" SIZE = 8 STYLE = "text-align:right;" -% if ( $fee ) { +% if ( $fee || $surcharge ) { onChange = "amount_changed(this)" onKeyDown = "amount_changed(this)" onKeyUp = "amount_changed(this)" @@ -27,18 +27,24 @@ <% length($amount) ? $money_char. sprintf('%.2f', ($fee_display eq 'add') ? $amount + $fee : $amount - $fee ) : '' %> <% $fee_display eq 'add' ? 'TOTAL' : 'AVAILABLE' %> +% } +% if ( $surcharge ) { + + + + 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)', &> + +

+

+> + + +<& /elements/cust_payby_new.html, + 'cust_payby' => \@cust_payby, + 'curr_value' => $custpaybynum, +&> + +
+
+ % } else { + % } +

+ <& /elements/tr-select-reason.html, 'field' => 'reasonnum', 'reason_class' => 'F', diff --git a/httemplate/edit/process/cust_refund.cgi b/httemplate/edit/process/cust_refund.cgi index 0a3d55036..77da8d5d2 100755 --- a/httemplate/edit/process/cust_refund.cgi +++ b/httemplate/edit/process/cust_refund.cgi @@ -71,6 +71,7 @@ if ( (my $custpaybynum = scalar($cgi->param('custpaybynum'))) > 0 ) { $paycvv = $cust_payby->paycvv; # pass it if we got it, running a transaction will clear it ( $month, $year ) = $cust_payby->paydate_mon_year; $payname = $cust_payby->payname; + $cgi->param(-name=>"paytype", -value=>$cust_payby->paytype) unless $cgi->param("paytype"); } else { @@ -192,8 +193,9 @@ if ( (my $custpaybynum = scalar($cgi->param('custpaybynum'))) > 0 ) { my $refund = "$1$2"; $cgi->param('paynum') =~ /^(\d*)$/ or die "Illegal paynum!"; my $paynum = $1; - my $paydate = $cgi->param('exp_year'). '-'. $cgi->param('exp_month'). '-01'; - $options{'paydate'} = $paydate if $paydate =~ /^\d{2,4}-\d{1,2}-01$/; + my $paydate; + if ($cust_payby->paydate) { $paydate = "$year-$month-01"; } + else { $paydate = "2037-12-01"; } if ( $cgi->param('batch') ) { @@ -201,7 +203,7 @@ if ( (my $custpaybynum = scalar($cgi->param('custpaybynum'))) > 0 ) { 'payby' => $payby, 'amount' => $refund, 'payinfo' => $payinfo, - 'paydate' => "$year-$month-01", + 'paydate' => $paydate, 'payname' => $payname, 'paycode' => 'C', map { $_ => scalar($cgi->param($_)) } diff --git a/httemplate/elements/cust_payby_new.html b/httemplate/elements/cust_payby_new.html new file mode 100644 index 000000000..7ed049686 --- /dev/null +++ b/httemplate/elements/cust_payby_new.html @@ -0,0 +1,222 @@ +% my $auto = 0; +% if ( $payby eq 'CARD' ) { +% +% my( $payinfo, $paycvv, $month, $year ) = ( '', '', '', '' ); +% my $payname = $cust_main->first. ' '. $cust_main->getfield('last'); +% my $location = $cust_main->bill_location; + + + + + + + + + + + + + + + <& /elements/location.html, + 'object' => $location, + 'no_asterisks' => 1, + 'address1_label' => emt('Card billing address'), + &> + +% } elsif ( $payby eq 'CHEK' ) { +% +% my( $account, $aba, $branch, $payname, $ss, $paytype, $paystate, +% $stateid, $stateid_state ) +% = ( '', '', '', '', '', '', '', '', '' ); +% +% #false laziness w/{edit,view}/cust_main/billing.html +% my $routing_label = $conf->config('echeck-country') eq 'US' +% ? 'ABA/Routing number' +% : 'Routing number'; +% my $routing_size = $conf->config('echeck-country') eq 'CA' ? 4 : 10; +% my $routing_maxlength = $conf->config('echeck-country') eq 'CA' ? 3 : 9; + + + + + + + + + + + + + +% if ( $conf->config('echeck-country') eq 'CA' ) { + + + + +% } + + + + + +% if ( $conf->exists('show_bankstate') ) { + + + + +% } else { + +% } + +% if ( $conf->exists('show_ss') ) { + + + + +% } else { + +% } + +% if ( $conf->exists('show_stateid') ) { + + + + + + +% } else { + + +% } + +% } #end CARD/CHEK-specific section + + + + + + + + + + +<%once> + +my %weight = ( + 1 => 'Primary', + 2 => 'Secondary', + 3 => 'Tertiary', + 4 => 'Fourth', + 5 => 'Fifth', + 6 => 'Sixth', + 7 => 'Seventh', +); + + + +<%init> + +my %opt = @_; + +my @cust_payby = @{$opt{cust_payby}}; + +my %type = ( 'CARD' => 'credit card', + 'CHEK' => 'electronic check (ACH)', + ); + +$cgi->param('payby') =~ /^(CARD|CHEK)$/ + or die "unknown payby ". $cgi->param('payby'); +my $payby = $1; + +$cgi->param('custnum') =~ /^(\d+)$/ + or die "illegal custnum ". $cgi->param('custnum'); +my $custnum = $1; + +my $cust_main = qsearchs( 'cust_main', { 'custnum'=>$custnum } ); +die "unknown custnum $custnum" unless $cust_main; + +my $balance = $cust_main->balance; + +my $payinfo = ''; + +my $conf = new FS::Conf; + +#false laziness w/selfservice make_payment.html shortcut for one-country +my %states = map { $_->state => 1 } + qsearch('cust_main_county', { + 'country' => $conf->config('countrydefault') || 'US' + } ); +my @states = sort { $a cmp $b } keys %states; + + \ No newline at end of file diff --git a/httemplate/elements/tr-select-cust_payby.html b/httemplate/elements/tr-select-cust_payby.html index e2b2e09d1..e5ace4d39 100644 --- a/httemplate/elements/tr-select-cust_payby.html +++ b/httemplate/elements/tr-select-cust_payby.html @@ -1,4 +1,4 @@ -% if ( scalar(@{ $opt{'cust_payby'} }) == 0 ) { +% if ( scalar(@{ $opt{'cust_payby'} }) == 0 ) { diff --git a/httemplate/misc/payment.cgi b/httemplate/misc/payment.cgi index 4f6f7ef75..80cb15d79 100644 --- a/httemplate/misc/payment.cgi +++ b/httemplate/misc/payment.cgi @@ -135,178 +135,10 @@ function change_batch_checkbox () { >
<% mt('Card number') |h %> + + + + + + + + +
+ <% mt('Exp.') |h %> + + / + +
+
<% mt('CVV2') |h %> + (<% mt('help') |h %>) +
<% mt('Exact name on card') |h %>
<% mt('Account number') |h %><% mt('Type') |h %>
<% mt($routing_label) |h %> + + (<% mt('help') |h %>) +
<% mt('Branch number') |h %> + +
<% mt('Bank name') |h %>
<% mt('Bank state') |h %><& /elements/select-state.html, + 'disable_empty' => 0, + 'empty_label' => emt('(choose)'), + 'state' => $paystate, + 'country' => $cust_main->country, + 'prefix' => 'pay', + &> +
+ <% mt('Account holder') |h %>
+ <% mt('Social security or tax ID #') |h %> +
+ <% mt('Account holder') |h %>
+ <% mt("Driver's license or state ID #") |h %> +
<% mt('State') |h %><& /elements/select-state.html, + 'disable_empty' => 0, + 'empty_label' => emt('(choose)'), + 'state' => $stateid_state, + 'country' => $cust_main->country, + 'prefix' => 'stateid_', + &> +
+ + <% mt('Remember this information') |h %> +
+ NAME="auto" VALUE="1" onClick="if (this.checked) { document.OneTrueForm.save.checked=true; }"> + <% mt("Charge future payments to this [_1] automatically",$type{$payby}) |h %> +% if ( @cust_payby ) { + <% mt('as') |h %> + +% } else { + +% } +
-% my $auto = 0; -% if ( $payby eq 'CARD' ) { -% -% my( $payinfo, $paycvv, $month, $year ) = ( '', '', '', '' ); -% my $payname = $cust_main->first. ' '. $cust_main->getfield('last'); -% my $location = $cust_main->bill_location; - - - - - - - - - - - - - - - <& /elements/location.html, - 'object' => $location, - 'no_asterisks' => 1, - 'address1_label' => emt('Card billing address'), - &> - -% } elsif ( $payby eq 'CHEK' ) { -% -% my( $account, $aba, $branch, $payname, $ss, $paytype, $paystate, -% $stateid, $stateid_state ) -% = ( '', '', '', '', '', '', '', '', '' ); -% -% #false laziness w/{edit,view}/cust_main/billing.html -% my $routing_label = $conf->config('echeck-country') eq 'US' -% ? 'ABA/Routing number' -% : 'Routing number'; -% my $routing_size = $conf->config('echeck-country') eq 'CA' ? 4 : 10; -% my $routing_maxlength = $conf->config('echeck-country') eq 'CA' ? 3 : 9; - - - - - - - - - - - - - -% if ( $conf->config('echeck-country') eq 'CA' ) { - - - - -% } - - - - - -% if ( $conf->exists('show_bankstate') ) { - - - - -% } else { - -% } - -% if ( $conf->exists('show_ss') ) { - - - - -% } else { - -% } - -% if ( $conf->exists('show_stateid') ) { - - - - - - -% } else { - - -% } - -% } #end CARD/CHEK-specific section - - - - - - - - - +<& /elements/cust_payby_new.html, + 'cust_payby' => \@cust_payby, + 'curr_value' => $custpaybynum, +&>
<% mt('Card number') |h %> - - - - - - - - -
- <% mt('Exp.') |h %> - - / - -
-
<% mt('CVV2') |h %> - (<% mt('help') |h %>) -
<% mt('Exact name on card') |h %>
<% mt('Account number') |h %><% mt('Type') |h %>
<% mt($routing_label) |h %> - - (<% mt('help') |h %>) -
<% mt('Branch number') |h %> - -
<% mt('Bank name') |h %>
<% mt('Bank state') |h %><& /elements/select-state.html, - 'disable_empty' => 0, - 'empty_label' => emt('(choose)'), - 'state' => $paystate, - 'country' => $cust_main->country, - 'prefix' => 'pay', - &> -
- <% mt('Account holder') |h %>
- <% mt('Social security or tax ID #') |h %> -
- <% mt('Account holder') |h %>
- <% mt("Driver's license or state ID #") |h %> -
<% mt('State') |h %><& /elements/select-state.html, - 'disable_empty' => 0, - 'empty_label' => emt('(choose)'), - 'state' => $stateid_state, - 'country' => $cust_main->country, - 'prefix' => 'stateid_', - &> -
- - <% mt('Remember this information') |h %> -
- NAME="auto" VALUE="1" onClick="if (this.checked) { document.OneTrueForm.save.checked=true; }"> - <% mt("Charge future payments to this [_1] automatically",$type{$payby}) |h %> -% if ( @cust_payby ) { - <% mt('as') |h %> - -% } else { - -% } -
@@ -355,13 +187,6 @@ my $payinfo = ''; my $conf = new FS::Conf; -#false laziness w/selfservice make_payment.html shortcut for one-country -my %states = map { $_->state => 1 } - qsearch('cust_main_county', { - 'country' => $conf->config('countrydefault') || 'US' - } ); -my @states = sort { $a cmp $b } keys %states; - my $payunique = "webui-payment-". time. "-$$-". rand() * 2**32; diff --git a/httemplate/misc/process/payment.cgi b/httemplate/misc/process/payment.cgi index 717d57c85..5620b5b4b 100644 --- a/httemplate/misc/process/payment.cgi +++ b/httemplate/misc/process/payment.cgi @@ -90,6 +90,7 @@ if ( (my $custpaybynum = scalar($cgi->param('custpaybynum'))) > 0 ) { $paycvv = $cust_payby->paycvv; # pass it if we got it, running a transaction will clear it ( $month, $year ) = $cust_payby->paydate_mon_year; $payname = $cust_payby->payname; + $cgi->param(-name=>"paytype", -value=>$cust_payby->paytype) unless $cgi->param("paytype"); } else { @@ -208,6 +209,10 @@ if ( (my $custpaybynum = scalar($cgi->param('custpaybynum'))) > 0 ) { my $error = ''; my $paynum = ''; +my $paydate; +if ($cust_payby->paydate) { $paydate = "$year-$month-01"; } +else { $paydate = "2037-12-01"; } + if ( $cgi->param('batch') ) { $error = 'Prepayment discounts not supported with batched payments' @@ -217,7 +222,7 @@ if ( $cgi->param('batch') ) { 'payby' => $payby, 'amount' => $amount, 'payinfo' => $payinfo, - 'paydate' => "$year-$month-01", + 'paydate' => $paydate, 'payname' => $payname, map { $_ => scalar($cgi->param($_)) } @{$payby2fields{$payby}} -- cgit v1.2.1 From 381992561d7b1a88e05d49d3e474a2fad25873c7 Mon Sep 17 00:00:00 2001 From: Mitch Jackson Date: Wed, 13 Jun 2018 02:25:09 -0500 Subject: RT# 32234 Allow unmask of SSN/DL# --- FS/FS/AccessRight.pm | 3 +- httemplate/edit/cust_main/name.html | 19 ++++++++- httemplate/edit/cust_main/stateid.html | 7 +++- httemplate/elements/link-replace_element_text.html | 45 ++++++++++++++++++++++ httemplate/view/cust_main/contacts.html | 30 +++++++++++++-- 5 files changed, 96 insertions(+), 8 deletions(-) create mode 100644 httemplate/elements/link-replace_element_text.html diff --git a/FS/FS/AccessRight.pm b/FS/FS/AccessRight.pm index 471e32aff..1b581b247 100644 --- a/FS/FS/AccessRight.pm +++ b/FS/FS/AccessRight.pm @@ -156,6 +156,8 @@ tie my %rights, 'Tie::IxHash', 'View package definition costs', #NEWNEW 'Change package start date', 'Change package contract end date', + 'Unmask customer DL', + 'Unmask customer SSN', ], ### @@ -509,4 +511,3 @@ L, L, L =cut 1; - diff --git a/httemplate/edit/cust_main/name.html b/httemplate/edit/cust_main/name.html index 713f54cdb..c1078d46a 100644 --- a/httemplate/edit/cust_main/name.html +++ b/httemplate/edit/cust_main/name.html @@ -1,7 +1,17 @@ <%def .namepart> -% my ($field, $value, $label, $extra) = @_; +% my ($field, $value, $label, $extra, $unmask_field) = @_;

> +% if ( +% ref $unmask_field +% && !$unmask_field->{unmask_ss} +% && $FS::CurrentUser::CurrentUser->access_right( $unmask_field->{access_right} ) +% ) { + <& /elements/link-replace_element_text.html, { + target_id => $unmask_field->{target_id}, + replace_text => $unmask_field->{replace_text}, + } &> +% }
<% emt($label) %>
@@ -13,7 +23,12 @@ <& .namepart, 'first', $cust_main->first, 'First' &> % if ( $conf->exists('show_ss') ) {   - <& .namepart, 'ss', $ss, 'SS#', "SIZE=11" &> + <& .namepart, 'ss', $ss, 'SS#', "SIZE=11 ID='ss'", { + target_id => 'ss', + replace_text => $cust_main->ss, + access_right => 'Unmask customer SSN', + unmask_ss => $conf->exists('unmask_ss'), + } &> % } else { % } diff --git a/httemplate/edit/cust_main/stateid.html b/httemplate/edit/cust_main/stateid.html index 3500d631c..cc0890fe1 100644 --- a/httemplate/edit/cust_main/stateid.html +++ b/httemplate/edit/cust_main/stateid.html @@ -1,7 +1,12 @@ % if ( $conf->exists('show_stateid') ) { <% $stateid_label %> - + + +% if ( $FS::CurrentUser::CurrentUser->access_right( 'Unmask customer DL' )) { + <& /elements/link-replace_element_text.html, {target_id => 'stateid', replace_text => $cust_main->stateid} &> +% } + <& /elements/select-state.html, state => $cust_main->stateid_state, country => $cust_main->country, # how does this work on new customer? diff --git a/httemplate/elements/link-replace_element_text.html b/httemplate/elements/link-replace_element_text.html new file mode 100644 index 000000000..8e611954c --- /dev/null +++ b/httemplate/elements/link-replace_element_text.html @@ -0,0 +1,45 @@ +<%doc> + +Display a link with javascript to replace text within a element. + +Usage: + +<& /elements/link-replace_element_text.html, { + target_id => 'input_id', + replace_text => 'hello', + + element_type => 'input', # Uses jquery val() method to replace text + element_type => 'div', # Uses jquery text() method to replace text + + href => ... + style => ... + class => ... + } +&> + + + +<%init> + +die "template call requires a parameter hashref" unless ref $_[0]; + +# Defaults that can be overridden in param hashref +my %param = ( + target_id => 'SPECIFY_AN_INPUT_ELEMENT_ID', + replace_text => 'REPLACEMENT_TEXT_FOR_INPUT_ELEMENT', + element_type => 'input', + + link_text => '%#x25C1;', # ◁ + href => 'javascript:void(0)', + style => 'text-decoration:none;', + class => undef, + + %{ $_[0] }, +); +$param{jmethod} = $param{element_type} eq 'input' ? 'val' : 'text'; + diff --git a/httemplate/view/cust_main/contacts.html b/httemplate/view/cust_main/contacts.html index 1660c1c22..367659293 100644 --- a/httemplate/view/cust_main/contacts.html +++ b/httemplate/view/cust_main/contacts.html @@ -29,9 +29,20 @@ <% $cust_main->contact |h %> % if ( $conf->exists('show_ss') ) { <% mt('SS#') |h %> - <% $conf->exists('unmask_ss') - ? $cust_main->ss - : $cust_main->masked('ss') || ' ' %> + + + <% $conf->exists('unmask_ss') + ? $cust_main->ss + : $cust_main->masked('ss') || ' ' %> +% if ( !$conf->exists('unmask_ss') && $FS::CurrentUser::CurrentUser->access_right('Unmask customer SSN')) { + <& /elements/link-replace_element_text.html, { + target_id => 'ss_span', + replace_text => $cust_main->ss, + element_type => 'span' + } &> +% } + + % } % if ( $conf->exists('cust_main-enable_spouse') and @@ -172,7 +183,18 @@ <% $stateid_label %> - <% $cust_main->masked('stateid') || ' ' %> + + + <% $cust_main->masked('stateid') || ' ' %> +% if ( $FS::CurrentUser::CurrentUser->access_right('Unmask customer DL')) { + <& /elements/link-replace_element_text.html, { + target_id => 'stateid_span', + replace_text => $cust_main->stateid, + element_type => 'span' + } &> +% } + + <% $stateid_state_label %> <% $cust_main->stateid_state || ' ' %> -- cgit v1.2.1 From 965826f80175e6e09b851bdd119e955b6c3783d4 Mon Sep 17 00:00:00 2001 From: Mitch Jackson Date: Thu, 14 Jun 2018 14:46:38 -0500 Subject: RT# 80543 Crash creating new quotation --- FS/FS/Template_Mixin.pm | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/FS/FS/Template_Mixin.pm b/FS/FS/Template_Mixin.pm index b9514fee1..578e5a192 100644 --- a/FS/FS/Template_Mixin.pm +++ b/FS/FS/Template_Mixin.pm @@ -3139,7 +3139,9 @@ sub _items_fee { my @cust_bill_pkg = grep { $_->feepart } $self->cust_bill_pkg; my $escape_function = $options{escape_function}; - my $locale = $self->cust_main->locale; + my $locale = $self->quotationnum + ? $self->prospect_main->locale + : $self->cust_main->locale; my @items; foreach my $cust_bill_pkg (@cust_bill_pkg) { -- cgit v1.2.1 From b4883ac11a0ddabd2b78a7451fe3634f9510f5f7 Mon Sep 17 00:00:00 2001 From: Mitch Jackson Date: Thu, 14 Jun 2018 16:31:42 -0500 Subject: RT# 31964 Display more information on prospect contacts --- httemplate/search/prospect_main.html | 1 - httemplate/view/prospect_main.html | 17 +++++++++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/httemplate/search/prospect_main.html b/httemplate/search/prospect_main.html index d65d4d19d..0eb45f338 100644 --- a/httemplate/search/prospect_main.html +++ b/httemplate/search/prospect_main.html @@ -17,7 +17,6 @@ } $pm->prospect_contact ]; - '' }, sub { my $pr = shift->part_referral; diff --git a/httemplate/view/prospect_main.html b/httemplate/view/prospect_main.html index f4dd4146f..504a5a8ec 100644 --- a/httemplate/view/prospect_main.html +++ b/httemplate/view/prospect_main.html @@ -24,8 +24,21 @@ % foreach my $prospect_contact ( $prospect_main->prospect_contact ) { % my $contact = $prospect_contact->contact; - <% $prospect_contact->contact_classname %> Contact - <% $contact->line %> + <% $prospect_contact->contact_classname %> Contact + + <% $contact->line %>
+ +% for my $row ( $contact->contact_email ) { + +% } +% for my $row ( $contact->contact_phone ) { + +% } +% if ( $prospect_contact->comment ) { + +% } +
E-Mail:<% $row->emailaddress %>
<% $row->phone_type->typename %>:<% $row->phonenum_pretty %>
Comment:<% $prospect_contact->comment %>
+ %} -- cgit v1.2.1 From 618f8725c360f29941b9d1720eb01fea85403185 Mon Sep 17 00:00:00 2001 From: Christopher Burger Date: Fri, 15 Jun 2018 16:52:39 -0400 Subject: RT# 80175 - fixed error with ACH gateway not being selected. --- 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 810709357..e1d9ccf1d 100644 --- a/FS/FS/agent.pm +++ b/FS/FS/agent.pm @@ -294,7 +294,7 @@ sub payment_gateway { } } - my $cardtype_search = "AND cardtype != 'ACH'"; + my $cardtype_search = "AND ( cardtype IS NULL OR cardtype <> 'ACH')"; $cardtype_search = "AND cardtype = 'ACH'" if $options{method} eq 'ECHECK'; my $override = -- cgit v1.2.1 From 7bf6dafb0da4a525388a6f145dd0a904eddcec14 Mon Sep 17 00:00:00 2001 From: Mitch Jackson Date: Fri, 15 Jun 2018 21:14:44 -0500 Subject: RT# 32233 Show unmask widget only if a value exists to unmask --- httemplate/edit/cust_main/name.html | 3 ++- httemplate/edit/cust_main/stateid.html | 2 +- httemplate/view/cust_main/contacts.html | 11 +++++++++-- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/httemplate/edit/cust_main/name.html b/httemplate/edit/cust_main/name.html index c1078d46a..120475b92 100644 --- a/httemplate/edit/cust_main/name.html +++ b/httemplate/edit/cust_main/name.html @@ -3,7 +3,8 @@
> % if ( -% ref $unmask_field +% $value +% && ref $unmask_field % && !$unmask_field->{unmask_ss} % && $FS::CurrentUser::CurrentUser->access_right( $unmask_field->{access_right} ) % ) { diff --git a/httemplate/edit/cust_main/stateid.html b/httemplate/edit/cust_main/stateid.html index cc0890fe1..0f288099b 100644 --- a/httemplate/edit/cust_main/stateid.html +++ b/httemplate/edit/cust_main/stateid.html @@ -3,7 +3,7 @@ <% $stateid_label %> -% if ( $FS::CurrentUser::CurrentUser->access_right( 'Unmask customer DL' )) { +% if ( $stateid && $FS::CurrentUser::CurrentUser->access_right( 'Unmask customer DL' )) { <& /elements/link-replace_element_text.html, {target_id => 'stateid', replace_text => $cust_main->stateid} &> % } diff --git a/httemplate/view/cust_main/contacts.html b/httemplate/view/cust_main/contacts.html index 367659293..11efcd568 100644 --- a/httemplate/view/cust_main/contacts.html +++ b/httemplate/view/cust_main/contacts.html @@ -34,7 +34,11 @@ <% $conf->exists('unmask_ss') ? $cust_main->ss : $cust_main->masked('ss') || ' ' %> -% if ( !$conf->exists('unmask_ss') && $FS::CurrentUser::CurrentUser->access_right('Unmask customer SSN')) { +% if ( +% $cust_main->ss +% && !$conf->exists('unmask_ss') +% && $FS::CurrentUser::CurrentUser->access_right('Unmask customer SSN') +% ) { <& /elements/link-replace_element_text.html, { target_id => 'ss_span', replace_text => $cust_main->ss, @@ -186,7 +190,10 @@ <% $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( + '' + + val + + '' + ); + }); + 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'} &> <& /elements/select-tiered.html, prefix => 'router_', tiers => [ @@ -58,14 +134,20 @@ function clearhint_ip_addr (what) { <& /elements/tr-td-label.html, label => ($opt{'ip_addr_label'} || 'IP address'), required => $opt{'ip_addr_required'} &> -% #warn Dumper \%fixed; % if ( exists $fixed{$ip_field} ) { <% $opt{'ip_addr'} || '' %> % } % else { - + + + % } % #can't quite handle CARD/CHEK on the same page yet, but very close @@ -144,9 +148,49 @@ function change_batch_checkbox () {

- + + + <& /elements/footer-cust_main.html &> <%once> @@ -174,6 +218,17 @@ $cgi->param('payby') =~ /^(CARD|CHEK)$/ or die "unknown payby ". $cgi->param('payby'); my $payby = $1; +my $validate_select_fields = "#payment_option, #invoice, #custpaybynum, "; +my $validate_input_fields = "#amount, input[name=payname], "; +if ($payby eq "CHEK") { + $validate_input_fields .= "input[name=payinfo1], input[name=payinfo2]"; + $validate_select_fields .= "select[name=paytype] "; +} +elsif ($payby eq "CARD") { + $validate_input_fields .= "input[name=payinfo], input[name=paycvv], input[name=address1], #city, #zip"; + $validate_select_fields .= "#state, #country "; +} + $cgi->param('custnum') =~ /^(\d+)$/ or die "illegal custnum ". $cgi->param('custnum'); my $custnum = $1; -- cgit v1.2.1 From e8740b7c8f772508c0a393d3c0de2b3c8531280d Mon Sep 17 00:00:00 2001 From: Mitch Jackson Date: Wed, 18 Jul 2018 19:03:57 +0000 Subject: RT# 32234 unmask_ss deprecated - drop from templates --- httemplate/edit/cust_main.cgi | 2 +- httemplate/edit/cust_main/name.html | 4 +--- httemplate/view/cust_main/contacts.html | 5 +---- 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/httemplate/edit/cust_main.cgi b/httemplate/edit/cust_main.cgi index 05bf4377a..56c396271 100755 --- a/httemplate/edit/cust_main.cgi +++ b/httemplate/edit/cust_main.cgi @@ -286,7 +286,7 @@ if ( $cgi->param('error') ) { $cust_main->paycvv($paycvv); } @invoicing_list = $cust_main->invoicing_list; - $ss = $conf->exists('unmask_ss') ? $cust_main->ss : $cust_main->masked('ss'); + $ss = $cust_main->masked('ss'); $stateid = $cust_main->masked('stateid'); } else { #new customer diff --git a/httemplate/edit/cust_main/name.html b/httemplate/edit/cust_main/name.html index 120475b92..0319cf027 100644 --- a/httemplate/edit/cust_main/name.html +++ b/httemplate/edit/cust_main/name.html @@ -5,7 +5,6 @@ % if ( % $value % && ref $unmask_field -% && !$unmask_field->{unmask_ss} % && $FS::CurrentUser::CurrentUser->access_right( $unmask_field->{access_right} ) % ) { <& /elements/link-replace_element_text.html, { @@ -28,7 +27,6 @@ target_id => 'ss', replace_text => $cust_main->ss, access_right => 'Unmask customer SSN', - unmask_ss => $conf->exists('unmask_ss'), } &> % } else { @@ -63,7 +61,7 @@ my $agentnum = $cust_main->agentnum if $cust_main->custnum; my $conf = FS::Conf->new; my $ss; -if ( $cgi->param('error') or $conf->exists('unmask_ss') ) { +if ( $cgi->param('error') ) { $ss = $cust_main->ss; } else { $ss = $cust_main->masked('ss'); diff --git a/httemplate/view/cust_main/contacts.html b/httemplate/view/cust_main/contacts.html index 11efcd568..efcf48ecc 100644 --- a/httemplate/view/cust_main/contacts.html +++ b/httemplate/view/cust_main/contacts.html @@ -31,12 +31,9 @@ <% mt('SS#') |h %> - <% $conf->exists('unmask_ss') - ? $cust_main->ss - : $cust_main->masked('ss') || ' ' %> + <% $cust_main->masked('ss') || ' ' %> % if ( % $cust_main->ss -% && !$conf->exists('unmask_ss') % && $FS::CurrentUser::CurrentUser->access_right('Unmask customer SSN') % ) { <& /elements/link-replace_element_text.html, { -- cgit v1.2.1 From b4fb50e7451aec55daff45a131ee0a2bd01a87b4 Mon Sep 17 00:00:00 2001 From: Ivan Kohler Date: Wed, 18 Jul 2018 14:47:32 -0700 Subject: fix phantom customer links --- httemplate/misc/timeworked.html | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/httemplate/misc/timeworked.html b/httemplate/misc/timeworked.html index a0cf74371..4d5c3efbd 100755 --- a/httemplate/misc/timeworked.html +++ b/httemplate/misc/timeworked.html @@ -113,12 +113,15 @@ foreach my $id ( map { /^transactionid(\d+)$/; $1; } $ticket->Load($ticketmap{$id}); $ticket{$ticketmap{$id}} = $ticket->Subject; $customers{$ticketmap{$id}} = - [ map { $_->Resolver->AsString } - grep { $_->Resolver->{'fstable'} eq 'cust_main' } - grep { $_->Scheme eq 'freeside' } - map { $_->TargetURI } - @{ $ticket->_Links('Base')->ItemsArrayRef } - ]; + [ map { $_->Resolver->AsString } + grep { $_->Resolver->{'fstable'} eq 'cust_main' } + grep { $_->Scheme eq 'freeside' } + map { $_->TargetURI } + grep { $_->BaseURI->Scheme eq 'fsck_com_rt' + && $_->BaseURI->ObjectType eq 'ticket' + } + @{ $ticket->_Links('Base')->ItemsArrayRef } + ]; } } -- cgit v1.2.1 From a7aed7a065ccf2206da434328123fae48e692229 Mon Sep 17 00:00:00 2001 From: Mitch Jackson Date: Wed, 18 Jul 2018 22:38:44 +0000 Subject: RT# 79705 fakesmtpserver - saved messages viewable with e-mail client --- bin/fakesmtpserver.pl | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/bin/fakesmtpserver.pl b/bin/fakesmtpserver.pl index 1f2ca3f31..5da6cb5cb 100755 --- a/bin/fakesmtpserver.pl +++ b/bin/fakesmtpserver.pl @@ -10,6 +10,9 @@ it could be updated to fork on client connections. When an e-mail is delivered, the TO and FROM are printed to STDOUT. The TO, FROM and MSG are saved to a file in $message_save_dir +Open a saved .eml file with Mozilla Thunderbird (or other mail clients) +to review e-mail with all html/pdf attachments + =cut use strict; @@ -33,21 +36,21 @@ while(my $conn = $server->accept()) { $client->process || next; - open my $fh, '>', $message_save_dir.'/'.time().'.txt' + open my $fh, '>', $message_save_dir.'/'.time().'.eml' or die "error: $!"; for my $f (qw/TO FROM/) { if (ref $client->{$f} eq 'ARRAY') { print "$f: $_\n" for @{$client->{$f}}; - print $fh "$f: $_\n" for @{$client->{$f}}; + # print $fh "$f: $_\n" for @{$client->{$f}}; } else { print "$f: $client->{$f}\n"; - print $fh "$f: $client->{$f}\n"; + # print $fh "$f: $client->{$f}\n"; } } - print $fh "\n\n$client->{MSG}\n"; + print $fh "$client->{MSG}\n"; print "\n"; close $fh; } -- cgit v1.2.1 From 9e3602f4523ff3f190bff5283ebecd913d6940ca Mon Sep 17 00:00:00 2001 From: Ivan Kohler Date: Wed, 18 Jul 2018 15:44:29 -0700 Subject: fix phantom customer links --- httemplate/misc/timeworked.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/httemplate/misc/timeworked.html b/httemplate/misc/timeworked.html index 4d5c3efbd..3ae0ac871 100755 --- a/httemplate/misc/timeworked.html +++ b/httemplate/misc/timeworked.html @@ -117,7 +117,7 @@ foreach my $id ( map { /^transactionid(\d+)$/; $1; } grep { $_->Resolver->{'fstable'} eq 'cust_main' } grep { $_->Scheme eq 'freeside' } map { $_->TargetURI } - grep { $_->BaseURI->Scheme eq 'fsck_com_rt' + grep { $_->BaseURI->Scheme eq 'fsck.com-rt' && $_->BaseURI->ObjectType eq 'ticket' } @{ $ticket->_Links('Base')->ItemsArrayRef } -- cgit v1.2.1 From ea749c5e875371d98bdcf173e19a2d57fa46481e Mon Sep 17 00:00:00 2001 From: Ivan Kohler Date: Wed, 18 Jul 2018 16:17:01 -0700 Subject: fix phantom customer links --- httemplate/misc/timeworked.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/httemplate/misc/timeworked.html b/httemplate/misc/timeworked.html index 3ae0ac871..24a0f5d40 100755 --- a/httemplate/misc/timeworked.html +++ b/httemplate/misc/timeworked.html @@ -117,8 +117,8 @@ foreach my $id ( map { /^transactionid(\d+)$/; $1; } grep { $_->Resolver->{'fstable'} eq 'cust_main' } grep { $_->Scheme eq 'freeside' } map { $_->TargetURI } - grep { $_->BaseURI->Scheme eq 'fsck.com-rt' - && $_->BaseURI->ObjectType eq 'ticket' + grep { $_->BaseURI->Scheme eq 'fsck.com-rt' + && $_->BaseURI->Resolver->ObjectType eq 'ticket' } @{ $ticket->_Links('Base')->ItemsArrayRef } ]; -- cgit v1.2.1 From 214c4e855a429f7a50dfd771b18c74ef7a34be58 Mon Sep 17 00:00:00 2001 From: Ivan Kohler Date: Wed, 18 Jul 2018 19:18:19 -0700 Subject: more careful --- bin/cust_main.restore-paymask | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/bin/cust_main.restore-paymask b/bin/cust_main.restore-paymask index c6c8a75ec..14dddd1b5 100755 --- a/bin/cust_main.restore-paymask +++ b/bin/cust_main.restore-paymask @@ -15,10 +15,14 @@ foreach my $cust_main ( my $custnum = $cust_main->custnum; + my $paydate = $cust_main->paydate; + my $paymask = FS::Record->scalar_sql(qq[ - SELECT paymask FROM h_cust_main WHERE custnum = $custnum AND history_action = 'replace_old' AND paymask IS NOT NULL AND paymask != 'N/A (tokenized)' ORDER BY historynum desc LIMIT 1 + SELECT paymask FROM h_cust_main WHERE custnum = $custnum AND history_action = 'replace_old' AND paymask IS NOT NULL AND paymask != 'N/A (tokenized)' AND paydate = '$paydate' ORDER BY historynum desc LIMIT 1 ]); + next unless length($paymask); + #dbh->do( print qq[UPDATE cust_main SET paymask = '$paymask' WHERE custnum = $custnum;] -- cgit v1.2.1 From 2e8c617c6ad1d4d9dc5672422e972f0ba4f79472 Mon Sep 17 00:00:00 2001 From: Ivan Kohler Date: Thu, 19 Jul 2018 08:21:00 -0700 Subject: fix phantom customer links (fix user links) --- rt/lib/RT/Record.pm | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/rt/lib/RT/Record.pm b/rt/lib/RT/Record.pm index 8f1b5be5e..b700fa75f 100755 --- a/rt/lib/RT/Record.pm +++ b/rt/lib/RT/Record.pm @@ -1282,9 +1282,12 @@ sub Customers { $self->{'Customers'} = $self->MemberOf->Clone; + my $RecordType = $self->RecordType; + my $uri_type = $RecordType eq 'Ticket' ? 'ticket' : "RT::$RecordType"; + $self->{'Customers'}->Limit( FIELD => 'Base', OPERATOR => 'STARTSWITH', - VALUE => 'fsck.com-rt://%/ticket/', + VALUE => 'fsck.com-rt://%/'.$uri_type.'/', ); for my $fstable (qw(cust_main cust_svc)) { -- cgit v1.2.1 From ed6b5876e77cd780c4054ed163b009899e2e5ecc Mon Sep 17 00:00:00 2001 From: Mitch Jackson Date: Fri, 20 Jul 2018 19:35:42 +0000 Subject: RT# 80624 Selfservice workaround for Bug on Win10 Edge Browser --- fs_selfservice/FS-SelfService/cgi/card.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs_selfservice/FS-SelfService/cgi/card.html b/fs_selfservice/FS-SelfService/cgi/card.html index dfe63710c..eb3e061c7 100644 --- a/fs_selfservice/FS-SelfService/cgi/card.html +++ b/fs_selfservice/FS-SelfService/cgi/card.html @@ -9,7 +9,7 @@ @@ -17,7 +17,7 @@ -- cgit v1.2.1 From 0d4a18f844844d3020f7dc3052baafed44cda564 Mon Sep 17 00:00:00 2001 From: Mitch Jackson Date: Thu, 26 Jul 2018 17:53:33 -0500 Subject: RT# 80624 Selfservice workaround for Bug on Win10 Edge Browser --- fs_selfservice/FS-SelfService/cgi/selfservice.cgi | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/fs_selfservice/FS-SelfService/cgi/selfservice.cgi b/fs_selfservice/FS-SelfService/cgi/selfservice.cgi index 6cf264c08..3dc69e142 100755 --- a/fs_selfservice/FS-SelfService/cgi/selfservice.cgi +++ b/fs_selfservice/FS-SelfService/cgi/selfservice.cgi @@ -726,9 +726,9 @@ sub payment_results { $cgi->param('paycvv') =~ /^\s*(.{0,4})\s*$/ or die "illegal CVV2"; my $paycvv = $1; - $cgi->param('month') =~ /^(\d{2})$/ or die "illegal month"; + $cgi->param('month') =~ /^(\d{2})/ or die "illegal month"; my $month = $1; - $cgi->param('year') =~ /^(\d{4})$/ or die "illegal year"; + $cgi->param('year') =~ /^(\d{4})/ or die "illegal year"; my $year = $1; $cgi->param('payname') =~ /^(.{0,80})$/ or die "illegal payname"; @@ -1325,5 +1325,3 @@ sub include { ); } - - -- cgit v1.2.1 From 25efd7207d9ea9be4bea1093917c0d8dc4b1e87a Mon Sep 17 00:00:00 2001 From: Christopher Burger Date: Fri, 27 Jul 2018 08:05:10 -0400 Subject: RT# 78356 - added speed test fields for broadband service and new modifier to get speed from fcc_477 form in package --- FS/FS/Schema.pm | 3 ++ FS/FS/cust_pkg.pm | 18 +++++++ FS/FS/part_export/saisei.pm | 77 +++++++++++++++++++-------- FS/FS/part_svc.pm | 4 +- FS/FS/part_svc_column.pm | 2 +- FS/FS/svc_broadband.pm | 15 +++++- httemplate/browse/part_svc.cgi | 1 + httemplate/edit/elements/part_svc_column.html | 19 +++++++ httemplate/edit/elements/svc_Common.html | 10 ++++ httemplate/edit/part_svc.cgi | 4 +- httemplate/edit/process/elements/process.html | 9 ++++ httemplate/edit/process/part_pkg.cgi | 32 +++++++++++ httemplate/edit/svc_broadband.cgi | 3 +- 13 files changed, 167 insertions(+), 30 deletions(-) diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm index e2c575eb5..0ada39fed 100644 --- a/FS/FS/Schema.pm +++ b/FS/FS/Schema.pm @@ -4890,6 +4890,9 @@ sub tables_hashref { 'suid', 'int', 'NULL', '', '', '', 'shared_svcnum', 'int', 'NULL', '', '', '', 'serviceid', 'varchar', 'NULL', 64, '', '',#srvexport/reportfields + 'speed_test_up', 'int', 'NULL', '', '', '', + 'speed_test_down', 'int', 'NULL', '', '', '', + 'speed_test_latency', 'int', 'NULL', '', '', '', ], 'primary_key' => 'svcnum', 'unique' => [ [ 'ip_addr' ], [ 'mac_addr' ] ], diff --git a/FS/FS/cust_pkg.pm b/FS/FS/cust_pkg.pm index d00f0397b..f29ab9fc0 100644 --- a/FS/FS/cust_pkg.pm +++ b/FS/FS/cust_pkg.pm @@ -5463,6 +5463,24 @@ sub fcc_477_count { } +=item fcc_477_record + +Returns a fcc_477 record based on option name. + +=cut + +sub fcc_477_record { + my ($self, $option_name) = @_; + + my $fcc_record = qsearchs({ + 'table' => 'part_pkg_fcc_option', + 'hashref' => { 'pkgpart' => $self->{Hash}->{pkgpart}, 'fccoptionname' => $option_name, }, + }); + + return ( $fcc_record ); + +} + =item tax_locationnum_sql Returns an SQL expression for the tax location for a package, based diff --git a/FS/FS/part_export/saisei.pm b/FS/FS/part_export/saisei.pm index 2fc112744..617a73226 100644 --- a/FS/FS/part_export/saisei.pm +++ b/FS/FS/part_export/saisei.pm @@ -128,9 +128,7 @@ END sub _export_insert { my ($self, $svc_broadband) = @_; - 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 $rateplan_name = $self->get_rateplan_name($svc_broadband); # check for existing rate plan my $existing_rateplan; @@ -217,15 +215,15 @@ sub _export_insert { sub _export_replace { my ($self, $svc_broadband) = @_; + $self->_export_insert($svc_broadband); return ''; } sub _export_delete { my ($self, $svc_broadband) = @_; - 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 $rateplan_name = $self->get_rateplan_name($svc_broadband); + my $username = $svc_broadband->{Hash}->{svcnum}; ## untie host to user @@ -247,25 +245,49 @@ sub _export_unsuspend { 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 $fcc_477_speeds; + if ($svc_part->{Hash}->{svc_broadband__speed_down} eq "down" || $svc_part->{Hash}->{svc_broadband__speed_up} eq "up") { + for my $type (qw( down up )) { + my $speed_type = "broadband_".$type."stream"; + foreach my $pkg_svc (FS::Record::qsearch({ + 'table' => 'pkg_svc', + 'select' => 'pkg_svc.*, part_pkg_fcc_option.fccoptionname, part_pkg_fcc_option.optionvalue', + 'addl_from' => ' LEFT JOIN part_pkg_fcc_option USING (pkgpart) ', + 'extra_sql' => " WHERE pkg_svc.svcpart = ".$svc_part->{Hash}->{svcpart}." AND pkg_svc.quantity > 0 AND part_pkg_fcc_option.fccoptionname = '".$speed_type."'", + })) { $fcc_477_speeds->{ + $pkg_svc->{Hash}->{pkgpart}}->{$speed_type} = $pkg_svc->{Hash}->{optionvalue} * 1000 unless !$pkg_svc->{Hash}->{optionvalue}; } + } + } + else { + $fcc_477_speeds->{1}->{broadband_downstream} = $svc_part->{Hash}->{"svc_broadband__speed_down"}; + $fcc_477_speeds->{1}->{broadband_upstream} = $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; + foreach my $key (keys %$fcc_477_speeds) { - # check for existing rate plan - my $existing_rateplan; - $existing_rateplan = $self->api_get_rateplan($rateplan_name) unless $self->{'__saisei_error'}; + $svc_part->{Hash}->{speed_down} = $fcc_477_speeds->{$key}->{broadband_downstream}; + $svc_part->{Hash}->{speed_up} = $fcc_477_speeds->{$key}->{broadband_upstream}; + $svc_part->{Hash}->{svc_broadband__speed_down} = $fcc_477_speeds->{$key}->{broadband_downstream}; + $svc_part->{Hash}->{svc_broadband__speed_up} = $fcc_477_speeds->{$key}->{broadband_upstream}; - # Modify the existing rate plan with new service data. - $self->api_modify_existing_rateplan($svc_broadband, $rateplan_name) unless ($self->{'__saisei_error'} || !$existing_rateplan); + my $temp_svc = $svc_part->{Hash}; + my $svc_broadband = {}; + map { if ($_ =~ /^svc_broadband__(.*)$/) { $svc_broadband->{Hash}->{$1} = $temp_svc->{$_}; } } keys %$temp_svc; - # 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); + my $rateplan_name = $self->get_rateplan_name($svc_broadband, $svc_part->{Hash}->{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; @@ -310,6 +332,19 @@ sub export_tower_sector { return $self->api_error; } +## creates the rateplan name +sub get_rateplan_name { + my ($self, $svc_broadband, $svc_name) = @_; + + my $service_part = FS::Record::qsearchs( 'part_svc', { 'svcpart' => $svc_broadband->{Hash}->{svcpart} } ) unless $svc_name; + my $service_name = $svc_name ? $svc_name : $service_part->{Hash}->{svc}; + + my $rateplan_name = $service_name . " " . $svc_broadband->{Hash}->{speed_down} . "-" . $svc_broadband->{Hash}->{speed_up}; + $rateplan_name =~ s/\s/_/g; + + return $rateplan_name; +} + =head1 Saisei API These methods allow access to the Saisei API using the credentials diff --git a/FS/FS/part_svc.pm b/FS/FS/part_svc.pm index 341559594..b82996e0d 100644 --- a/FS/FS/part_svc.pm +++ b/FS/FS/part_svc.pm @@ -873,10 +873,10 @@ sub process { map { my $f = $svcdb.'__'.$_; my $flag = $param->{ $f.'_flag' } || ''; #silence warnings - if ( $flag =~ /^[MAH]$/ ) { + if ( $flag =~ /^[MAHP]$/ ) { $param->{ $f } = delete( $param->{ $f.'_classnum' } ); } - if ( ( $flag =~ /^[MAHS]$/ or $_ eq 'usergroup' ) + if ( ( $flag =~ /^[MAHSP]$/ or $_ eq 'usergroup' ) and ref($param->{ $f }) ) { $param->{ $f } = join(',', @{ $param->{ $f } }); } diff --git a/FS/FS/part_svc_column.pm b/FS/FS/part_svc_column.pm index 75a2dfb1a..e055af35a 100644 --- a/FS/FS/part_svc_column.pm +++ b/FS/FS/part_svc_column.pm @@ -97,7 +97,7 @@ sub check { ; return $error if $error; - $self->columnflag =~ /^([DFSMAHX]?)$/ + $self->columnflag =~ /^([DFSMAHXP]?)$/ or return "illegal columnflag ". $self->columnflag; $self->columnflag(uc($1)); diff --git a/FS/FS/svc_broadband.pm b/FS/FS/svc_broadband.pm index b8b1a6eab..bd7bf9b13 100755 --- a/FS/FS/svc_broadband.pm +++ b/FS/FS/svc_broadband.pm @@ -107,8 +107,16 @@ sub table_info { 'fields' => { 'svcnum' => 'Service', 'description' => 'Descriptive label', - 'speed_down' => 'Download speed (Kbps)', - 'speed_up' => 'Upload speed (Kbps)', + 'speed_up' => { + 'label' => 'Upload speed (Kbps)', + 'type' => 'fcc_477_speed', + 'def_info' => 'both upload and download speed must be set to FCC 477 information if using that modifier', + }, + 'speed_down' => { + 'label' => 'Download speed (Kbps)', + 'type' => 'fcc_477_speed', + 'def_info' => 'both upload and download speed must be set to FCC 477 information if using that modifier', + }, 'ip_addr' => 'IP address', 'blocknum' => { 'label' => 'Address block', @@ -148,6 +156,9 @@ sub table_info { disable_inventory => 1, }, 'serviceid' => 'Torrus serviceid', #but is should be hidden + 'speed_test_up' => 'Speed test download (Kbps)', + 'speed_test_down' => 'Speed test upload (Kbps)', + 'speed_test_latency' => 'Speed test latency (ms)', }, }; } diff --git a/httemplate/browse/part_svc.cgi b/httemplate/browse/part_svc.cgi index b9474636d..222433db3 100755 --- a/httemplate/browse/part_svc.cgi +++ b/httemplate/browse/part_svc.cgi @@ -251,6 +251,7 @@ my %flag = ( 'A' => 'Automatically filled in from inventory', 'H' => 'Selected from hardware class', 'X' => 'Excluded', + 'P' => 'From package 477 information', ); my %search; diff --git a/httemplate/edit/elements/part_svc_column.html b/httemplate/edit/elements/part_svc_column.html index 816f3428b..bdbce7c79 100644 --- a/httemplate/edit/elements/part_svc_column.html +++ b/httemplate/edit/elements/part_svc_column.html @@ -15,6 +15,7 @@ To be called from part_svc.cgi. # don't allow the 'inventory' flags (M, A) to be chosen for # fields that aren't free-text my $inv_sub = sub { $_[0]->{disable_inventory} || $_[0]->{type} ne 'text' }; + tie my %flag, 'Tie::IxHash', '' => { 'desc' => 'No default', 'condition' => sub { 0 } }, 'D' => { 'desc' => 'Default', @@ -38,6 +39,9 @@ tie my %flag, 'Tie::IxHash', 'H' => { 'desc' => 'Select from hardware class', 'condition' => sub { $_[0]->{type} ne 'select-hardware' }, }, + 'P' => { 'desc' => 'From package FCC 477 information', + 'condition' => sub { $_[0]->{type} ne 'fcc_477_speed' }, # get values from package fcc 477 information + }, 'X' => { 'desc' => 'Excluded', 'condition' => sub { 1 }, # obsolete }, @@ -202,6 +206,20 @@ my %communigate_fields = ( % $mode = 'hardware'; % $multiple = 0; % } +% +% if ( $def->{'type'} eq 'fcc_477_speed' ) { +% if ($field eq 'speed_up') { + + upstream speed + + +% } elsif ($field eq 'speed_down') { + + downstream speed + + +% } +% } else { <& /elements/select-table.html, 'field' => $name.'_classnum', 'id' => $name.'_select', @@ -211,6 +229,7 @@ my %communigate_fields = ( 'empty_label' => "Select $mode class", 'multiple' => $multiple, &> +% } % } diff --git a/httemplate/edit/elements/svc_Common.html b/httemplate/edit/elements/svc_Common.html index a4e345e40..e1c309080 100644 --- a/httemplate/edit/elements/svc_Common.html +++ b/httemplate/edit/elements/svc_Common.html @@ -169,6 +169,16 @@ ]; } # shouldn't this be enforced for all 'S' fields? + elsif ( $flag eq 'P' ) { #form fcc_477 values + $f->{type} = 'fixed'; + my $cust_pkg = FS::Record::qsearchs({ + 'table' => 'cust_pkg', + 'hashref' => { 'pkgnum' => $object->{Hash}->{pkgnum} } + }); + my $fcc_record = $cust_pkg->fcc_477_record('broadband_'.$columndef->columnvalue.'stream') if $cust_pkg; + $f->{'value'} = $fcc_record->{Hash}->{optionvalue} ? $fcc_record->{Hash}->{optionvalue} * 1000 : ''; + } # end 477 values + if ( $f->{'type'} =~ /^select-svc/ ) { $f->{'include_opt_callback'} = diff --git a/httemplate/edit/part_svc.cgi b/httemplate/edit/part_svc.cgi index fed21256f..49c1c03d8 100755 --- a/httemplate/edit/part_svc.cgi +++ b/httemplate/edit/part_svc.cgi @@ -107,7 +107,7 @@ function flag_changed(obj) { select.multiple = false; } } - } else if ( newflag == 'M' || newflag == 'A' || newflag == 'H' ) { + } else if ( newflag == 'M' || newflag == 'A' || newflag == 'H' || newflag == 'P' ) { // these all require a class selection if ( select ) { select.disabled = false; @@ -120,7 +120,7 @@ function flag_changed(obj) { } var required = document.getElementById(layer + '__' + field + '_required'); if (required && !required.disabledinit) { - if (newflag == "F") { + if (newflag == "F" || newflag =="P") { required.checked = false; required.disabled = true; } else { diff --git a/httemplate/edit/process/elements/process.html b/httemplate/edit/process/elements/process.html index 8c307f0b6..c197eb123 100644 --- a/httemplate/edit/process/elements/process.html +++ b/httemplate/edit/process/elements/process.html @@ -89,6 +89,9 @@ Example: # for use with tables that are FS::option_Common (among other things) 'args_callback' => sub { my( $cgi, $object ) = @_; }, + # if no errors after package insert or replace will update services attached to package. + 'update_svc' => sub { my( $cgi, $object ) = @_; }, + 'debug' => 1, #turns on debugging output #agent virtualization @@ -438,6 +441,12 @@ foreach my $value ( @values ) { } } + if ( !$error and $opt{'update_svc'} ) { + my @args = (); + @args = &{ $opt{'args_callback'} }( $cgi, $new ) if $opt{'args_callback'}; + $error = &{ $opt{'update_svc'} }( $cgi, $new, @args ); + } + if ( $error ) { $cgi->param('error', $error); diff --git a/httemplate/edit/process/part_pkg.cgi b/httemplate/edit/process/part_pkg.cgi index c4d150ba1..6bab20278 100755 --- a/httemplate/edit/process/part_pkg.cgi +++ b/httemplate/edit/process/part_pkg.cgi @@ -9,6 +9,7 @@ 'edit_ext' => 'cgi', 'precheck_callback' => $precheck_callback, 'args_callback' => $args_callback, + 'update_svc' => $update_svc, 'process_locale' => 'pkg', 'process_m2m' => \@process_m2m, 'process_o2m' => \@process_o2m, @@ -199,6 +200,37 @@ my $args_callback = sub { }; +## update services upon package change. +my $update_svc = sub { + my $cgi = shift @_; + my $new = shift @_; + my %args = @_; + my $error; + + my @svcs = $new->pkg_svc(); + + foreach my $svc_part(@svcs) { + my @part_svc_column = qsearch('part_svc_column',{ 'svcpart' => $svc_part->{Hash}->{svcpart}, 'columnflag' => 'P' }); + + if ($svc_part->{Hash}->{svcdb} eq "svc_broadband" && (keys $args{fcc_options}) && @part_svc_column ) { + ## find provisioned services to update + my @svc_svcdb = qsearch({ + 'table' => 'svc_broadband', + 'select' => 'svc_broadband.*, cust_svc.svcpart', + 'addl_from' => 'LEFT JOIN cust_svc USING (svcnum) LEFT JOIN cust_pkg USING (pkgnum)', + 'extra_sql' => " WHERE cust_svc.svcpart = '".$svc_part->{Hash}->{svcpart}."' AND cust_pkg.pkgpart = '".$svc_part->{Hash}->{pkgpart}."'", + }); + foreach my $svc (@svc_svcdb) { + #my $svc_new = $svc; + $svc->{Hash}->{speed_down} = $args{fcc_options}->{broadband_downstream} * 1000; + $svc->{Hash}->{speed_up} = $args{fcc_options}->{broadband_upstream} * 1000; + $error = $svc->replace(); + } + } + } + return $error; +}; + my $redirect_callback = sub { #my( $cgi, $new ) = @_; return '' unless $custnum; diff --git a/httemplate/edit/svc_broadband.cgi b/httemplate/edit/svc_broadband.cgi index 81c694aa5..bcf55fe11 100644 --- a/httemplate/edit/svc_broadband.cgi +++ b/httemplate/edit/svc_broadband.cgi @@ -100,7 +100,7 @@ END ; my @fields = ( - qw( description speed_down speed_up ), + qw( description speed_down speed_up speed_test_down speed_test_up speed_test_latency), { field=>'sectornum', type=>'select-tower_sector', }, { field=>'routernum', type=>'select-router_block_ip', include_opt_callback => sub { @@ -179,7 +179,6 @@ my $svc_field_callback = sub { my $columndef = $part_svc->part_svc_column($fieldref->{'field'}); if ($fieldref->{field} eq 'usergroup' && $columndef->columnflag eq 'F') { - $fieldref->{'formatted_value'} = [ $object->radius_groups('long_description') ]; } -- cgit v1.2.1 From b548768fbbcdcae4df6f46ac38f0615437a20298 Mon Sep 17 00:00:00 2001 From: Christopher Burger Date: Fri, 27 Jul 2018 18:14:31 -0400 Subject: RT# 77160 - added date search on customer timespan report --- httemplate/search/cust_timespan.html | 7 ++++++- httemplate/search/elements/search.html | 15 ++++++++------- httemplate/search/report_cust_timespan.html | 2 ++ 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/httemplate/search/cust_timespan.html b/httemplate/search/cust_timespan.html index f5684251a..f95250a44 100644 --- a/httemplate/search/cust_timespan.html +++ b/httemplate/search/cust_timespan.html @@ -11,6 +11,7 @@ 'header' => \@header, 'fields' => \@fields, 'links' => \@links, + 'disable_maxselect' => '1', &> <%init> @@ -84,7 +85,11 @@ my $active_pkg_sql = 'select pkgnum from cust_pkg where cust_pkg.custnum = cust_ ## sql to get the first active date, last cancel date, and last reason. my $active_date = 'select min(setup) from cust_pkg left join part_pkg using (pkgpart) where cust_pkg.custnum = cust_main.custnum and part_pkg.freq > \'0\''; -my $cancel_date = 'select max(cancel) from cust_pkg where cust_pkg.custnum = cust_main.custnum'; + +## set cancel date range here +my($beginning_date, $ending_date) = FS::UI::Web::parse_beginning_ending($cgi, ''); +my $cancel_date = 'select max(cancel) from cust_pkg left join part_pkg using (pkgpart) where cust_pkg.custnum = cust_main.custnum and part_pkg.freq > \'0\' and (cancel >= '.$beginning_date.' and cancel <= '.$ending_date.')'; + my $cancel_reason = 'select reason.reason from cust_pkg left join cust_pkg_reason on (cust_pkg.pkgnum = cust_pkg_reason.pkgnum) left join reason on (cust_pkg_reason.reasonnum = reason.reasonnum) diff --git a/httemplate/search/elements/search.html b/httemplate/search/elements/search.html index 0e8c69a51..730a51aa3 100644 --- a/httemplate/search/elements/search.html +++ b/httemplate/search/elements/search.html @@ -389,14 +389,15 @@ unless ( $type =~ /^(csv|xml|\w*.xls)$/) { #setup some pagination things if we're in html mode my $conf = new FS::Conf; - $confmax = $conf->config('maxsearchrecordsperpage') || 100; - if ( $cgi->param('maxrecords') =~ /^(\d+)$/ ) { - $maxrecords = $1; - } else { - $maxrecords ||= $confmax; - } - $opt{'disable_maxselect'} ||= $conf->exists('disable_maxselect'); + unless ($opt{'disable_maxselect'}) { + $confmax = $conf->config('maxsearchrecordsperpage') || 100; + if ( $cgi->param('maxrecords') =~ /^(\d+)$/ ) { + $maxrecords = $1; + } else { + $maxrecords ||= $confmax; + } + } $limit = $maxrecords ? "LIMIT $maxrecords" : ''; diff --git a/httemplate/search/report_cust_timespan.html b/httemplate/search/report_cust_timespan.html index 4ff3bb892..27dd94006 100644 --- a/httemplate/search/report_cust_timespan.html +++ b/httemplate/search/report_cust_timespan.html @@ -20,6 +20,8 @@ 'curr_value' => scalar( $cgi->param('cust_status') ), &> + <& /elements/tr-input-beginning_ending.html &> + -- cgit v1.2.1 From a5bfed744069d69a1fe07eca1a64a2b22692cc22 Mon Sep 17 00:00:00 2001 From: Mitch Jackson Date: Sat, 28 Jul 2018 14:46:38 -0500 Subject: RT# 78547 - Flag to disable email/print/fax/etc during tests or reports --- FS/FS/Misc.pm | 44 +++++++++++++++++++++++++++++++++++++++++++- FS/FS/Template_Mixin.pm | 5 +++++ FS/FS/cust_bill.pm | 10 ++++++++++ FS/FS/msg_template/email.pm | 5 +++++ 4 files changed, 63 insertions(+), 1 deletion(-) diff --git a/FS/FS/Misc.pm b/FS/FS/Misc.pm index d84aaced5..fd2c32513 100644 --- a/FS/FS/Misc.pm +++ b/FS/FS/Misc.pm @@ -1,7 +1,7 @@ package FS::Misc; use strict; -use vars qw ( @ISA @EXPORT_OK $DEBUG ); +use vars qw ( @ISA @EXPORT_OK $DEBUG $DISABLE_ALL_NOTICES ); use Exporter; use Carp; use Data::Dumper; @@ -43,6 +43,32 @@ Miscellaneous subroutines. This module contains miscellaneous subroutines called from multiple other modules. These are not OO or necessarily related, but are collected here to eliminate code duplication. +=head1 DISABLE ALL NOTICES + +Set $FS::Misc::DISABLE_ALL_NOTICES to suppress: + +=over 4 + +=item FS::cust_bill::send_csv + +=item FS::cust_bill::spool_csv + +=item FS::msg_template::email::send_prepared + +=item FS::Misc::send_email + +=item FS::Misc::do_print + +=item FS::Misc::send_fax + +=item FS::Template_Mixin::postal_mail_fsinc + +=back + +=cut + +$DISABLE_ALL_NOTICES = 0; + =head1 SUBROUTINES =over 4 @@ -118,6 +144,12 @@ FS::UID->install_callback( sub { sub send_email { my(%options) = @_; + + if ( $DISABLE_ALL_NOTICES ) { + warn 'send_email() disabled by $FS::Misc::DISABLE_ALL_NOTICES' if $DEBUG; + return; + } + if ( $DEBUG ) { my %doptions = %options; $doptions{'body'} = '(full body not shown in debug)'; @@ -450,6 +482,11 @@ sub send_fax { die 'HylaFAX support has not been configured.' unless $conf->exists('hylafax'); + if ( $DISABLE_ALL_NOTICES ) { + warn 'send_fax() disabled by $FS::Misc::DISABLE_ALL_NOTICES' if $DEBUG; + return; + } + eval { require Fax::Hylafax::Client; }; @@ -869,6 +906,11 @@ global value and agentnum). sub do_print { my( $data, %opt ) = @_; + if ( $DISABLE_ALL_NOTICES ) { + warn 'do_print() disabled by $FS::Misc::DISABLE_ALL_NOTICES' if $DEBUG; + return; + } + my $lpr = ( exists($opt{'lpr'}) && $opt{'lpr'} ) ? $opt{'lpr'} : $conf->config('lpr', $opt{'agentnum'} ); diff --git a/FS/FS/Template_Mixin.pm b/FS/FS/Template_Mixin.pm index 1b3df0066..34e9e6ef8 100644 --- a/FS/FS/Template_Mixin.pm +++ b/FS/FS/Template_Mixin.pm @@ -2513,6 +2513,11 @@ use MIME::Base64; sub postal_mail_fsinc { my ( $self, %opt ) = @_; + if ( $FS::Misc::DISABLE_PRINT ) { + warn 'postal_mail_fsinc() disabled by $FS::Misc::DISABLE_PRINT' if $DEBUG; + return; + } + my $url = 'https://ws.freeside.biz/print'; my $cust_main = $self->cust_main; diff --git a/FS/FS/cust_bill.pm b/FS/FS/cust_bill.pm index 0a735f7ec..47f71c458 100644 --- a/FS/FS/cust_bill.pm +++ b/FS/FS/cust_bill.pm @@ -1405,6 +1405,11 @@ See L for a description of the output format. sub send_csv { my($self, %opt) = @_; + if ( $FS::Misc::DISABLE_ALL_NOTICES ) { + warn 'send_csv() disabled by $FS::Misc::DISABLE_ALL_NOTICES' if $DEBUG; + return; + } + #create file(s) my $spooldir = "/usr/local/etc/freeside/export.". datasrc. "/cust_bill"; @@ -1481,6 +1486,11 @@ in the ICS format. sub spool_csv { my($self, %opt) = @_; + if ( $FS::Misc::DISABLE_ALL_NOTICES ) { + warn 'spool_csv() disabled by $FS::Misc::DISABLE_ALL_NOTICES' if $DEBUG; + return; + } + my $time = $opt{'time'} || time; my $cust_main = $self->cust_main; diff --git a/FS/FS/msg_template/email.pm b/FS/FS/msg_template/email.pm index 37c1fab46..c2c370760 100644 --- a/FS/FS/msg_template/email.pm +++ b/FS/FS/msg_template/email.pm @@ -529,6 +529,11 @@ sub send_prepared { my $self = shift; my $cust_msg = shift or die "cust_msg required"; + if ( $FS::Misc::DISABLE_ALL_NOTICES ) { + warn 'send_prepared() disabled by $FS::Misc::DISABLE_ALL_NOTICES' if $DEBUG; + return; + } + my $domain = 'example.com'; if ( $cust_msg->env_from =~ /\@([\w\.\-]+)/ ) { $domain = $1; -- cgit v1.2.1 From ee3fc5b946e538ac7b51a6c92ea8eac205b9fda3 Mon Sep 17 00:00:00 2001 From: Christopher Burger Date: Mon, 30 Jul 2018 12:37:52 -0400 Subject: RT# 80898 - added config option to allow for the changing of the name for credit card surcharge on invoice. --- FS/FS/Conf.pm | 8 ++++++++ FS/FS/cust_main/Billing_Realtime.pm | 4 +++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm index bd544c944..302bae72c 100644 --- a/FS/FS/Conf.pm +++ b/FS/FS/Conf.pm @@ -788,6 +788,14 @@ my $validate_email = sub { $_[0] =~ 'per_agent' => 1, }, + { + 'key' => 'credit-card-surcharge-text', + 'section' => 'credit_cards', + 'description' => 'Text for the credit card surcharge invoice line. If not set, it will default to Credit Card Surcharge.', + 'type' => 'text', + 'per_agent' => 1, + }, + { 'key' => 'discount-show-always', 'section' => 'invoicing', diff --git a/FS/FS/cust_main/Billing_Realtime.pm b/FS/FS/cust_main/Billing_Realtime.pm index f4d87ddd1..d286f635e 100644 --- a/FS/FS/cust_main/Billing_Realtime.pm +++ b/FS/FS/cust_main/Billing_Realtime.pm @@ -1078,9 +1078,11 @@ sub _realtime_bop_result { } my $cust_pkg; + my $cc_surcharge_text = 'Credit Card Surcharge'; + $cc_surcharge_text = $conf->config('credit-card-surcharge-text', $self->agentnum) if $conf->exists('credit-card-surcharge-text', $self->agentnum); my $charge_error = $self->charge({ 'amount' => $options{'cc_surcharge'}, - 'pkg' => 'Credit Card Surcharge', + 'pkg' => $cc_surcharge_text, 'setuptax' => 'Y', 'cust_pkg_ref' => \$cust_pkg, }); -- cgit v1.2.1 From a354fed1dedc65cf62e63d940e1cfb9c4364e6d3 Mon Sep 17 00:00:00 2001 From: Christopher Burger Date: Tue, 31 Jul 2018 10:29:19 -0400 Subject: RT# 78356 - made status bar more responsive on export. Changed svc update to only happen when up and down speeds change. --- FS/FS/part_export/saisei.pm | 2 +- httemplate/edit/process/part_pkg.cgi | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/FS/FS/part_export/saisei.pm b/FS/FS/part_export/saisei.pm index 617a73226..c79f79dac 100644 --- a/FS/FS/part_export/saisei.pm +++ b/FS/FS/part_export/saisei.pm @@ -830,7 +830,7 @@ sub export_provisioned_services { my $svc_count = scalar @svcs; my %status = {}; - for (my $c=10; $c <=100; $c=$c+10) { $status{int($svc_count * ($c/100))} = $c; } + for (my $c=1; $c <=100; $c=$c+1) { $status{int($svc_count * ($c/100))} = $c; } my $process_count=0; foreach my $svc (@svcs) { diff --git a/httemplate/edit/process/part_pkg.cgi b/httemplate/edit/process/part_pkg.cgi index 6bab20278..5beb03411 100755 --- a/httemplate/edit/process/part_pkg.cgi +++ b/httemplate/edit/process/part_pkg.cgi @@ -209,6 +209,7 @@ my $update_svc = sub { my @svcs = $new->pkg_svc(); +## update broadband services getting their up and down speeds from package fcc_477 options foreach my $svc_part(@svcs) { my @part_svc_column = qsearch('part_svc_column',{ 'svcpart' => $svc_part->{Hash}->{svcpart}, 'columnflag' => 'P' }); @@ -221,7 +222,7 @@ my $update_svc = sub { 'extra_sql' => " WHERE cust_svc.svcpart = '".$svc_part->{Hash}->{svcpart}."' AND cust_pkg.pkgpart = '".$svc_part->{Hash}->{pkgpart}."'", }); foreach my $svc (@svc_svcdb) { - #my $svc_new = $svc; + next if ($svc->{Hash}->{speed_down} == $args{fcc_options}->{broadband_downstream} * 1000 && $svc->{Hash}->{speed_up} == $args{fcc_options}->{broadband_upstream} * 1000); $svc->{Hash}->{speed_down} = $args{fcc_options}->{broadband_downstream} * 1000; $svc->{Hash}->{speed_up} = $args{fcc_options}->{broadband_upstream} * 1000; $error = $svc->replace(); -- cgit v1.2.1 From 7c4c3bd3e3e2d6a820c77bec346f9d378e2761dc Mon Sep 17 00:00:00 2001 From: Christopher Burger Date: Tue, 31 Jul 2018 11:29:35 -0400 Subject: RT# 78356 - created import format for broadband service --- FS/FS/cust_pkg/Import.pm | 1 + httemplate/misc/cust_pkg-import.html | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/FS/FS/cust_pkg/Import.pm b/FS/FS/cust_pkg/Import.pm index 93bd88d7b..2b1832329 100644 --- a/FS/FS/cust_pkg/Import.pm +++ b/FS/FS/cust_pkg/Import.pm @@ -102,6 +102,7 @@ my %formatfields = ( 'default' => [], 'all_dates' => [], 'svc_acct' => [qw( username _password domsvc )], + 'svc_broadband' => [qw( ip_addr description routernum blocknum sectornum speed_up speed_down )], 'svc_phone' => [qw( countrycode phonenum sip_password pin )], 'svc_external' => [qw( id title )], 'location' => [qw( address1 address2 city state zip country )], diff --git a/httemplate/misc/cust_pkg-import.html b/httemplate/misc/cust_pkg-import.html index 3b200e5f3..9bee7a060 100644 --- a/httemplate/misc/cust_pkg-import.html +++ b/httemplate/misc/cust_pkg-import.html @@ -36,6 +36,7 @@ Import a file containing customer packages.