Merge branch 'master' of git.freeside.biz:/home/git/freeside
authorIvan Kohler <ivan@freeside.biz>
Sat, 26 Dec 2015 20:30:18 +0000 (12:30 -0800)
committerIvan Kohler <ivan@freeside.biz>
Sat, 26 Dec 2015 20:30:18 +0000 (12:30 -0800)
42 files changed:
FS/FS/AccessRight.pm
FS/FS/Conf.pm
FS/FS/Mason.pm
FS/FS/Misc/Geo.pm
FS/FS/Schema.pm
FS/FS/cust_main/Billing.pm
FS/FS/cust_main/Billing_Batch.pm
FS/FS/fiber_olt.pm [new file with mode: 0644]
FS/FS/geocode_Mixin.pm
FS/FS/log.pm
FS/FS/log_context.pm
FS/FS/msg_template.pm
FS/FS/msg_template/InitialData.pm
FS/FS/part_export/broadband_snmp_get.pm
FS/FS/pay_batch.pm
FS/FS/svc_fiber.pm [new file with mode: 0644]
FS/MANIFEST
FS/bin/freeside-eftca-upload
FS/bin/freeside-paymentech-upload
FS/bin/freeside-rbc-upload
FS/t/fiber_olt.t [new file with mode: 0644]
httemplate/browse/fiber_olt.html [new file with mode: 0644]
httemplate/edit/fiber_olt.html [new file with mode: 0644]
httemplate/edit/log_email.html
httemplate/edit/process/fiber_olt.html [new file with mode: 0644]
httemplate/edit/process/svc_fiber.html [new file with mode: 0644]
httemplate/edit/quick-charge.html
httemplate/edit/svc_fiber.html [new file with mode: 0644]
httemplate/elements/broadband_snmp_get-dialog.html [deleted file]
httemplate/elements/broadband_snmp_get.html [new file with mode: 0644]
httemplate/elements/menu.html
httemplate/elements/standardize_locations.js
httemplate/misc/download-batch.cgi
httemplate/search/cust_msg.html
httemplate/search/log.html
httemplate/search/report_svc_fiber.html [new file with mode: 0755]
httemplate/search/svc_fiber.html [new file with mode: 0644]
httemplate/view/cust_main/packages/location.html
httemplate/view/cust_main/packages/package.html
httemplate/view/svc_broadband.cgi
httemplate/view/svc_fiber.cgi [new file with mode: 0644]
rt/share/html/Admin/Queues/Tasks.html

index a96a6cb..51e47ad 100644 (file)
@@ -314,6 +314,8 @@ tie my %rights, 'Tie::IxHash',
     'Services: Alarm services',
     'Services: Video',
     'Services: Circuits',
+    'Services: Fiber',
+    'Services: Fiber: Advanced search',
     'Services: External services',
     'Usage: RADIUS sessions',
     'Usage: Call Detail Records (CDRs)',
index 72421c7..e066225 100644 (file)
@@ -4321,6 +4321,7 @@ and customer address. Include units.',
     'description' => 'Method for standardizing customer addresses.',
     'type'        => 'select',
     'select_hash' => [ '' => '', 
+                       'uscensus' => 'U.S. Census Bureau',
                        'usps'     => 'U.S. Postal Service',
                        'uscensus' => 'U.S. Census Bureau',
                        'tomtom'   => 'TomTom',
index a2a7617..370ac1a 100644 (file)
@@ -411,6 +411,8 @@ if ( -e $addl_handler_use_file ) {
   use FS::report_batch;
   use FS::report_batch;
   use FS::password_history;
+  use FS::svc_fiber;
+  use FS::fiber_olt;
   # Sammath Naur
 
   if ( $FS::Mason::addl_handler_use ) {
index e83d6dc..aa4e55e 100644 (file)
@@ -11,6 +11,7 @@ use Cpanel::JSON::XS;
 use URI::Escape 3.31;
 use Data::Dumper;
 use FS::Conf;
+use FS::Log;
 use Locale::Country;
 
 FS::UID->install_callback( sub {
@@ -300,6 +301,8 @@ sub standardize_usps {
 sub standardize_uscensus {
   my $self = shift;
   my $location = shift;
+  my $log = FS::Log->new('FS::Misc::Geo::standardize_uscensus');
+  $log->debug(join("\n", @{$location}{'address1', 'city', 'state', 'zip'}));
 
   eval "use Geo::USCensus::Geocoding";
   die $@ if $@;
@@ -322,6 +325,7 @@ sub standardize_uscensus {
   my $result = Geo::USCensus::Geocoding->query($request);
   if ( $result->is_match ) {
     # unfortunately we get the address back as a single line
+    $log->debug($result->address);
     if ($result->address =~ /^(.*), (.*), ([A-Z]{2}), (\d{5}.*)$/) {
       return +{
         address1    => $1,
@@ -341,8 +345,8 @@ sub standardize_uscensus {
   } elsif ( $result->match_level ) {
     die "Geocoding did not find a matching address.\n";
   } else {
-    warn Dumper($result) if $DEBUG;
-    die $result->error_message;
+    $log->error($result->error_message);
+    return; # for internal errors, don't return anything
   }
 }
 
index c1ed79c..a10b5c0 100644 (file)
@@ -6870,6 +6870,52 @@ sub tables_hashref {
       ],
     },
 
+    'svc_fiber' => {
+      'columns' => [
+        'svcnum',         'int',     '',      '', '', '',
+        'oltnum',         'int', 'NULL',      '', '', '',
+        'shelf',          'int', 'NULL',      '', '', '',
+        'card',           'int', 'NULL',      '', '', '',
+        'olt_port',       'int', 'NULL',      '', '', '',
+        'ont_id',         'int', 'NULL',      '', '', '',
+        'ont_typenum',    'int', 'NULL',      '', '', '',
+        'ont_serial', 'varchar', 'NULL', $char_d, '', '',
+        'ont_port',   'varchar', 'NULL',      16, '', '',
+        'vlan',           'int', 'NULL',      '', '', '',
+        'signal',         'int', 'NULL',      '', '', '',
+        'speed_up',       'int', 'NULL',      '', '', '',
+        'speed_down',     'int', 'NULL',      '', '', '',
+        'ont_install','varchar', 'NULL', $char_d, '', '',
+      ],
+      'primary_key' => 'svcnum',
+      'unique'      => [ ],
+      'index'       => [ [ 'ont_serial' ] ],
+      'foreign_keys' => [
+                          { columns => [ 'svcnum' ],
+                            table   => 'cust_svc',
+                          },
+                          { columns => [ 'oltnum' ],
+                            table   => 'fiber_olt',
+                          },
+                          { columns => [ 'ont_typenum' ],
+                            table   => 'hardware_type',
+                            references => [ 'typenum' ],
+                          },
+                        ],
+    },
+
+    'fiber_olt' => {
+      'columns' => [
+        'oltnum',   'serial', '',       '', '', '',
+        'oltname', 'varchar', '',  $char_d, '', '',
+        'serial',  'varchar', '',  $char_d, '', '',
+        'disabled',   'char', 'NULL',    1, '', '',
+      ],
+      'primary_key' => 'oltnum',
+      'unique' => [ ],
+      'index'  => [ ],
+    },
+
     'vend_main' => {
       'columns' => [
         'vendnum',   'serial',     '',      '', '', '',
index d3c618d..29535f2 100644 (file)
@@ -999,9 +999,10 @@ sub _make_lines {
   #   - it doesn't already HAVE a setup date
   #   - or a start date in the future
   #   - and it's not suspended
+  # - and it doesn't have an expire date in the past
   #
-  # The last condition used to check the "disable_setup_suspended" option but 
-  # that's obsolete. We now never set the setup date on a suspended package.
+  # The "disable_setup_suspended" option is now obsolete; we never set the
+  # setup date on a suspended package.
   if (     ! $options{recurring_only}
        and ! $options{cancel}
        and ( $options{'resetup'}
@@ -1012,6 +1013,8 @@ sub _make_lines {
                   && ( ! $cust_pkg->getfield('susp') )
                 )
            )
+       and ( ! $cust_pkg->expire
+             || $cust_pkg->expire > $cmp_time )
      )
   {
     
@@ -1059,6 +1062,20 @@ sub _make_lines {
   my $recur_billed_currency = '';
   my $recur_billed_amount = 0;
   my $sdate;
+  # Conditions for billing the recurring fee:
+  # - the package doesn't have a future start date
+  # - and it's not suspended
+  #   - unless suspend_bill is enabled on the package or package def
+  #     - but still not, if the package is on hold
+  #   - or it's suspended for a delayed cancellation
+  # - and its next bill date is in the past
+  #   - or it doesn't have a next bill date yet
+  #   - or it's a one-time charge
+  #   - or it's a CDR plan with the "bill_every_call" option
+  #   - or it's being canceled
+  # - and it doesn't have an expire date in the past (this can happen with
+  #   advance billing)
+  #   - again, unless it's being canceled
   if (     ! $cust_pkg->start_date
        and 
            ( ! $cust_pkg->susp
@@ -1077,6 +1094,12 @@ sub _make_lines {
                && $part_pkg->option('bill_every_call')
             )
          || $options{cancel}
+
+       and
+          ( ! $cust_pkg->expire
+            || $cust_pkg->expire > $cmp_time
+            || $options{cancel}
+          )
   ) {
 
     # XXX should this be a package event?  probably.  events are called
index cdaf293..f91c5fb 100644 (file)
@@ -23,8 +23,6 @@ Options may include:
 B<amount>: the amount to be paid; defaults to the customer's balance minus
 any payments in transit.
 
-B<payby>: the payment method; defaults to cust_main.payby
-
 B<realtime>: runs this as a realtime payment instead of adding it to a 
 batch.  Deprecated.
 
@@ -34,8 +32,9 @@ B<address1>, B<address2>, B<city>, B<state>, B<zip>, B<country>: sets
 the billing address for the payment; defaults to the customer's billing
 location.
 
-B<payinfo>, B<paydate>, B<payname>: sets the payment account, expiration
-date, and name; defaults to those fields in cust_main.
+B<payby>, B<payinfo>, B<paydate>, B<payname>: sets the payment method, 
+payment account, expiration date, and name; defaults to those fields 
+in cust_main.
 
 =cut
 
@@ -58,6 +57,13 @@ sub batch_card {
   
   my $invnum = delete $options{invnum};
 
+  #pay fields should all come from either cust_payby or options, not both
+  #  in theory, could just pass payby, and use it to select cust_payby,
+  #  but nothing currently needs that, so not implementing it now
+  die "Incomplete payment details" 
+    if  ($options{payby} || $options{payinfo} || $options{paydate} || $options{payname})
+    && !($options{payby} && $options{payinfo} && $options{paydate} && $options{payname});
+
   #false laziness with Billing_Realtime
   my @cust_payby = qsearch({
     'table'     => 'cust_payby',
@@ -67,7 +73,10 @@ sub batch_card {
   });
 
   # batch can't try out every one like realtime, just use first one
-  my $cust_payby = $cust_payby[0] || $self; # somewhat dubious
+  my $cust_payby = $cust_payby[0];
+
+  die "No customer payment info found"
+    unless $options{payinfo} || $cust_payby;
                                                    
   my $payby = $options{payby} || $cust_payby->payby;
 
diff --git a/FS/FS/fiber_olt.pm b/FS/FS/fiber_olt.pm
new file mode 100644 (file)
index 0000000..a0de38d
--- /dev/null
@@ -0,0 +1,106 @@
+package FS::fiber_olt;
+use base qw( FS::Record );
+
+use strict;
+use FS::Record qw( qsearch qsearchs );
+
+=head1 NAME
+
+FS::fiber_olt - Object methods for fiber_olt records
+
+=head1 SYNOPSIS
+
+  use FS::fiber_olt;
+
+  $record = new FS::fiber_olt \%hash;
+  $record = new FS::fiber_olt { 'column' => 'value' };
+
+  $error = $record->insert;
+
+  $error = $new_record->replace($old_record);
+
+  $error = $record->delete;
+
+  $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::fiber_olt object represents an Optical Line Terminal that fiber
+connections (L<FS::svc_fiber>) connect to.  FS::fiber_olt inherits from
+FS::Record.  The following fields are currently supported:
+
+=over 4
+
+=item oltnum - primary key
+
+=item oltname - name of this device
+
+=item serial - serial number
+
+=item disabled - set to 'Y' to make this OLT unavailable for new connections
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new fiber_olt record.  To add it to the database, see L<"insert">.
+
+=cut
+
+# the new method can be inherited from FS::Record, if a table method is defined
+
+sub table { 'fiber_olt'; }
+
+=item insert
+
+Adds this record to the database.  If there is an error, returns the error,
+otherwise returns false.
+
+=item delete
+
+Delete this record from the database.
+
+=item replace OLD_RECORD
+
+Replaces the OLD_RECORD with this one in the database.  If there is an error,
+returns the error, otherwise returns false.
+
+=item check
+
+Checks all fields to make sure this is a valid example.  If there is
+an error, returns the error, otherwise returns false.  Called by the insert
+and replace methods.
+
+=cut
+
+# the check method should currently be supplied - FS::Record contains some
+# data checking routines
+
+sub check {
+  my $self = shift;
+
+  my $error = 
+    $self->ut_numbern('oltnum')
+    || $self->ut_text('oltname')
+    || $self->ut_text('serial')
+    || $self->ut_flag('disabled')
+  ;
+  return $error if $error;
+
+  $self->SUPER::check;
+}
+
+=back
+
+=head1 SEE ALSO
+
+L<FS::svc_fiber>, L<FS::Record>
+
+=cut
+
+1;
+
index 0625b5a..bc8c118 100644 (file)
@@ -140,7 +140,7 @@ Returns the full country name.
 
 sub country_full {
   my $self = shift;
-  $self->code2country($self->country);
+  $self->code2country($self->get('country'));
 }
 
 sub code2country {
index b079105..95bc4c4 100644 (file)
@@ -110,6 +110,7 @@ sub insert {
       next;
     }
     my $emailerror = $msg_template->send(
+      'msgtype'       => 'admin',
       'to'            => $log_email->to_addr,
       'substitutions' => {
         'loglevel'   => $FS::Log::LEVELS[$self->level], # which has hopefully been loaded...
@@ -147,7 +148,7 @@ sub check {
     || $self->ut_textn('tablename')
     || $self->ut_numbern('tablenum')
     || $self->ut_number('level')
-    || $self->ut_text('message')
+    || $self->ut_anything('message')
   ;
   return $error if $error;
 
index ff34717..9dba582 100644 (file)
@@ -10,6 +10,7 @@ my @contexts = ( qw(
   FS::cust_main::Billing::bill_and_collect
   FS::cust_main::Billing::bill
   FS::pay_batch::import_from_gateway
+  FS::Misc::Geo::standardize_uscensus
   Cron::bill
   Cron::backup
   Cron::upload
index 7d9750c..978d713 100644 (file)
@@ -803,6 +803,25 @@ sub _upgrade_data {
   ###
   $self->_populate_initial_data;
 
+  ### Fix dump-email_to (needs to happen after _populate_initial_data)
+  if ($conf->config('dump-email_to')) {
+    # anyone who still uses dump-email_to should have just had this created
+    my ($msg_template) = qsearch('msg_template',{ msgname => 'System log' });
+    if ($msg_template) {
+      eval "use FS::log_email;";
+      die $@ if $@;
+      my $log_email = new FS::log_email {
+        'context' => 'Cron::backup',
+        'min_level' => 1,
+        'msgnum' => $msg_template->msgnum,
+        'to_addr' => $conf->config('dump-email_to'),
+      };
+      my $error = $log_email->insert;
+      die $error if $error;
+      $conf->delete('dump-email_to');
+    }
+  }
+
 }
 
 sub _populate_initial_data { #class method
@@ -811,18 +830,22 @@ sub _populate_initial_data { #class method
 
   eval "use FS::msg_template::InitialData;";
   die $@ if $@;
+  eval "use FS::upgrade_journal;";
+  die $@ if $@;
 
   my $initial_data = FS::msg_template::InitialData->_initial_data;
 
   foreach my $hash ( @$initial_data ) {
 
     next if $hash->{_conf} && $conf->config( $hash->{_conf} );
+    next if $hash->{_upgrade_journal} && FS::upgrade_journal->is_done( $hash->{_upgrade_journal} );
 
     my $msg_template = new FS::msg_template($hash);
     my $error = $msg_template->insert( @{ $hash->{_insert_args} || [] } );
     die $error if $error;
 
     $conf->set( $hash->{_conf}, $msg_template->msgnum ) if $hash->{_conf};
+    FS::upgrade_journal->set_done( $hash->{_upgrade_journal} );
   
   }
 
index baf145d..d502b3f 100644 (file)
@@ -39,6 +39,20 @@ Amount:    {$refund}<BR>
 END
                       ],
     },
+    { msgname   => 'System log',
+      msgclass  => 'email',
+      mime_type => 'text/html',
+      _upgrade_journal => 'system_log_email_template',
+      _insert_args => [ subject => '{ $company_name } system log',
+                        body    => <<'END',
+Level: {$loglevel}<BR>
+Context: {$logcontext}<BR>
+<BR>
+{$logmessage}<BR>
+
+END
+                      ],
+    },
   ];
 }
 
index faa51ed..fafe91a 100644 (file)
@@ -30,8 +30,7 @@ tie my %options, 'Tie::IxHash',
   'options' => \%options,
   'no_machine' => 1,
   'notes'   => <<'END',
-Use this export to configure the community and object ids for displaying realtime 
-SNMP data from the service IP address when viewing a provisioned service.  Timeout is
+Display broadband service status information via SNMP.  Timeout is
 per object, and should be small enough for realtime use.  This export takes no action 
 during provisioning itself;  it is expected that snmp will be separately
 configured on the service machine.
index e299dd9..35c79f5 100644 (file)
@@ -888,7 +888,8 @@ Prepare the batch to be exported.  This will:
   increment expiration dates that are in the past.
 - If this is the first download for this batch, adjust payment amounts to 
   not be greater than the customer's current balance.  If the customer's 
-  balance is zero, the entry will be removed.
+  balance is zero, the entry will be removed (caution: all cust_pay_batch
+  entries might be removed!)
 
 Use this within a transaction.
 
@@ -947,15 +948,6 @@ sub prepare_for_export {
       # else $balance >= $cust_pay_batch->amount
     }
 
-    # we might end up removing all cust_pay_batch above...
-    # probably the better way to handle this is to commit that removal,
-    # but no time to trace code & test that right now
-    #
-    # additionally, UI currently allows hand-deletion of all payments from a batch, meaning
-    # it's possible to try and process an empty batch...this is where we catch
-    # such an attempt, though it probably shouldn't be possible in the first place
-    return "Batch is empty" unless $self->cust_pay_batch;
-
     #need to do this after unbatch_and_delete
     my $error = $self->set_status('I');
     return "error updating pay_batch status: $error\n" if $error;
@@ -973,6 +965,10 @@ module, in which case the configuration options are in 'batchconfig-FORMAT'.
 Alternatively, GATEWAY can be an L<FS::payment_gateway> object set to a
 L<Business::BatchPayment> module.
 
+Returns the text of the batch.  If batch contains no cust_pay_batch entries
+(or has them all removed by L</prepare_for_export>) then the batch will be 
+resolved and a blank string will be returned.  All other errors are fatal.
+
 =cut
 
 sub export_batch {
@@ -1008,6 +1004,12 @@ sub export_batch {
   my $batchcount = 0;
 
   my @cust_pay_batch = $self->cust_pay_batch;
+  unless (@cust_pay_batch) {
+    # if it's empty, just resolve the batch
+    $self->set_status('R');
+    $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+    return '';
+  }
 
   my $delim = exists($info->{'delimiter'}) ? $info->{'delimiter'} : "\n";
 
@@ -1052,6 +1054,10 @@ that gateway via Business::BatchPayment. OPTIONS may include:
 
 - file: override the default transport and write to this file (name or handle)
 
+If batch contains no cust_pay_batch entries (or has them all removed by 
+L</prepare_for_export>) then nothing will be transported (or written to 
+the override file) and the batch will be resolved.
+
 =cut
 
 sub export_to_gateway {
@@ -1072,6 +1078,13 @@ sub export_to_gateway {
   my $processor = $gateway->batch_processor(%proc_opt);
 
   my @items = map { $_->request_item } $self->cust_pay_batch;
+  unless (@items) {
+    # if it's empty, just resolve the batch
+    $self->set_status('R');
+    $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+    return '';
+  }
+
   my $batch = Business::BatchPayment->create(Batch =>
     batch_id  => $self->batchnum,
     items     => \@items
diff --git a/FS/FS/svc_fiber.pm b/FS/FS/svc_fiber.pm
new file mode 100644 (file)
index 0000000..c4036dc
--- /dev/null
@@ -0,0 +1,323 @@
+package FS::svc_fiber;
+
+use strict;
+use base qw( FS::svc_Common );
+use FS::cust_svc;
+use FS::hardware_type;
+use FS::fiber_olt;
+use FS::Record 'dbh';
+
+=head1 NAME
+
+FS::svc_fiber - Object methods for svc_fiber records
+
+=head1 SYNOPSIS
+
+  use FS::table_name;
+
+  $record = new FS::table_name \%hash;
+  $record = new FS::table_name { 'column' => 'value' };
+
+  $error = $record->insert;
+
+  $error = $new_record->replace($old_record);
+
+  $error = $record->delete;
+
+  $error = $record->check;
+
+  $error = $record->suspend;
+
+  $error = $record->unsuspend;
+
+  $error = $record->cancel;
+
+=head1 DESCRIPTION
+
+An FS::svc_fiber object represents a fiber-to-the-premises service.  
+FS::svc_fiber inherits from FS::svc_Common.  The following fields are
+currently supported:
+
+=over 4
+
+=item svcnum - Primary key
+
+=item oltnum - The Optical Line Terminal this service connects to (see
+L<FS::fiber_olt>).
+
+=item shelf - The shelf number on the OLT.
+
+=item card - The card number on the OLT shelf.
+
+=item olt_port - The port number on that card.
+
+=item vlan - The VLAN number.
+
+=item signal - Measured signal strength in dB.
+
+=item speed_up - Measured uplink speed in Mbps.
+
+=item speed_down - Measured downlink speed in Mbps.
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new fiber service record.  To add it to the database, see L<"insert">.
+
+=cut
+
+sub table { 'svc_fiber'; }
+
+sub table_info {
+  {
+    'name' => 'Fiber',
+    'name_plural' => 'Fiber', # really the name of the ACL
+    'longname_plural' => 'Fiber services',
+    'sorts' => [ 'oltnum', ],
+    'display_weight' => 74,
+    'cancel_weight'  => 74,
+    'fields' => {
+      'oltnum'        => {
+                          'label'        => 'OLT',
+                          'type'         => 'select',
+                          'select_table' => 'fiber_olt',
+                          'select_key'   => 'oltnum',
+                          'select_label' => 'oltname',
+                          'disable_inventory' => 1,
+                         },
+      'shelf'         => {
+                          'label' => 'Shelf',
+                          'type'  => 'text',
+                          'disable_inventory' => 1,
+                          'disable_select'    => 1,
+                         },
+      'card'          => {
+                          'label' => 'Card',
+                          'type'  => 'text',
+                          'disable_inventory' => 1,
+                          'disable_select'    => 1,
+                         },
+      'olt_port'      => {
+                          'label' => 'GPON port',
+                          'type'  => 'text',
+                          'disable_inventory' => 1,
+                          'disable_select'    => 1,
+                         },
+      # ONT stuff
+      'ont_id'        => {
+                          'label' => 'ONT #',
+                          'disable_select'    => 1,
+                         },
+      'ont_typenum'   => {
+                          'label' => 'Device type',
+                          'type'  => 'select-hardware',
+                          'disable_select'    => 1,
+                          'disable_default'   => 1,
+                          'disable_inventory' => 1,
+                         },
+      'ont_serial'    => {
+                          'label' => 'Serial number',
+                          'disable_select'    => 1,
+                         },
+      'ont_port'      => {
+                          'label' => 'GE port',
+                          'type'  => 'text',
+                          'disable_inventory' => 1,
+                          'disable_select'    => 1,
+                         },
+      'vlan'          => {
+                          'label' => 'VLAN #',
+                          'type'  => 'text',
+                          'disable_inventory' => 1,
+                          'disable_select'    => 1,
+                         },
+      'signal'        => {
+                          'label' => 'Signal strength (dB)',
+                          'type'  => 'text',
+                          'disable_inventory' => 1,
+                          'disable_select'    => 1,
+                         },
+      'speed_down'    => {
+                          'label' => 'Download speed (Mbps)',
+                          'type'  => 'text',
+                          'disable_inventory' => 1,
+                          'disable_select'    => 1,
+                         },
+      'speed_up'      => {
+                          'label' => 'Upload speed (Mbps)',
+                          'type'  => 'text',
+                          'disable_inventory' => 1,
+                          'disable_select'    => 1,
+                         },
+      'ont_install'   => {
+                          'label' => 'ONT install location',
+                          'type'  => 'text',
+                          'disable_inventory' => 1,
+                          'disable_select'    => 1,
+                         },
+    },
+  };
+}
+
+=item search_sql STRING
+
+Class method which returns an SQL fragment to search for the given string.
+For svc_fiber, STRING can be a full or partial ONT serial number.
+
+=cut
+
+#or something more complicated if necessary
+sub search_sql {
+  my($class, $string) = @_;
+  $string = dbh->quote('%' . $string . '%');
+  "LOWER(svc_fiber.ont_serial) LIKE LOWER($string)";
+}
+
+=item label
+
+Returns a description of this fiber service containing the OLT name and
+port location, and the ONT serial number.
+
+=cut
+
+sub label {
+  my $self = shift;
+  $self->ont_serial . ' @ ' . $self->fiber_olt->oltname . ' ' .
+  join('-', $self->shelf, $self->card, $self->olt_port);
+}
+
+# nothing special for insert, delete, or replace
+
+=item insert
+
+Adds this record to the database.  If there is an error, returns the error,
+otherwise returns false.
+
+The additional fields pkgnum and svcpart (see L<FS::cust_svc>) should be 
+defined.  An FS::cust_svc record will be created and inserted.
+
+=item delete
+
+Delete this record from the database.
+
+=item replace OLD_RECORD
+
+Replaces the OLD_RECORD with this one in the database.  If there is an error,
+returns the error, otherwise returns false.
+
+=item suspend
+
+Called by the suspend method of FS::cust_pkg (see L<FS::cust_pkg>).
+
+=item unsuspend
+
+Called by the unsuspend method of FS::cust_pkg (see L<FS::cust_pkg>).
+
+=item cancel
+
+Called by the cancel method of FS::cust_pkg (see L<FS::cust_pkg>).
+
+=item check
+
+Checks all fields to make sure this is a valid example.  If there is
+an error, returns the error, otherwise returns false.  Called by the insert
+and repalce methods.
+
+=cut
+
+sub check {
+  my $self = shift;
+
+  my $x = $self->setfixed;
+  return $x unless ref($x);
+  my $part_svc = $x;
+
+  my $error =
+       $self->ut_number('oltnum')
+    || $self->ut_numbern('shelf')
+    || $self->ut_numbern('card')
+    || $self->ut_numbern('olt_port')
+    || $self->ut_number('ont_id')
+    || $self->ut_number('ont_typenum')
+    || $self->ut_alphan('ont_serial')
+    || $self->ut_alphan('ont_port')
+    || $self->ut_numbern('vlan')
+    || $self->ut_snumbern('signal')
+    || $self->ut_numbern('speed_up')
+    || $self->ut_numbern('speed_down')
+    || $self->ut_textn('ont_install')
+  ;
+  return $error if $error;
+
+  $self->SUPER::check;
+}
+
+=item ont_description
+
+Returns the description of the ONT hardware, if there is one.
+
+=cut
+
+sub ont_description {
+  my $self = shift;
+  $self->ont_typenum ? $self->hardware_type->description : '';
+}
+
+=item search HASHREF
+
+Returns a qsearch hash expression to search for parameters specified in
+HASHREF.
+
+Parameters are those in L<FS::svc_Common/search>, plus:
+
+ont_typenum - the ONT L<FS::hardware_type> key
+
+oltnum - the OLT L<FS::fiber_olt> key
+
+shelf, card, olt_port - the OLT port location fields
+
+vlan - the VLAN number
+
+ont_serial - the ONT serial number
+
+=cut
+
+sub _search_svc {
+  my ($class, $params, $from, $where) = @_;
+
+  # make this simple: all of these are numeric fields, except that 0 means null
+  foreach my $field (qw(ont_typenum oltnum shelf olt_port card vlan)) {
+    if ( $params->{$field} =~ /^(\d+)$/ ) {
+      push @$where, "COALESCE($field,0) = $1";
+    }
+  }
+  if ( length($params->{ont_serial}) ) {
+    my $string = dbh->quote('%'.$params->{ont_serial}.'%');
+    push @$where, "LOWER(ont_serial) LIKE LOWER($string)";
+  }
+
+}
+
+#stub still needed under 4.x+
+
+sub hardware_type {
+  my $self = shift;
+  $self->ont_typenum ? FS::hardware_type->by_key($self->ont_typenum) : '';
+}
+
+=back
+
+=head1 SEE ALSO
+
+L<FS::svc_Common>, L<FS::Record>, L<FS::cust_svc>, L<FS::part_svc>,
+L<FS::cust_pkg>, schema.html from the base documentation.
+
+=cut
+
+1;
+
index f1195ac..176fe78 100644 (file)
@@ -860,3 +860,5 @@ FS/report_batch.pm
 t/report_batch.t
 FS/password_history.pm
 t/password_history.t
+FS/fiber_olt.pm
+t/fiber_olt.t
index b66765a..107aa19 100755 (executable)
@@ -47,6 +47,10 @@ foreach my $pay_batch (@batches) {
   my $filename = time2str('%Y%m%d', time) . '-' . sprintf('%06d.csv',$batchnum);
   print STDERR "Exporting batch $batchnum to $filename...\n" if $opt_v;
   my $text = $pay_batch->export_batch(format => 'eft_canada');
+  unless ($text) {
+    print STDERR "Batch is empty, resolving..." if $opt_v;
+    next;
+  }
   open OUT, ">$tmpdir/$filename";
   print OUT $text;
   close OUT;
index 5ae147d..799e6c4 100755 (executable)
@@ -68,6 +68,10 @@ foreach my $pay_batch (@batches) {
   my $filename = sprintf('%06d',$batchnum) . '-' .time2str('%Y%m%d%H%M%S', time);
   print STDERR "Exporting batch $batchnum to $filename...\n" if $opt_v;
   my $text = $pay_batch->export_batch(format => 'paymentech');
+  unless ($text) {
+    print STDERR "Batch is empty, resolving..." if $opt_v;
+    next;
+  }
   $text =~ s!<fileID>FILEID</fileID>!<fileID>$filename</fileID>! 
     or log_and_die("couldn't find FILEID tag\n");
   open OUT, ">$tmpdir/$filename.xml";
@@ -80,6 +84,7 @@ foreach my $pay_batch (@batches) {
   log_and_die("failed to create zip file\n") if (! -f "$tmpdir/$filename.zip" );
   push @filenames, $filename;
 }
+log_and_die("All batches empty\n") if !@filenames;
 
 my $host = ($opt_t ? 'orbitalbatchvar.paymentech.net'
                    : 'orbitalbatch.paymentech.net');
index 5250102..3fff32a 100755 (executable)
@@ -70,6 +70,10 @@ foreach my $pay_batch (@batches) {
   debug "Exporting batch $batchnum to $filename\n";
 
   my $text = $pay_batch->export_batch(format => 'RBC');
+  unless ($text) {
+    print STDERR "Batch is empty, resolving..." if $opt_v;
+    next;
+  }
   write_file("$tmpdir/$filename", $text);
 
   debug "Uploading $filename...";
diff --git a/FS/t/fiber_olt.t b/FS/t/fiber_olt.t
new file mode 100644 (file)
index 0000000..4caada1
--- /dev/null
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::fiber_olt;
+$loaded=1;
+print "ok 1\n";
diff --git a/httemplate/browse/fiber_olt.html b/httemplate/browse/fiber_olt.html
new file mode 100644 (file)
index 0000000..bffd5f2
--- /dev/null
@@ -0,0 +1,11 @@
+<& elements/browse-simple.html,
+  'table'               => 'fiber_olt',
+  'title'               => 'Fiber service OLTs',
+  'menubar'             => [ ],
+  'name_singular'       => 'OLT',
+  'acl'                 => 'Configuration',
+# overrides
+  'disabled_statuspos' => 3,
+  'header' => [ '#',      'Name',     'Serial' ],
+  'fields' => [ 'oltnum', 'oltname',  'serial' ],
+&>
diff --git a/httemplate/edit/fiber_olt.html b/httemplate/edit/fiber_olt.html
new file mode 100644 (file)
index 0000000..ab7d2f0
--- /dev/null
@@ -0,0 +1,24 @@
+<& elements/edit.html,
+  'popup'         => 1,
+  'table'         => 'fiber_olt',
+  'name_singular' => 'OLT',
+  'labels'        => \%labels,
+  'fields'        => \@fields,
+&>
+<%init>
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my @fields = (
+  'oltname',
+  'serial',
+  { field => 'disabled', type => 'checkbox', value => 'Y' }
+);
+
+my %labels = (
+  'oltnum' => '',
+  'oltname' => 'Name',
+  'serial'  => 'Serial',
+  'disabled' => 'Disabled',
+);
+</%init>
index bbce7c7..709a240 100644 (file)
@@ -18,6 +18,7 @@
                             { 'field' => 'msgnum',
                               'type' => 'select-msg_template',
                               'empty_label' => 'Select template',
+                              'value' => $msgnum,
                               'required' => 1,
                             },
                           ],
@@ -42,4 +43,14 @@ my %opts = @_;
 die "access denied"
   unless $FS::CurrentUser::CurrentUser->access_right([ 'View system logs', 'Configuration' ]);
 
+my $msgnum = $cgi->param('msgnum');
+unless ($msgnum) {
+  my ($msg_template) = qsearch('msg_template',{ msgname => 'System log' });
+  # doesn't seem worth having a config just for the default selected template
+  # if they've deleted the system-generated one, just default to empty "Select template"
+  if ($msg_template) {
+    $msgnum = $msg_template->msgnum;
+  }
+}
+
 </%init>
diff --git a/httemplate/edit/process/fiber_olt.html b/httemplate/edit/process/fiber_olt.html
new file mode 100644 (file)
index 0000000..c8c4af4
--- /dev/null
@@ -0,0 +1,11 @@
+<& elements/process.html,
+  'table'         => 'fiber_olt',
+  'viewall_dir'   => 'browse',
+  'popup_reload'  => 'Updating',
+&>
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+</%init>
diff --git a/httemplate/edit/process/svc_fiber.html b/httemplate/edit/process/svc_fiber.html
new file mode 100644 (file)
index 0000000..815a8ee
--- /dev/null
@@ -0,0 +1,11 @@
+<& elements/svc_Common.html,
+    table       => 'svc_fiber',
+    edit_ext    => 'html',
+    redirect    => popurl(3)."view/svc_fiber.cgi?",
+&>
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Provision customer service'); #something else more specific?
+
+</%init>
index 4d87fab..14c3f51 100644 (file)
@@ -328,6 +328,8 @@ function bill_now_changed (what) {
 </TR>
 
 % my $row = 0;
+% # quotation details are handled by quotation_pkg_detail records, added via link from view/quotation.html
+% # the details below get attached to the part_pkg record, and there's no way to edit that from quotations
 % unless ($quotationnum) {
 <TR>
   <TD></TD>
diff --git a/httemplate/edit/svc_fiber.html b/httemplate/edit/svc_fiber.html
new file mode 100644 (file)
index 0000000..e07caa1
--- /dev/null
@@ -0,0 +1,36 @@
+<& elements/svc_Common.html,
+  'table'             => 'svc_fiber',
+  'fields'            => \@fields,
+&>
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Provision customer service'); #something else more specific?
+
+my $conf = new FS::Conf;
+
+my @fields = (
+  { field     => 'oltnum',
+    type      => 'select-table',
+    table     => 'fiber_olt',
+    name_col  => 'oltname',
+    hashref   => { disabled => '' },
+    disable_empty => 1,
+  },
+  { field     => 'shelf' },
+  { field     => 'card'  },
+  { field     => 'olt_port' },
+  { field     => 'ont_id'   },
+  { field     => 'ont_typenum',
+    type      => 'select-hardware_type'
+  },
+  { field     => 'ont_serial' },
+  { field     => 'ont_port' },
+  { field     => 'vlan' },
+  { field     => 'signal' },
+  { field     => 'speed_down' },
+  { field     => 'speed_up' },
+  { field     => 'ont_install', size => 50 },
+);
+
+</%init>
diff --git a/httemplate/elements/broadband_snmp_get-dialog.html b/httemplate/elements/broadband_snmp_get-dialog.html
deleted file mode 100644 (file)
index 61bb9c7..0000000
+++ /dev/null
@@ -1,68 +0,0 @@
-<%doc>
-Adds a link to display snmp statistics based on broadband_snmp_get export config.
-Performs necessary checks such that, if no such exports are configured for the passed 
-service, returns blank space (ie may be safely invoked even if no exports are configured.)
-
-  <& '/elements/broadband_snmp_get-dialog.html', svc => $svc &>
-
-</%doc>
-% if (@snmp) {
-<& '/elements/xmlhttp.html',
-   'url'  => $fsurl.'misc/xmlhttp-broadband_snmp_get.cgi',
-   'subs' => [ 'broadband_snmp_get_request' ]
- &>
-<SCRIPT>
-function broadband_snmp_get (svcnum) {
-  var jqd = $( '#broadband_snmp_get_dialog' );
-  if (!jqd.dialog( 'isOpen' )) {
-    jqd.dialog( 'open' );
-  }
-  document.getElementById('broadband_snmp_get_dialog').innerHTML = '<B>Loading...</B>';
-  broadband_snmp_get_request('svcnum',svcnum,
-    function (result) {
-      var objects = JSON.parse(result) || [];
-      if (objects.length) {
-        var table = document.createElement('table');
-        for (i = 0; i < objects.length; i++) {
-          var row = document.createElement('tr');
-          var obj = objects[i];
-          if (obj.error) {
-            var cell = document.createElement('td');
-            cell.colSpan = '2';
-            cell.innerHTML = obj['error'];
-            row.appendChild(cell);
-          } else {
-              var cell = document.createElement('td');
-              cell.innerHTML = obj['label'];
-              row.appendChild(cell);
-              cell = document.createElement('td');
-              cell.innerHTML = obj['value'];
-              row.appendChild(cell);
-          }
-          table.appendChild(row);
-        }
-        var dialog = document.getElementById('broadband_snmp_get_dialog');
-        dialog.innerHTML = '';
-        dialog.appendChild(table);
-      }  // if objects.length
-    }  // function
-  ); // broadband_snmp_get_request
-} // broadband_snmp_get
-</SCRIPT>
-<SPAN ID="broadband_snmp_get_dialog"></SPAN>
-<SPAN ID="broadband_snmp_get_link">
-<A HREF="javascript: void(0)" onclick="broadband_snmp_get('<% $svcnum %>')">(snmp)</A>
-</SPAN>
-<SCRIPT>
-$( '#broadband_snmp_get_dialog' ).dialog({
-  position: { my: "left top", at: "left top", of: "#broadband_snmp_get_link" },
-  autoOpen: false,
-  title: 'SNMP',
-});
-</SCRIPT>
-% } #if @snmp
-<%init>
-my(%opt) = @_;
-my @snmp = $opt{'svc'}->cust_svc->part_svc->part_export('broadband_snmp_get');
-my $svcnum = $opt{'svc'}->svcnum;
-</%init>
diff --git a/httemplate/elements/broadband_snmp_get.html b/httemplate/elements/broadband_snmp_get.html
new file mode 100644 (file)
index 0000000..d4cc4e4
--- /dev/null
@@ -0,0 +1,56 @@
+<%doc>
+Adds a table to display dynamically loaded snmp statistics based on broadband_snmp_get 
+export config.  Performs necessary checks such that, if no such exports are configured 
+for the passed service, returns blank space (ie may be safely invoked even if no exports 
+are configured.)
+
+  <& '/elements/broadband_snmp_get.html', svc => $svc &>
+
+</%doc>
+% if (@snmp) {
+<& '/elements/xmlhttp.html',
+   'url'  => $fsurl.'misc/xmlhttp-broadband_snmp_get.cgi',
+   'subs' => [ 'broadband_snmp_get_request' ]
+ &>
+<DIV ID="broadband_snmp_get"></DIV>
+<SCRIPT>
+function broadband_snmp_get (svcnum) {
+  document.getElementById('broadband_snmp_get').innerHTML = 'Loading SNMP...';
+  broadband_snmp_get_request('svcnum',svcnum,
+    function (result) {
+      var objects = JSON.parse(result) || [];
+      if (objects.length) {
+        var table = document.createElement('table');
+        for (i = 0; i < objects.length; i++) {
+          var row = document.createElement('tr');
+          var obj = objects[i];
+          if (obj.error) {
+            var cell = document.createElement('td');
+            cell.colSpan = '2';
+            cell.innerHTML = obj['error'];
+            row.appendChild(cell);
+          } else {
+              var cell = document.createElement('td');
+              cell.innerHTML = obj['label'];
+              row.appendChild(cell);
+              cell = document.createElement('td');
+              cell.innerHTML = obj['value'];
+              row.appendChild(cell);
+          }
+          table.appendChild(row);
+        }
+        var resultblock = document.getElementById('broadband_snmp_get');
+        resultblock.innerHTML = '';
+        resultblock.appendChild(table);
+      }  // if objects.length
+    }  // function
+  ); // broadband_snmp_get_request
+} // broadband_snmp_get
+broadband_snmp_get('<% $svcnum %>');
+</SCRIPT>
+% } #if @snmp
+<%init>
+my(%opt) = @_;
+my @snmp = $opt{'svc'}->cust_svc->part_svc->part_export('broadband_snmp_get');
+my $svcnum = $opt{'svc'}->svcnum;
+</%init>
index fa44e86..a3bfeb7 100644 (file)
@@ -227,10 +227,12 @@ foreach my $svcdb ( FS::part_svc->svc_tables() ) {
       ];
   }
 
-  $report_svc{"Advanced $lcsname reports"} = 
+  if ( $curuser->access_right("Services: $name: Advanced search")
+    && $svcdb =~ /^svc_(acct|broadband|hardware|phone|fiber)$/ ) {
+
+    $report_svc{"Advanced $lcsname reports"} = 
       [ $fsurl."search/report_$svcdb.html", '' ]
-    if $svcdb =~ /^svc_(acct|broadband|hardware|phone)$/
-    && $curuser->access_right("Services: $name: Advanced search");
+  }
 
   if ( $svcdb eq 'svc_phone' ) {
 
@@ -594,6 +596,10 @@ tie my %config_circuit, 'Tie::IxHash',
   'Termination types' => [ $fsurl.'browse/circuit_termination.html',  '' ],
 ;
 
+tie my %config_fiber, 'Tie::IxHash',
+  'OLTs' => [ $fsurl.'browse/fiber_olt.html', '' ],
+;
+
 tie my %config_export_svc, 'Tie::IxHash', ();
 if ( $curuser->access_right('Configuration') ) {
   $config_export_svc{'Service definitions'} = [ $fsurl.'browse/part_svc.cgi', 'Services are items you offer to your customers' ];
@@ -617,6 +623,8 @@ $config_export_svc{'Alarm'} = [ \%config_alarm, '' ]
   if $curuser->access_right(['Alarm configuration', 'Alarm global configuration']);
 $config_export_svc{'Circuits'} = [ \%config_circuit, '' ]
   if $curuser->access_right('Configuration');
+$config_export_svc{'Fiber'} = [ \%config_fiber, '' ]
+  if $curuser->access_right('Configuration');
 $config_export_svc{'Hardware types'} = [ $fsurl.'browse/hardware_class.html', 'Set up hardware type catalog' ]
   if $curuser->access_right('Configuration');
 
index b824fb2..0c4fb02 100644 (file)
@@ -114,7 +114,12 @@ function confirm_standardize(arg) {
 
     // then all entered address fields are correct
     // but we still need to set the lat/long fields and addr_clean
-    status_message('Verified');
+
+    if ( returned['addr_clean'] ) {
+      status_message('Verified');
+    } else {
+      status_message('Unverified');
+    }
     replace_address();
 
   } else {
index f3a31eb..c4bc37e 100644 (file)
@@ -1,4 +1,4 @@
-<% $pay_batch->export_batch(%opt) %><%init>
+<% $exporttext %><%init>
 
 #http_header('Content-Type' => 'text/comma-separated-values' ); #IE chokes
 http_header('Content-Type' => 'text/plain' ); # not necessarily correct...
@@ -23,4 +23,15 @@ elsif ( $cgi->param('format') =~ /^([\w\- ]+)$/ ) {
 my $pay_batch = qsearchs('pay_batch', { batchnum => $batchnum } );
 die "Batch not found: '$batchnum'" if !$pay_batch;
 
+my $exporttext = $pay_batch->export_batch(%opt);
+unless ($exporttext) {
+  http_header('Content-Type' => 'text/html' );
+  $exporttext = <<EOF;
+<SCRIPT>
+alert('Batch was empty, and has been resolved');
+window.top.location.href = '${p}search/pay_batch.cgi?magic=_date;open=1;intransit=1;resolved=1';
+</SCRIPT>
+EOF
+}
+
 </%init>
index e9aece2..2bfbd7c 100644 (file)
@@ -59,8 +59,10 @@ my $conf = new FS::Conf;
 my $title = 'Outgoing Message Log';
 
 #here is the agent virtualization
-my $agentnums_sql =
-  $FS::CurrentUser::CurrentUser->agentnums_sql( 'table' => 'cust_main' );
+my $agentnums_sql = $FS::CurrentUser::CurrentUser->agentnums_sql(
+  'table' => 'cust_main',
+  'null_right' => [ 'View system logs', 'Configuration' ],
+);
 
 my @where = ( $agentnums_sql );
 
index a707928..b607f50 100644 (file)
@@ -138,7 +138,7 @@ my $tt_sub = sub {
   return '' if @context == 1 and length($log->message) <= 60;
   my $html = '<DIV CLASS="tooltip">'.(shift @context).'</DIV>';
   my $pre = '&#8627;';
-  foreach (@context, $log->message) {
+  foreach (map encode_entities($_), @context, $log->message) {
     $html .= "<DIV>$pre$_</DIV>";
     $pre = '&nbsp;&nbsp;&nbsp;'.$pre;
   }
diff --git a/httemplate/search/report_svc_fiber.html b/httemplate/search/report_svc_fiber.html
new file mode 100755 (executable)
index 0000000..d563c53
--- /dev/null
@@ -0,0 +1,47 @@
+<& /elements/header.html, $title &>
+
+<FORM ACTION="svc_fiber.html" METHOD="GET">
+
+  <TABLE BGCOLOR="#cccccc" CELLSPACING=0>
+    <TR>
+      <TH CLASS="background" COLSPAN=2 ALIGN="left"><FONT SIZE="+1">Search options</FONT></TH>
+    </TR>
+
+    <& /elements/tr-select-table.html,
+      label     => 'OLT',
+      table     => 'fiber_olt',
+      name_col  => 'oltname',
+    &>
+    <& /elements/tr-input-text.html, field => 'shelf', label => 'Shelf' &>
+    <& /elements/tr-input-text.html, field => 'card',  label => 'Card' &>
+    <& /elements/tr-input-text.html, field => 'olt_port', label => 'Port' &>
+
+    <& /elements/tr-td-label.html, label => 'ONT model' &>
+      <TD>
+      <& /elements/select-hardware_type.html,
+          'empty_label' => '(all)'
+       &>
+      </TD>
+    </TR>
+
+    <& /elements/tr-input-text.html, field => 'ont_serial', label => 'Serial number' &>
+    <& /elements/tr-input-text.html, field => 'vlan', label => 'VLAN' &>
+  </TABLE>
+
+<BR>
+<INPUT TYPE="submit" VALUE="Search">
+
+</FORM>
+
+<& /elements/footer.html &>
+<%init>
+
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Services: Fiber');
+
+my $title = 'Search Fiber Services';
+
+my @olts = qsearch('fiber_olt', {});
+
+</%init>
+
diff --git a/httemplate/search/svc_fiber.html b/httemplate/search/svc_fiber.html
new file mode 100644 (file)
index 0000000..0cb735c
--- /dev/null
@@ -0,0 +1,69 @@
+<& elements/svc_Common.html,
+  'title'       => 'Fiber Search Results',
+  'name'        => 'fiber services',
+  'query'       => $query,
+  'count_query' => $query->{'count_query'},
+  'redirect'    => $link,
+  'header'      => [ '#',
+                     'OLT',
+                     'Shelf/Card/Port',
+                     'ONT',
+                     'Model',
+                     'Serial',
+                     FS::UI::Web::cust_header($cgi->param('cust_fields')),
+                   ],
+  'fields'      => [ 'svcnum',
+                     'oltname',
+                     sub { my $svc = shift;
+                           join('-', $svc->shelf, $svc->card, $svc->olt_port)
+                     },
+                     'ont_id',
+                     'ont_description',
+                     'ont_serial',
+                     \&FS::UI::Web::cust_fields,
+                   ],
+  'links'       => [ $link,
+                     '',
+                     '',
+                     $link,
+                     $link,
+                     $link,
+                     FS::UI::Web::cust_links($cgi->param('cust_fields')),
+                   ],
+  'align'       => 'rlllll'.  FS::UI::Web::cust_aligns(),
+  'color'       => [ 
+                     ('') x 6,
+                     FS::UI::Web::cust_colors(),
+                   ],
+  'style'       => [ 
+                     ('') x 6,
+                     FS::UI::Web::cust_styles(),
+                   ],
+
+&>
+<%init>
+
+die "access denied" unless
+  $FS::CurrentUser::CurrentUser->access_right([ 'Services: Fiber',
+                                                'List services'
+                                              ]);
+
+my $conf = new FS::Conf;
+
+my %search_hash;
+if ( $cgi->param('magic') eq 'unlinked' ) {
+  %search_hash = ( 'unlinked' => 1 );
+} else {
+  foreach (qw( custnum agentnum svcpart cust_fields
+               ont_typenum oltnum shelf olt_port card vlan )) {
+    $search_hash{$_} = $cgi->param($_) if defined($cgi->param($_));
+  }
+}
+
+my $query = FS::svc_fiber->search(\%search_hash);
+$query->{addl_from} .= ' LEFT JOIN fiber_olt USING (oltnum) ';
+$query->{select} .= ', oltname';
+
+my $link = [ $p.'view/svc_fiber.cgi?', 'svcnum' ];
+
+</%init>
index 99d91e5..595d6b3 100644 (file)
@@ -91,7 +91,7 @@ sub pkg_change_location_link {
     'actionlabel' => emt('Change'),
     'cust_pkg'    => $cust_pkg,
     'width'       => 960,
-    'height'      => 490,
+    'height'      => 530,
   );
 }
 
index e98b95e..dec1c6f 100644 (file)
@@ -371,7 +371,7 @@ sub pkg_change_location_link {
     'actionlabel' => emt('Change'),
     'cust_pkg'    => $cust_pkg,
     'width'       => 960,
-    'height'      => 490,
+    'height'      => 530,
   );
 }
 
index bc272e8..0517c30 100644 (file)
@@ -72,7 +72,6 @@ sub ip_addr {
   my $out = $ip_addr;
   $out .= ' (' . include('/elements/popup_link-ping.html', ip => $ip_addr) . ')'
     if $ip_addr;
-  $out .= include('/elements/broadband_snmp_get-dialog.html', svc => $svc);
   if ($svc->cust_svc->part_svc->part_export('cacti')) {
     $out .= ' (<A HREF="'
          .  popurl(2)
@@ -84,6 +83,7 @@ sub ip_addr {
     $out .= '<br>Netmask: ' . $addr_block->NetAddr->mask .
             '<br>Gateway: ' . $addr_block->ip_gateway;
   }
+  $out .= include('/elements/broadband_snmp_get.html', svc => $svc);
   $out;
 }
 
diff --git a/httemplate/view/svc_fiber.cgi b/httemplate/view/svc_fiber.cgi
new file mode 100644 (file)
index 0000000..ce9c03d
--- /dev/null
@@ -0,0 +1,34 @@
+<& elements/svc_Common.html,
+  'table'       => 'svc_fiber',
+  'fields'      => \@fields,
+  'labels'      => \%labels,
+  'edit_url'    => $fsurl.'edit/svc_fiber.html?',
+&>
+<%init>
+
+my @fields = (
+  { field     => 'oltnum',
+    type      => 'select-table',
+    table     => 'fiber_olt',
+    name_col  => 'oltname',
+  },
+  'shelf',
+  'card',
+  'olt_port',
+  'ont_id',
+  'ont_description',
+  'ont_serial',
+  'ont_port',
+  'vlan',
+  'signal',
+  'speed_down',
+  'speed_up',
+  'ont_install',
+);
+
+my $fields = FS::svc_fiber->table_info->{'fields'};
+my %labels = map { $_ => $fields->{$_}{'label'} } keys %$fields;
+
+$labels{'ont_description'} = 'ONT model';
+
+</%init>
index 94df549..e8f8468 100755 (executable)
@@ -4,22 +4,6 @@
 
 <form action="Tasks.html" method="post">
 <input type="hidden" name="Queue" value="<% $Queue %>" />
-<h2>
-  <label for="ConditionCF"><&|/l&>Enabled if</&>:</label>
-% if ( $PossibleCustomFields->Count > 0 ) {
-  <select name="ConditionCF">
-%   while ( my $thiscf = $PossibleCustomFields->Next ) {
-    <option value="<% $thiscf->Id %>" <% $thiscf->Id == $cfid ? 'selected' : '' %>><% $thiscf->Name %></option>
-%   }
-  </select>
-  <label for="ConditionValue"><&|/l&>equals</&></label>
-  <input name="ConditionValue" value="<% $cfvalue %>" />
-% } else {
-  <select name="no_cfs" disabled>
-    <option value="1">(no custom fields defined)</option>
-  </select>
-% }
-</h2>
 <table>
 % my (@links, @postponed); # not really used here
 % my $idx = 1;
@@ -93,6 +77,7 @@ my $title = loc("Set up subtasks for queue [_1]", $QueueObj->Name);
 my $TEMPLATE_NAME = '[Subtask]';
 my $SCRIPCONDITION_NAME = '[Subtask] Queue='.$Queue;
 my $SUBJECT_PREFIX = q({ $TOP->Subject }-);
+my $CUSTOMFIELD_NAME = 'Create subtasks';
 
 my ($Scrip, $ScripCondition, $Template, $CustomField);
 
@@ -112,20 +97,17 @@ $Scrip = RT::Scrip->new($RT::SystemUser);
 {
   my $Scrips = RT::Scrips->new($RT::SystemUser);
   $Scrips->LimitToQueue($Queue);
-  $Scrips->Limit( FIELD => 'Template', VALUE => $Template->Id );
+  $Scrips->Limit( FIELD => 'Template', VALUE => $Template->Name );
   if ( $Scrips->Count > 0 ) {
     $Scrip = $Scrips->First;
   }
 }
 
-# The CF name to test, and the value it must have to trigger the scrip.
-my $cfid = $ARGS{ConditionCF};
-my $cfvalue = $ARGS{ConditionValue};
-$CustomField = RT::CustomField->new($session{'CurrentUser'});
-if ( $cfid ) {
-  $CustomField->Load($cfid);
-}
-my $cfname = $CustomField->Name;
+my $CustomField = RT::CustomField->new($RT::SystemUser);
+$CustomField->LoadByName(
+  Name        => $CUSTOMFIELD_NAME,
+  LookupType  => 'RT::Queue-RT::Ticket',
+);
 
 # if there's input from the form, process it into a new template content
 my $new_content = '';
@@ -138,7 +120,7 @@ if ( $ARGS{task_id} ) { # actually contains numeric indices
     my %task_opts = map { $_ => $ARGS{$_} }
                     grep /^$task_id-/, keys(%ARGS);
     my $task_content = "===Create-Ticket: $task_id
-CF-$cfname:" . q[
+CF-$CUSTOMFIELD_NAME" . q[
 Depended-On-By: TOP
 Owner: { $TOP->Owner }
 { join("\n", map { "Requestor: $_" }
@@ -173,6 +155,45 @@ Owner: { $TOP->Owner }
     }
   }
 
+  # set up custom field, if necessary
+  if ( ! $CustomField->Id ) {
+    # should be RenderType 'Checkbox', but there isn't one yet...
+    my ($val, $msg) = $CustomField->Create(
+      Name          => $CUSTOMFIELD_NAME,
+      Type          => 'Select',
+      MaxValues     => 1,
+      LookupType    => 'RT::Queue-RT::Ticket',
+      Description   => 'Start subtasks for this ticket',
+      RenderType    => 'Dropdown',
+    );
+    if ($val) {
+      # should be impossible for this to fail
+      ($val, $msg) = $CustomField->AddValue(Name => 'Yes');
+    }
+    if (!$val) {
+      push @results, loc("Could not create ticket custom field: [_1]", $msg);
+    } else {
+      push @results, loc("Custom field created");
+    }
+  }
+
+  # apply CF to the queue, iff there are any tasks set up
+  if ( length($new_content) and ! $CustomField->IsAdded($Queue) ) {
+    my ($val, $msg) = $CustomField->AddToObject($QueueObj);
+    if (!$val) {
+      push @results, loc("Could not apply custom field to this queue: [_1]", $msg);
+    } else {
+      push @results, loc("Applied custom field to this queue");
+    }
+  } elsif ( ! length($new_content) and $CustomField->IsAdded($Queue) ) {
+    my ($val, $msg) = $CustomField->RemoveFromObject($QueueObj);
+    if (!$val) {
+      push @results, loc("Could not remove custom field from this queue: [_1]", $msg);
+    } else {
+      push @results, loc("Removed custom field from this queue");
+    }
+  }
+
   if ( ! $Template->Id ) {
     my ( $val, $msg ) = $Template->Create(
       Queue           => $Queue,
@@ -196,16 +217,12 @@ Owner: { $TOP->Owner }
   }
 
   # Set up ScripCondition
-  if ( !$cfname ) {
-    push @results, loc("No custom field selected");
-  } elsif ( length($cfvalue) == 0 ) {
-    push @results, loc("Custom field value is required");
-  } elsif ( ! $ScripCondition->Id ) {
+  if ( ! $ScripCondition->Id ) {
     my ( $val, $msg ) = $ScripCondition->Create(
       Name            => $SCRIPCONDITION_NAME,
-      Description     => "When CF.[$cfname] equals '$cfvalue'",
+      Description     => "When CF.[$CUSTOMFIELD_NAME] equals 'Yes'",
       ExecModule      => 'CustomFieldEquals',
-      Argument        => "$cfname=$cfvalue",
+      Argument        => "$CUSTOMFIELD_NAME=Yes",
       ApplicableTransTypes => 'Any',
     );
     if (!$val) {
@@ -213,8 +230,8 @@ Owner: { $TOP->Owner }
     } else {
       push @results, loc("Custom field condition created");
     }
-  } elsif ( $ScripCondition->Argument ne "$cfname=$cfvalue" ) {
-    my ( $val, $msg ) = $ScripCondition->SetArgument("$cfname=$cfvalue");
+  } elsif ( $ScripCondition->Argument ne "$CUSTOMFIELD_NAME=Yes" ) {
+    my ( $val, $msg ) = $ScripCondition->SetArgument("$CUSTOMFIELD_NAME=Yes");
     if (!$val) {
       push @results, loc("Could not set custom field condition: [_1]", $msg);
     } else {
@@ -259,8 +276,6 @@ $Action->Parse(
 my @task_ids;
 @task_ids = @{ $Action->{create_tickets} } if exists $Action->{create_tickets};
 
-my $PossibleCustomFields = $QueueObj->TicketCustomFields;
-
 </%init>
 <%ARGS>
 $Queue => undef         #queue id