'Services: Alarm services',
'Services: Video',
'Services: Circuits',
+ 'Services: Fiber',
+ 'Services: Fiber: Advanced search',
'Services: External services',
'Usage: RADIUS sessions',
'Usage: Call Detail Records (CDRs)',
'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',
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 ) {
use URI::Escape 3.31;
use Data::Dumper;
use FS::Conf;
+use FS::Log;
use Locale::Country;
FS::UID->install_callback( sub {
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 $@;
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,
} 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
}
}
],
},
+ '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', '', '', '', '',
# - 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'}
&& ( ! $cust_pkg->getfield('susp') )
)
)
+ and ( ! $cust_pkg->expire
+ || $cust_pkg->expire > $cmp_time )
)
{
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
&& $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
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.
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
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',
});
# 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;
--- /dev/null
+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;
+
sub country_full {
my $self = shift;
- $self->code2country($self->country);
+ $self->code2country($self->get('country'));
}
sub code2country {
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...
|| $self->ut_textn('tablename')
|| $self->ut_numbern('tablenum')
|| $self->ut_number('level')
- || $self->ut_text('message')
+ || $self->ut_anything('message')
;
return $error if $error;
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
###
$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
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} );
}
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
+ ],
+ },
];
}
'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.
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.
# 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;
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 {
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";
- 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 {
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
--- /dev/null
+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;
+
t/report_batch.t
FS/password_history.pm
t/password_history.t
+FS/fiber_olt.pm
+t/fiber_olt.t
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;
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";
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');
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...";
--- /dev/null
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::fiber_olt;
+$loaded=1;
+print "ok 1\n";
--- /dev/null
+<& 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' ],
+&>
--- /dev/null
+<& 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>
{ 'field' => 'msgnum',
'type' => 'select-msg_template',
'empty_label' => 'Select template',
+ 'value' => $msgnum,
'required' => 1,
},
],
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>
--- /dev/null
+<& elements/process.html,
+ 'table' => 'fiber_olt',
+ 'viewall_dir' => 'browse',
+ 'popup_reload' => 'Updating',
+&>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+</%init>
--- /dev/null
+<& 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>
</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>
--- /dev/null
+<& 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>
+++ /dev/null
-<%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>
--- /dev/null
+<%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>
];
}
- $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' ) {
'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' ];
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');
// 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 {
-<% $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...
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>
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 );
return '' if @context == 1 and length($log->message) <= 60;
my $html = '<DIV CLASS="tooltip">'.(shift @context).'</DIV>';
my $pre = '↳';
- foreach (@context, $log->message) {
+ foreach (map encode_entities($_), @context, $log->message) {
$html .= "<DIV>$pre$_</DIV>";
$pre = ' '.$pre;
}
--- /dev/null
+<& /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>
+
--- /dev/null
+<& 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>
'actionlabel' => emt('Change'),
'cust_pkg' => $cust_pkg,
'width' => 960,
- 'height' => 490,
+ 'height' => 530,
);
}
'actionlabel' => emt('Change'),
'cust_pkg' => $cust_pkg,
'width' => 960,
- 'height' => 490,
+ 'height' => 530,
);
}
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)
$out .= '<br>Netmask: ' . $addr_block->NetAddr->mask .
'<br>Gateway: ' . $addr_block->ip_gateway;
}
+ $out .= include('/elements/broadband_snmp_get.html', svc => $svc);
$out;
}
--- /dev/null
+<& 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>
<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;
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);
{
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 = '';
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: $_" }
}
}
+ # 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,
}
# 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) {
} 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 {
my @task_ids;
@task_ids = @{ $Action->{create_tickets} } if exists $Action->{create_tickets};
-my $PossibleCustomFields = $QueueObj->TicketCustomFields;
-
</%init>
<%ARGS>
$Queue => undef #queue id