summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIvan Kohler <ivan@freeside.biz>2014-12-04 10:37:16 -0800
committerIvan Kohler <ivan@freeside.biz>2014-12-04 10:37:16 -0800
commit8dd41f364aaba88969dfd0908feb22709025e7f6 (patch)
tree471cb3796019d873da648413d88ca70a657414ed
parenta2df4ef9575be1ae2f1f5b9089f121316f796bac (diff)
parentbf50a8356a7344b4f75c7bc7f952019b98867f26 (diff)
Merge branch 'master' of git.freeside.biz:/home/git/freeside
-rw-r--r--FS/FS/AccessRight.pm1
-rw-r--r--FS/FS/Mason.pm4
-rw-r--r--FS/FS/Misc/Geo.pm131
-rw-r--r--FS/FS/Report/FCC_477.pm4
-rw-r--r--FS/FS/Schema.pm75
-rw-r--r--FS/FS/Template_Mixin.pm8
-rw-r--r--FS/FS/UI/Web.pm19
-rwxr-xr-xFS/FS/addr_block.pm18
-rw-r--r--FS/FS/cdr/cx3.pm1
-rw-r--r--FS/FS/cdr/earthlink.pm44
-rw-r--r--FS/FS/cdr/thinktel.pm42
-rw-r--r--FS/FS/circuit_provider.pm101
-rw-r--r--FS/FS/circuit_termination.pm98
-rw-r--r--FS/FS/circuit_type.pm98
-rw-r--r--FS/FS/cust_credit.pm21
-rw-r--r--FS/FS/cust_pkg.pm24
-rw-r--r--FS/FS/part_event/Action/Mixin/credit_agent_pkg_class.pm15
-rw-r--r--FS/FS/part_event/Action/Mixin/credit_bill.pm95
-rw-r--r--FS/FS/part_event/Action/Mixin/credit_flat.pm25
-rw-r--r--FS/FS/part_event/Action/Mixin/credit_pkg.pm33
-rw-r--r--FS/FS/part_event/Action/Mixin/credit_sales_pkg_class.pm20
-rw-r--r--FS/FS/part_event/Action/bill_sales_credit.pm91
-rw-r--r--FS/FS/part_event/Action/bill_sales_credit_pkg_class.pm11
-rw-r--r--FS/FS/part_event/Action/pkg_agent_credit.pm5
-rw-r--r--FS/FS/part_event/Action/pkg_agent_credit_pkg_class.pm3
-rw-r--r--FS/FS/part_event/Action/pkg_employee_credit.pm5
-rw-r--r--FS/FS/part_event/Action/pkg_referral_credit.pm24
-rw-r--r--FS/FS/part_event/Action/pkg_sales_credit.pm11
-rw-r--r--FS/FS/part_event/Action/pkg_sales_credit_pkg.pm2
-rw-r--r--FS/FS/part_event/Action/pkg_sales_credit_pkg_class.pm4
-rw-r--r--FS/FS/part_export/send_email.pm3
-rw-r--r--FS/FS/pay_batch/RBC.pm2
-rw-r--r--FS/FS/quotation_pkg.pm20
-rw-r--r--FS/FS/reason.pm13
-rwxr-xr-xFS/FS/router.pm7
-rw-r--r--FS/FS/svc_circuit.pm230
-rw-r--r--FS/FS/svc_phone.pm35
-rw-r--r--FS/MANIFEST8
-rw-r--r--FS/t/circuit_provider.t5
-rw-r--r--FS/t/circuit_termination.t5
-rw-r--r--FS/t/circuit_type.t5
-rw-r--r--FS/t/svc_circuit.t5
-rwxr-xr-xbin/cdr-thinktel.import127
-rwxr-xr-xdebian/rules1
-rwxr-xr-xfs_selfservice/FS-SelfService/cgi/customer_order_pkg.html8
-rw-r--r--fs_selfservice/FS-SelfService/cgi/do_process_forgot_password.html2
-rw-r--r--httemplate/browse/circuit_provider.html11
-rw-r--r--httemplate/browse/circuit_termination.html11
-rw-r--r--httemplate/browse/circuit_type.html11
-rw-r--r--httemplate/browse/elements/browse-simple.html57
-rwxr-xr-xhttemplate/browse/part_pkg-fcc.html33
-rw-r--r--httemplate/docs/part_svc-table.html1
-rw-r--r--httemplate/edit/circuit_provider.html21
-rw-r--r--httemplate/edit/circuit_termination.html21
-rw-r--r--httemplate/edit/circuit_type.html21
-rw-r--r--httemplate/edit/credit-cust_bill_pkg.html4
-rwxr-xr-xhttemplate/edit/cust_credit.cgi2
-rw-r--r--httemplate/edit/cust_main/bottomfixup.js22
-rw-r--r--httemplate/edit/elements/part_svc_column.html5
-rw-r--r--httemplate/edit/elements/svc_Common.html46
-rwxr-xr-xhttemplate/edit/part_pkg.cgi5
-rw-r--r--httemplate/edit/process/bulk-part_pkg-fcc.html6
-rw-r--r--httemplate/edit/process/circuit_provider.html11
-rw-r--r--httemplate/edit/process/circuit_termination.html11
-rw-r--r--httemplate/edit/process/circuit_type.html11
-rw-r--r--httemplate/edit/process/credit-cust_bill_pkg.html26
-rwxr-xr-xhttemplate/edit/process/cust_credit.cgi17
-rw-r--r--httemplate/edit/process/elements/svc_Common.html2
-rw-r--r--httemplate/edit/process/part_event.html15
-rw-r--r--httemplate/edit/process/svc_circuit.html11
-rw-r--r--httemplate/edit/svc_circuit.cgi54
-rw-r--r--httemplate/edit/svc_phone.cgi36
-rw-r--r--httemplate/elements/input-fcc_options.html3
-rw-r--r--httemplate/elements/location.html52
-rw-r--r--httemplate/elements/menu.html8
-rw-r--r--httemplate/elements/standardize_locations.js18
-rw-r--r--httemplate/elements/tr-censustract.html2
-rw-r--r--httemplate/elements/tr-input-fcc_options.html89
-rwxr-xr-xhttemplate/elements/tr-select-reason.html233
-rw-r--r--httemplate/elements/tr-select-svc_circuit.html41
-rw-r--r--httemplate/misc/cancel_cust.html2
-rwxr-xr-xhttemplate/misc/cancel_pkg.html6
-rw-r--r--httemplate/misc/confirm-censustract.html7
-rwxr-xr-xhttemplate/misc/cust_main-cancel.cgi30
-rwxr-xr-xhttemplate/misc/cust_main-suspend.cgi37
-rw-r--r--httemplate/misc/part_pkg_fcc_options.html3
-rwxr-xr-xhttemplate/misc/process/cancel_pkg.html19
-rw-r--r--httemplate/misc/process/elements/reason17
-rw-r--r--httemplate/misc/suspend_cust.html2
-rw-r--r--httemplate/misc/xmlhttp-address_standardize.html4
-rw-r--r--httemplate/search/477.html2
-rwxr-xr-xhttemplate/search/svc_circuit.cgi65
-rwxr-xr-xhttemplate/view/cust_main.cgi6
-rwxr-xr-xhttemplate/view/cust_main/locations.html22
-rw-r--r--httemplate/view/cust_main/packages/location.html22
-rw-r--r--httemplate/view/cust_main/packages/status.html2
-rw-r--r--httemplate/view/elements/svc_Common.html163
-rw-r--r--httemplate/view/svc_circuit.html80
-rw-r--r--httemplate/view/svc_phone.cgi7
-rw-r--r--rt/lib/RT/CustomFields.pm6
-rw-r--r--rt/share/html/Elements/ShowCustomFields37
101 files changed, 2306 insertions, 659 deletions
diff --git a/FS/FS/AccessRight.pm b/FS/FS/AccessRight.pm
index bad831a94..92cede6a5 100644
--- a/FS/FS/AccessRight.pm
+++ b/FS/FS/AccessRight.pm
@@ -310,6 +310,7 @@ tie my %rights, 'Tie::IxHash',
'Services: Mailing lists',
'Services: Alarm services',
'Services: Video',
+ 'Services: Circuits',
'Services: External services',
'Usage: RADIUS sessions',
'Usage: Call Detail Records (CDRs)',
diff --git a/FS/FS/Mason.pm b/FS/FS/Mason.pm
index 900da1005..d3e45dfee 100644
--- a/FS/FS/Mason.pm
+++ b/FS/FS/Mason.pm
@@ -392,6 +392,10 @@ if ( -e $addl_handler_use_file ) {
use FS::deploy_zone_vertex;
use FS::TaxEngine;
use FS::tax_status;
+ use FS::circuit_type;
+ use FS::circuit_provider;
+ use FS::circuit_termination;
+ use FS::svc_circuit;
# Sammath Naur
if ( $FS::Mason::addl_handler_use ) {
diff --git a/FS/FS/Misc/Geo.pm b/FS/FS/Misc/Geo.pm
index e41ba5d76..dbc383a14 100644
--- a/FS/FS/Misc/Geo.pm
+++ b/FS/FS/Misc/Geo.pm
@@ -6,8 +6,7 @@ use vars qw( $DEBUG @EXPORT_OK $conf );
use LWP::UserAgent;
use HTTP::Request;
use HTTP::Request::Common qw( GET POST );
-use HTTP::Cookies;
-use HTML::TokeParser;
+use JSON;
use URI::Escape 3.31;
use Data::Dumper;
use FS::Conf;
@@ -29,7 +28,7 @@ FS::Misc::Geo - routines to fetch geographic information
=over 4
-=item get_censustract LOCATION YEAR
+=item get_censustract_ffiec LOCATION YEAR
Given a location hash (see L<FS::location_Mixin>) and a census map year,
returns a census tract code (consisting of state, county, and tract
@@ -41,105 +40,65 @@ sub get_censustract_ffiec {
my $class = shift;
my $location = shift;
my $year = shift;
+ $year ||= 2013;
- warn Dumper($location, $year) if $DEBUG;
+ if ( length($location->{country}) and uc($location->{country}) ne 'US' ) {
+ return '';
+ }
- my $url = 'http://www.ffiec.gov/Geocode/default.aspx';
+ warn Dumper($location, $year) if $DEBUG;
- my $return = {};
- my $error = '';
+ # the old FFIEC geocoding service was shut down December 1, 2014.
+ # welcome to the future.
+ my $url = 'https://geomap.ffiec.gov/FFIECGeocMap/GeocodeMap1.aspx/GetGeocodeData';
+ # build the single-line query
+ my $single_line = join(', ', $location->{address1},
+ $location->{city},
+ $location->{state}
+ );
+ my $hashref = { sSingleLine => $single_line, iCensusYear => $year };
+ my $request = POST( $url,
+ 'Content-Type' => 'application/json; charset=utf-8',
+ 'Accept' => 'application/json',
+ 'Content' => encode_json($hashref)
+ );
- my $ua = new LWP::UserAgent('cookie_jar' => HTTP::Cookies->new);
- my $res = $ua->request( GET( $url ) );
+ my $ua = new LWP::UserAgent;
+ my $res = $ua->request( $request );
warn $res->as_string
if $DEBUG > 2;
if (!$res->is_success) {
- $error = $res->message;
-
- } else {
-
- my $content = $res->content;
-
- my $p = new HTML::TokeParser \$content;
- my $viewstate;
- my $eventvalidation;
- while (my $token = $p->get_tag('input') ) {
- if ($token->[1]->{name} eq '__VIEWSTATE') {
- $viewstate = $token->[1]->{value};
- }
- if ($token->[1]->{name} eq '__EVENTVALIDATION') {
- $eventvalidation = $token->[1]->{value};
- }
- last if $viewstate && $eventvalidation;
- }
-
- if (!$viewstate or !$eventvalidation ) {
+ die "Census tract lookup error: ".$res->message;
- $error = "either no __VIEWSTATE or __EVENTVALIDATION found";
-
- } else {
-
- my($zip5, $zip4) = split('-',$location->{zip});
-
- $year ||= '2013';
- my @ffiec_args = (
- __VIEWSTATE => $viewstate,
- __EVENTVALIDATION => $eventvalidation,
- __VIEWSTATEENCRYPTED => '',
- ddlbYear => $year,
- txtAddress => $location->{address1},
- txtCity => $location->{city},
- ddlbState => $location->{state},
- txtZipCode => $zip5,
- btnSearch => 'Search',
- );
- warn join("\n", @ffiec_args )
- if $DEBUG > 1;
-
- push @{ $ua->requests_redirectable }, 'POST';
- $res = $ua->request( POST( $url, \@ffiec_args ) );
- warn $res->as_string
- if $DEBUG > 2;
-
- unless ($res->code eq '200') {
-
- $error = $res->message;
-
- } else {
-
- my @id = qw( MSACode StateCode CountyCode TractCode );
- $content = $res->content;
- warn $res->content if $DEBUG > 2;
- $p = new HTML::TokeParser \$content;
- my $prefix = 'UcGeoResult11_lb';
- my $compare =
- sub { my $t=shift; scalar( grep { lc($t) eq lc("$prefix$_")} @id ) };
-
- while (my $token = $p->get_tag('span') ) {
- next unless ( $token->[1]->{id} && &$compare( $token->[1]->{id} ) );
- $token->[1]->{id} =~ /^$prefix(\w+)$/;
- $return->{lc($1)} = $p->get_trimmed_text("/span");
- }
-
- unless ( $return->{tractcode} ) {
- warn "$error: $content ". Dumper($return) if $DEBUG;
- $error = "No census tract found";
- }
- $return->{tractcode} .= ' '
- unless $error || $JSON::VERSION >= 2; #broken JSON 1 workaround
+ }
- } #unless ($res->code eq '200')
+ local $@;
+ my $content = eval { decode_json($res->content) };
+ die "Census tract JSON error: $@\n" if $@;
- } #unless ($viewstate)
+ if ( !exists $content->{d}->{sStatus} ) {
+ die "Census tract response is missing a status indicator.\nThis is an FFIEC problem.\n";
+ }
+ if ( $content->{d}->{sStatus} eq 'Y' ) {
+ # success
+ # this also contains the (partial) standardized address, correct zip
+ # code, coordinates, etc., and we could get all of them, but right now
+ # we only want the census tract
+ my $tract = join('', $content->{d}->{sStateCode},
+ $content->{d}->{sCountyCode},
+ $content->{d}->{sTractCode});
+ return $tract;
- } #unless ($res->code eq '200')
+ } else {
- die "FFIEC Geocoding error: $error\n" if $error;
+ my $error = $content->{d}->{sMsg}
+ || 'FFIEC lookup failed, but with no status message.';
+ die "$error\n";
- $return->{'statecode'} . $return->{'countycode'} . $return->{'tractcode'};
+ }
}
#sub get_district_methods {
diff --git a/FS/FS/Report/FCC_477.pm b/FS/FS/Report/FCC_477.pm
index ff29d1953..f5d6a06ec 100644
--- a/FS/FS/Report/FCC_477.pm
+++ b/FS/FS/Report/FCC_477.pm
@@ -4,14 +4,13 @@ use base qw( FS::Report );
use strict;
use vars qw( @upload @download @technology @part2aoption @part2boption
%states
- $DEBUG
);
use FS::Record qw( dbh );
use Tie::IxHash;
use Storable;
-$DEBUG = 0;
+our $DEBUG = 0;
=head1 NAME
@@ -305,6 +304,7 @@ sub report {
unless $class->can($method);
my $statement = $class->$method(%opt);
+ warn $statement if $DEBUG;
my $sth = dbh->prepare($statement);
$sth->execute or die $sth->errstr;
$sth->fetchall_arrayref;
diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm
index 396c86622..91dfc5d97 100644
--- a/FS/FS/Schema.pm
+++ b/FS/FS/Schema.pm
@@ -5653,6 +5653,7 @@ sub tables_hashref {
'max_simultaneous', 'int', 'NULL', '', '', '',
'e911_class', 'char', 'NULL', 1, '', '',
'e911_type', 'char', 'NULL', 1, '', '',
+ 'circuit_svcnum', 'int', 'NULL', '', '', '',
],
'primary_key' => 'svcnum',
'unique' => [ [ 'sms_carrierid', 'sms_account'] ],
@@ -5678,6 +5679,10 @@ sub tables_hashref {
table => 'cdr_carrier',
references => [ 'carrierid' ],
},
+ { columns => [ 'circuit_svcnum' ],
+ table => 'svc_circuit',
+ references => [ 'svcnum' ],
+ },
],
},
@@ -5881,6 +5886,7 @@ sub tables_hashref {
'disabled', 'char', 'NULL', 1, '', '',
'unsuspend_pkgpart', 'int', 'NULL', '', '', '',
'unsuspend_hold','char', 'NULL', 1, '', '',
+ 'unused_credit', 'char', 'NULL', 1, '', '',
],
'primary_key' => 'reasonnum',
'unique' => [],
@@ -6526,6 +6532,75 @@ sub tables_hashref {
],
},
+ 'circuit_type' => {
+ 'columns' => [
+ 'typenum', 'serial', '', '', '', '',
+ 'typename', 'varchar', '', $char_d, '', '',
+ 'disabled', 'char', 'NULL', 1, '', '',
+ # speed? number of voice lines? anything else?
+ ],
+ 'primary_key' => 'typenum',
+ 'unique' => [ [ 'typename' ] ],
+ 'index' => [],
+ },
+
+ 'circuit_provider' => {
+ 'columns' => [
+ 'providernum', 'serial', '', '', '', '',
+ 'provider', 'varchar', '', $char_d, '', '',
+ 'disabled', 'char', 'NULL', 1, '', '',
+ ],
+ 'primary_key' => 'providernum',
+ 'unique' => [ [ 'provider' ], ],
+ 'index' => [],
+ },
+
+ 'circuit_termination' => {
+ 'columns' => [
+ 'termnum', 'serial', '', '', '', '',
+ 'termination','varchar', '', $char_d, '', '',
+ 'disabled', 'char', 'NULL', 1, '', '',
+ ],
+ 'primary_key' => 'termnum',
+ 'unique' => [ [ 'termination' ] ],
+ 'index' => [],
+ },
+
+ 'svc_circuit' => {
+ 'columns' => [
+ 'svcnum', 'int', '', '', '', '',
+ 'typenum', 'int', '', '', '', '',
+ 'providernum', 'int', '', '', '', '',
+ 'termnum', 'int', '', '', '', '',
+ 'circuit_id', 'varchar', '', 64, '', '',
+ 'desired_due_date', 'int', 'NULL', '', '', '',
+ 'due_date', 'int', 'NULL', '', '', '',
+ 'vendor_order_id', 'varchar', 'NULL', $char_d, '', '',
+ 'vendor_qual_id', 'varchar', 'NULL', $char_d, '', '',
+ 'vendor_order_type', 'varchar', 'NULL', $char_d, '', '',
+ 'vendor_order_status', 'varchar', 'NULL', $char_d, '', '',
+ 'endpoint_ip_addr', 'varchar', 'NULL', 40, '', '',
+ 'endpoint_mac_addr', 'varchar', 'NULL', 12, '', '',
+ ],
+ 'primary_key' => 'svcnum',
+ 'unique' => [],
+ 'index' => [ [ 'providernum' ], [ 'typenum' ] ],
+ 'foreign_keys' => [
+ { columns => [ 'svcnum' ],
+ table => 'cust_svc',
+ },
+ { columns => [ 'typenum' ],
+ table => 'circuit_type',
+ },
+ { columns => [ 'providernum' ],
+ table => 'circuit_provider',
+ },
+ { columns => [ 'termnum' ],
+ table => 'circuit_termination',
+ },
+ ],
+ },
+
'vend_main' => {
'columns' => [
'vendnum', 'serial', '', '', '', '',
diff --git a/FS/FS/Template_Mixin.pm b/FS/FS/Template_Mixin.pm
index 5af5b270b..05972c0b7 100644
--- a/FS/FS/Template_Mixin.pm
+++ b/FS/FS/Template_Mixin.pm
@@ -2729,6 +2729,8 @@ sub _items_cust_bill_pkg {
'pkgnum' => $cust_bill_pkg->pkgpart, #so it displays in Ref
'description' => $description,
'amount' => sprintf("%.2f", $cust_bill_pkg->setup),
+ 'unit_amount' => sprintf("%.2f", $cust_bill_pkg->unitsetup),
+ 'quantity' => $cust_bill_pkg->quantity,
'preref_html' => ( $opt{preref_callback}
? &{ $opt{preref_callback} }( $cust_bill_pkg )
: ''
@@ -2740,6 +2742,12 @@ sub _items_cust_bill_pkg {
'pkgnum' => $cust_bill_pkg->pkgpart, #so it displays in Ref
'description' => "$desc (". $cust_bill_pkg->part_pkg->freq_pretty.")",
'amount' => sprintf("%.2f", $cust_bill_pkg->recur),
+ 'unit_amount' => sprintf("%.2f", $cust_bill_pkg->unitrecur),
+ 'quantity' => $cust_bill_pkg->quantity,
+ 'preref_html' => ( $opt{preref_callback}
+ ? &{ $opt{preref_callback} }( $cust_bill_pkg )
+ : ''
+ ),
};
}
diff --git a/FS/FS/UI/Web.pm b/FS/FS/UI/Web.pm
index 99c35609e..e13869265 100644
--- a/FS/FS/UI/Web.pm
+++ b/FS/FS/UI/Web.pm
@@ -113,16 +113,16 @@ sub svc_url {
if $DEBUG;
if ( $opt{m}->interp->comp_exists("/$opt{action}/$svcdb.cgi") ) {
$url = "$svcdb.cgi?";
+ } elsif ( $opt{m}->interp->comp_exists("/$opt{action}/$svcdb.html") ) {
+ $url = "$svcdb.html?";
} else {
-
my $generic = $opt{action} eq 'search' ? 'cust_svc' : 'svc_Common';
$url = "$generic.html?svcdb=$svcdb;";
$url .= 'svcnum=' if $query =~ /^\d+(;|$)/ or $query eq '';
}
- import FS::CGI 'rooturl'; #WTF! why is this necessary
- my $return = rooturl(). "$opt{action}/$url$query";
+ my $return = FS::CGI::rooturl(). "$opt{action}/$url$query";
$return = qq!<A HREF="$return">! if $opt{ahref};
@@ -574,6 +574,19 @@ sub cust_aligns {
}
}
+=item cust_links
+
+Returns an array of links to view/cust_main.cgi, for use with cust_fields.
+
+=cut
+
+sub cust_links {
+ my $link = [ FS::CGI::rooturl().'view/cust_main.cgi?', 'custnum' ];
+
+ return map { $_ eq 'cust_status_label' ? '' : $link }
+ @cust_fields;
+}
+
=item is_mobile
Utility function to determine if the client is a mobile browser.
diff --git a/FS/FS/addr_block.pm b/FS/FS/addr_block.pm
index 3e62a688b..ba0f61db1 100755
--- a/FS/FS/addr_block.pm
+++ b/FS/FS/addr_block.pm
@@ -388,6 +388,24 @@ sub label {
($router ? $router->routername : '(unallocated)'). ':'. $self->NetAddr;
}
+=item router
+
+Returns the router assigned to this block.
+
+=cut
+
+# necessary, because this can't be foreign keyed
+
+sub router {
+ my $self = shift;
+ my $routernum = $self->routernum;
+ if ( $routernum ) {
+ return FS::router->by_key($routernum);
+ } else {
+ return;
+ }
+}
+
=back
=head1 BUGS
diff --git a/FS/FS/cdr/cx3.pm b/FS/FS/cdr/cx3.pm
index e5b5f0350..8c848078a 100644
--- a/FS/FS/cdr/cx3.pm
+++ b/FS/FS/cdr/cx3.pm
@@ -43,6 +43,7 @@ sub { my ($cdr, $duration) = @_;
}, #duration
skip(1), # unknown
'disposition', # call status
+ 'accountcode', # AccountCode
],
);
diff --git a/FS/FS/cdr/earthlink.pm b/FS/FS/cdr/earthlink.pm
new file mode 100644
index 000000000..0421ef935
--- /dev/null
+++ b/FS/FS/cdr/earthlink.pm
@@ -0,0 +1,44 @@
+package FS::cdr::earthlink;
+
+use strict;
+use vars qw( @ISA %info $date);
+use Time::Local;
+use FS::cdr qw(_cdr_date_parser_maker _cdr_min_parser_maker);
+use Date::Parse;
+
+@ISA = qw(FS::cdr);
+
+%info = (
+ 'name' => 'Earthlink',
+ 'weight' => 120,
+ 'header' => 1,
+ 'import_fields' => [
+
+ 'accountcode', #Account number
+ skip(2), #SERVICE LOC / BILL NUMBER
+ sub { my($cdr, $date) = @_;
+
+ }, #date
+ sub { my($cdr, $time) = @_;
+
+ my $datetime = $date. " ". $time;
+ $cdr->set('startdate', $datetime );
+ }, #time
+ sub { my($cdr, $src) = @_;
+ $src =~ s/\D//g;
+ $cdr->set('src', $src);
+ }, #ORIG NUMBER
+ skip(2), #ORIG CITY/ORIGSTATE
+ sub { my($cdr, $dst) = @_;
+ $dst =~ s/\D//g;
+ $cdr->set('dst', $dst);
+ }, #TERM NUMBER
+ skip(2), #TERM CITY / TERM STATE
+ _cdr_min_parser_maker, #MINUTES
+ ],
+);
+
+sub skip { map {''} (1..$_[0]) }
+
+1;
+
diff --git a/FS/FS/cdr/thinktel.pm b/FS/FS/cdr/thinktel.pm
new file mode 100644
index 000000000..ddb2127a6
--- /dev/null
+++ b/FS/FS/cdr/thinktel.pm
@@ -0,0 +1,42 @@
+package FS::cdr::thinktel;
+
+use strict;
+use base qw( FS::cdr );
+use FS::cdr qw( _cdr_date_parser_maker _cdr_min_parser_maker );
+
+our %info = (
+ 'name' => 'Thinktel',
+ 'weight' => 541,
+ 'header' => 1, #0 default, set to 1 to ignore the first line, or
+ # to higher numbers to ignore that number of lines
+ 'type' => 'csv', #csv (default), fixedlength or xls
+ 'sep_char' => ',', #for csv, defaults to ,
+ 'disabled' => 0, #0 default, set to 1 to disable
+
+ #listref of what to do with each field from the CDR, in order
+ 'import_fields' => [
+ 'charged_party',
+ 'src',
+ 'dst',
+ _cdr_date_parser_maker('startdate'),
+ 'billsec', # rounded call duration
+ 'dcontext', # Usage Type: 'Local', 'Canada', 'Incoming', ...
+ 'upstream_price',
+ 'upstream_src_regionname',
+ 'upstream_dst_regionname',
+ '', # upstream rate per minute
+ '', # "Label"
+ # raw seconds, to one decimal place
+ sub { my ($cdr, $sec) = @_;
+ $cdr->set('duration', sprintf('%.0f', $sec));
+ },
+ # newly added fields of unclear meaning:
+ # Subscription (UUID, seems to correspond to charged_party)
+ # Call Type (always "Normal" thus far)
+ # Carrier (always empty)
+ # Alt Destination Name (always empty)
+ ],
+);
+
+1;
+
diff --git a/FS/FS/circuit_provider.pm b/FS/FS/circuit_provider.pm
new file mode 100644
index 000000000..6cb784117
--- /dev/null
+++ b/FS/FS/circuit_provider.pm
@@ -0,0 +1,101 @@
+package FS::circuit_provider;
+
+use strict;
+use base qw( FS::Record );
+use FS::Record qw( qsearch qsearchs );
+
+=head1 NAME
+
+FS::circuit_provider - Object methods for circuit_provider records
+
+=head1 SYNOPSIS
+
+ use FS::circuit_provider;
+
+ $record = new FS::circuit_provider \%hash;
+ $record = new FS::circuit_provider { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::circuit_provider object represents a telecom carrier that provides
+physical circuits (L<FS::svc_circuit>). FS::circuit_provider inherits from
+FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item providernum - primary key
+
+=item provider - provider name
+
+=item disabled - disabled
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new record. To add the record to the database, see L<"insert">.
+
+=cut
+
+sub table { 'circuit_provider'; }
+
+=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('providernum')
+ || $self->ut_text('provider')
+ || $self->ut_flag('disabled')
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=back
+
+=head1 SEE ALSO
+
+L<FS::Record>
+
+=cut
+
+1;
+
diff --git a/FS/FS/circuit_termination.pm b/FS/FS/circuit_termination.pm
new file mode 100644
index 000000000..3f0afc1f9
--- /dev/null
+++ b/FS/FS/circuit_termination.pm
@@ -0,0 +1,98 @@
+package FS::circuit_termination;
+
+use strict;
+use base qw( FS::Record );
+use FS::Record qw( qsearch qsearchs );
+
+=head1 NAME
+
+FS::circuit_termination - Object methods for circuit_termination records
+
+=head1 SYNOPSIS
+
+ use FS::circuit_termination;
+
+ $record = new FS::circuit_termination \%hash;
+ $record = new FS::circuit_termination { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::circuit_termination object represents a central office circuit
+interface type. FS::circuit_termination inherits from FS::Record. The
+following fields are currently supported:
+
+=over 4
+
+=item termnum - primary key
+
+=item termination - description of the termination type
+
+=item disabled - 'Y' if this is disabled
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new example. To add the example to the database, see L<"insert">.
+
+=cut
+
+sub table { 'circuit_termination'; }
+
+=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
+
+sub check {
+ my $self = shift;
+
+ my $error =
+ $self->ut_numbern('termnum')
+ || $self->ut_text('termination')
+ || $self->ut_flag('disabled')
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=back
+
+=head1 SEE ALSO
+
+L<FS::Record>
+
+=cut
+
+1;
+
diff --git a/FS/FS/circuit_type.pm b/FS/FS/circuit_type.pm
new file mode 100644
index 000000000..3b3653693
--- /dev/null
+++ b/FS/FS/circuit_type.pm
@@ -0,0 +1,98 @@
+package FS::circuit_type;
+
+use strict;
+use base qw( FS::Record );
+use FS::Record qw( qsearch qsearchs );
+
+=head1 NAME
+
+FS::circuit_type - Object methods for circuit_type records
+
+=head1 SYNOPSIS
+
+ use FS::circuit_type;
+
+ $record = new FS::circuit_type \%hash;
+ $record = new FS::circuit_type { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::circuit_type object represents a circuit type (such as "DS1" or "OC3").
+FS::circuit_type inherits from FS::Record. The following fields are currently
+supported:
+
+=over 4
+
+=item typenum - primary key
+
+=item typename - name of the circuit type
+
+=item disabled - 'Y' if this is disabled
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new example. To add the example to the database, see L<"insert">.
+
+=cut
+
+sub table { 'circuit_type'; }
+
+=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
+
+sub check {
+ my $self = shift;
+
+ my $error =
+ $self->ut_numbern('typenum')
+ || $self->ut_text('typename')
+ || $self->ut_flag('disabled')
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=back
+
+=head1 SEE ALSO
+
+L<FS::Record>
+
+=cut
+
+1;
+
diff --git a/FS/FS/cust_credit.pm b/FS/FS/cust_credit.pm
index 156ba5fd6..212be7a37 100644
--- a/FS/FS/cust_credit.pm
+++ b/FS/FS/cust_credit.pm
@@ -679,11 +679,9 @@ Example:
'apply' => 1, #0 leaves the credit unapplied
#the credit
- 'newreasonnum' => scalar($cgi->param('newreasonnum')),
- 'newreasonnum_type' => scalar($cgi->param('newreasonnumT')),
map { $_ => scalar($cgi->param($_)) }
#fields('cust_credit')
- qw( custnum _date amount reason reasonnum addlinfo ), #pkgnum eventnum
+ qw( custnum _date amount reasonnum addlinfo ), #pkgnum eventnum
);
@@ -725,26 +723,11 @@ sub credit_lineitems {
#});
my $error = '';
- if ($arg{reasonnum} == -1) {
-
- $error = 'Enter a new reason (or select an existing one)'
- unless $arg{newreasonnum} !~ /^\s*$/;
- my $reason = new FS::reason {
- 'reason' => $arg{newreasonnum},
- 'reason_type' => $arg{newreasonnum_type},
- };
- $error ||= $reason->insert;
- if ( $error ) {
- $dbh->rollback if $oldAutoCommit;
- return "Error inserting reason: $error";
- }
- $arg{reasonnum} = $reason->reasonnum;
- }
my $cust_credit = new FS::cust_credit ( {
map { $_ => $arg{$_} }
#fields('cust_credit')
- qw( custnum _date amount reason reasonnum addlinfo ), #pkgnum eventnum
+ qw( custnum _date amount reasonnum addlinfo ), #pkgnum eventnum
} );
$error = $cust_credit->insert;
if ( $error ) {
diff --git a/FS/FS/cust_pkg.pm b/FS/FS/cust_pkg.pm
index e8e202e3d..a810f5ab4 100644
--- a/FS/FS/cust_pkg.pm
+++ b/FS/FS/cust_pkg.pm
@@ -1207,7 +1207,7 @@ Available options are:
=over 4
-=item reason - can be set to a cancellation reason (see L<FS:reason>),
+=item reason - can be set to a cancellation reason (see L<FS:reason>),
either a reasonnum of an existing reason, or passing a hashref will create
a new reason. The hashref should have the following keys:
- typenum - Reason type (see L<FS::reason_type>
@@ -1297,6 +1297,16 @@ sub suspend {
}
}
+ # if a reasonnum was passed, get the actual reason object so we can check
+ # unused_credit
+ # (passing a reason hashref is still allowed, but it can't be used with
+ # the fancy behavioral options.)
+
+ my $reason;
+ if ($options{'reason'} =~ /^\d+$/) {
+ $reason = FS::reason->by_key($options{'reason'});
+ }
+
my %hash = $self->hash;
if ( $date ) {
$hash{'adjourn'} = $date;
@@ -1321,9 +1331,15 @@ sub suspend {
return $error;
}
- unless ( $date ) {
+ unless ( $date ) { # then we are suspending now
+
# credit remaining time if appropriate
- if ( $self->part_pkg->option('unused_credit_suspend', 1) ) {
+ # (if required by the package def, or the suspend reason)
+ my $unused_credit = $self->part_pkg->option('unused_credit_suspend',1)
+ || ( defined($reason) && $reason->unused_credit );
+
+ if ( $unused_credit ) {
+ warn "crediting unused time on pkg#".$self->pkgnum."\n" if $DEBUG;
my $error = $self->credit_remaining('suspend', $suspend_time);
if ($error) {
$dbh->rollback if $oldAutoCommit;
@@ -3872,7 +3888,7 @@ sub insert_reason {
$reasonnum = $reason->reasonnum;
} else {
- return "Unparsable reason: ". $options{'reason'};
+ return "Unparseable reason: ". $options{'reason'};
}
my $cust_pkg_reason =
diff --git a/FS/FS/part_event/Action/Mixin/credit_agent_pkg_class.pm b/FS/FS/part_event/Action/Mixin/credit_agent_pkg_class.pm
index cb61f1b77..488132a36 100644
--- a/FS/FS/part_event/Action/Mixin/credit_agent_pkg_class.pm
+++ b/FS/FS/part_event/Action/Mixin/credit_agent_pkg_class.pm
@@ -1,21 +1,16 @@
package FS::part_event::Action::Mixin::credit_agent_pkg_class;
-use base qw( FS::part_event::Action::Mixin::credit_pkg );
+
+# calculates a credit percentage on a specific package for use with
+# credit_pkg or credit_bill, based on an agent's commission table
use strict;
use FS::Record qw(qsearchs);
-sub option_fields {
- my $class = shift;
- my %option_fields = $class->SUPER::option_fields;
- delete $option_fields{'percent'};
- %option_fields;
-}
-
sub _calc_credit_percent {
- my( $self, $cust_pkg ) = @_;
+ my( $self, $cust_pkg, $agent ) = @_;
my $agent_pkg_class = qsearchs( 'agent_pkg_class', {
- 'agentnum' => $self->cust_main($cust_pkg)->agentnum,
+ 'agentnum' => $agent->agentnum,
'classnum' => $cust_pkg->part_pkg->classnum,
});
diff --git a/FS/FS/part_event/Action/Mixin/credit_bill.pm b/FS/FS/part_event/Action/Mixin/credit_bill.pm
new file mode 100644
index 000000000..4930e35fb
--- /dev/null
+++ b/FS/FS/part_event/Action/Mixin/credit_bill.pm
@@ -0,0 +1,95 @@
+package FS::part_event::Action::Mixin::credit_bill;
+
+use strict;
+
+# credit_bill: calculates a credit amount that is some percentage of each
+# line item of an invoice
+
+sub eventtable_hashref {
+ { 'cust_bill' => 1 };
+}
+
+sub option_fields {
+ my $class = shift;
+ my @fields = (
+ 'reasonnum' => { 'label' => 'Credit reason',
+ 'type' => 'select-reason',
+ 'reason_class' => 'R',
+ },
+ 'percent' => { 'label' => 'Percent',
+ 'type' => 'input-percentage',
+ 'default' => '100',
+ },
+ 'what' => {
+ 'label' => 'Of',
+ 'type' => 'select',
+ #add additional ways to specify in the package def
+ 'options' => [qw( setuprecur setup recur setuprecur_margin setup_margin recur_margin )],
+ 'labels' => {
+ 'setuprecur' => 'Amount charged',
+ 'setup' => 'Setup fee',
+ 'recur' => 'Recurring fee',
+ 'setuprecur_margin' => 'Amount charged minus total cost',
+ 'setup_margin' => 'Setup fee minus setup cost',
+ 'recur_margin' => 'Recurring fee minus recurring cost',
+ },
+ },
+ );
+ if ($class->can('_calc_credit_percent')) {
+ splice @fields, 2, 2; #remove the percentage option
+ }
+ @fields;
+
+}
+
+our %part_pkg_cache;
+
+# arguments:
+# 1. the line item
+# 2. the recipient of the commission; may be FS::sales, FS::agent,
+# FS::access_user, etc. Here we don't use it, but it will be passed through
+# to _calc_credit_percent.
+
+sub _calc_credit {
+ my $self = shift;
+ my $cust_bill_pkg = shift;
+
+ my $what = $self->option('what');
+ my $margin = 1 if $what =~ s/_margin$//;
+
+ my $pkgnum = $cust_bill_pkg->pkgnum;
+ my $cust_pkg = $cust_bill_pkg->cust_pkg;
+
+ my $percent;
+ if ( $self->can('_calc_credit_percent') ) {
+ $percent = $self->_calc_credit_percent($cust_pkg, @_);
+ } else {
+ $percent = $self->option('percent') || 0;
+ }
+
+ my $charge = 0;
+ if ( $what eq 'setup' ) {
+ $charge = $cust_bill_pkg->get('setup');
+ } elsif ( $what eq 'recur' ) {
+ $charge = $cust_bill_pkg->get('recur');
+ } elsif ( $what eq 'setuprecur' ) {
+ $charge = $cust_bill_pkg->get('setup') + $cust_bill_pkg->get('recur');
+ }
+ if ( $margin ) {
+ my $pkgpart = $cust_bill_pkg->pkgpart_override || $cust_pkg->pkgpart;
+ my $part_pkg = $part_pkg_cache{$pkgpart}
+ ||= FS::part_pkg->by_key($pkgpart);
+ if ( $what eq 'setup' ) {
+ $charge -= $part_pkg->get('setup_cost');
+ } elsif ( $what eq 'recur' ) {
+ $charge -= $part_pkg->get('recur_cost');
+ } elsif ( $what eq 'setuprecur' ) {
+ $charge -= $part_pkg->get('setup_cost') + $part_pkg->get('recur_cost');
+ }
+ }
+
+ $charge = 0 if $charge < 0; # e.g. prorate
+ return ($percent * $charge / 100);
+}
+
+1;
diff --git a/FS/FS/part_event/Action/Mixin/credit_flat.pm b/FS/FS/part_event/Action/Mixin/credit_flat.pm
new file mode 100644
index 000000000..374cf5d6b
--- /dev/null
+++ b/FS/FS/part_event/Action/Mixin/credit_flat.pm
@@ -0,0 +1,25 @@
+package FS::part_event::Action::Mixin::credit_flat;
+
+# credit_flat: return a fixed amount for _calc_credit, specified in the
+# options
+
+use strict;
+
+sub option_fields {
+ (
+ 'reasonnum' => { 'label' => 'Credit reason',
+ 'type' => 'select-reason',
+ 'reason_class' => 'R',
+ },
+ 'amount' => { 'label' => 'Credit amount',
+ 'type' => 'money',
+ },
+ );
+}
+
+sub _calc_credit {
+ my $self = shift;
+ $self->option('amount');
+}
+
+1;
diff --git a/FS/FS/part_event/Action/Mixin/credit_pkg.pm b/FS/FS/part_event/Action/Mixin/credit_pkg.pm
index e586f8532..400ece97b 100644
--- a/FS/FS/part_event/Action/Mixin/credit_pkg.pm
+++ b/FS/FS/part_event/Action/Mixin/credit_pkg.pm
@@ -2,12 +2,19 @@ package FS::part_event::Action::Mixin::credit_pkg;
use strict;
+# credit_pkg: calculates a credit amount that is some percentage of the
+# package charge / cost / margin / some other amount of a package
+#
+# also provides an option field for the percentage, unless the action knows
+# how to calculate its own percentage somehow (has a _calc_credit_percent)
+
sub eventtable_hashref {
{ 'cust_pkg' => 1 };
}
sub option_fields {
- (
+ my $class = shift;
+ my @fields = (
'reasonnum' => { 'label' => 'Credit reason',
'type' => 'select-reason',
'reason_class' => 'R',
@@ -36,12 +43,19 @@ sub option_fields {
},
},
);
+ if ($class->can('_calc_credit_percent')) {
+ splice @fields, 2, 2; #remove the percentage option
+ }
+ @fields;
}
-#my %no_cust_pkg = ( 'setup_cost' => 1 );
+# arguments:
+# 1. cust_pkg
+# 2. recipient of the credit (passed through to _calc_credit_percent)
sub _calc_credit {
- my( $self, $cust_pkg ) = @_;
+ my $self = shift;
+ my $cust_pkg = shift;
my $cust_main = $self->cust_main($cust_pkg);
@@ -59,18 +73,17 @@ sub _calc_credit {
}
}
- my $percent = $self->_calc_credit_percent($cust_pkg);
+ my $percent;
+ if ( $self->can('_calc_credit_percent') ) {
+ $percent = $self->_calc_credit_percent($cust_pkg, @_);
+ } else {
+ $percent = $self->option('percent') || 0;
+ }
- #my @arg = $no_cust_pkg{$what} ? () : ($cust_pkg);
my @arg = ($what eq 'setup_cost') ? () : ($cust_pkg);
sprintf('%.2f', $part_pkg->$what(@arg) * $percent / 100 );
}
-sub _calc_credit_percent {
- my( $self, $cust_pkg ) = @_;
- $self->option('percent');
-}
-
1;
diff --git a/FS/FS/part_event/Action/Mixin/credit_sales_pkg_class.pm b/FS/FS/part_event/Action/Mixin/credit_sales_pkg_class.pm
index 5c090ef54..61302aa27 100644
--- a/FS/FS/part_event/Action/Mixin/credit_sales_pkg_class.pm
+++ b/FS/FS/part_event/Action/Mixin/credit_sales_pkg_class.pm
@@ -1,30 +1,16 @@
package FS::part_event::Action::Mixin::credit_sales_pkg_class;
-use base qw( FS::part_event::Action::Mixin::credit_pkg );
use strict;
use FS::Record qw(qsearchs);
use FS::sales_pkg_class;
-sub option_fields {
- my $class = shift;
- my %option_fields = $class->SUPER::option_fields;
-
- delete $option_fields{'percent'};
-
- %option_fields;
-}
-
sub _calc_credit_percent {
- my( $self, $cust_pkg ) = @_;
-
- my $salesnum = $cust_pkg->salesnum;
- $salesnum ||= $self->cust_main($cust_pkg)->salesnum
- if $self->option('cust_main_sales');
+ my( $self, $cust_pkg, $sales ) = @_;
- return 0 unless $salesnum;
+ die "sales record required" unless $sales;
my $sales_pkg_class = qsearchs( 'sales_pkg_class', {
- 'salesnum' => $salesnum,
+ 'salesnum' => $sales->salesnum,
'classnum' => $cust_pkg->part_pkg->classnum,
});
diff --git a/FS/FS/part_event/Action/bill_sales_credit.pm b/FS/FS/part_event/Action/bill_sales_credit.pm
new file mode 100644
index 000000000..3193a81ef
--- /dev/null
+++ b/FS/FS/part_event/Action/bill_sales_credit.pm
@@ -0,0 +1,91 @@
+package FS::part_event::Action::bill_sales_credit;
+
+# in this order:
+# - pkg_sales_credit invokes NEXT, then appends the 'cust_main_sales' param
+# - credit_bill contains the core _calc_credit logic, and also defines other
+# params
+
+use base qw( FS::part_event::Action::Mixin::pkg_sales_credit
+ FS::part_event::Action::Mixin::credit_bill
+ FS::part_event::Action );
+use FS::Record qw(qsearch qsearchs);
+use FS::Conf;
+use Date::Format qw(time2str);
+
+use strict;
+
+sub description { 'Credit the sales person based on the billed amount'; }
+
+sub eventtable_hashref {
+ { 'cust_bill' => 1 };
+}
+
+our $date_format;
+
+sub do_action {
+ my( $self, $cust_bill, $cust_event ) = @_;
+
+ $date_format ||= FS::Conf->new->config('date_format') || '%x';
+
+ my $cust_main = $self->cust_main($cust_bill);
+
+ my %salesnum_sales; # salesnum => FS::sales object
+ my %salesnum_amount; # salesnum => credit amount
+ my %pkgnum_pkg; # pkgnum => FS::cust_pkg
+ my %salesnum_pkgnums; # salesnum => [ pkgnum, ... ]
+
+ my @items = qsearch('cust_bill_pkg', { invnum => $cust_bill->invnum,
+ pkgnum => { op => '>', value => '0' }
+ });
+
+ foreach my $cust_bill_pkg (@items) {
+ my $pkgnum = $cust_bill_pkg->pkgnum;
+ my $cust_pkg = $pkgnum_pkg{$pkgnum} ||= $cust_bill_pkg->cust_pkg;
+
+ my $salesnum = $cust_pkg->salesnum;
+ $salesnum ||= $cust_main->salesnum
+ if $self->option('cust_main_sales');
+ my $sales = $salesnum_sales{$salesnum}
+ ||= FS::sales->by_key($salesnum);
+
+ next if !$sales; #no sales person, no credit
+
+ my $amount = $self->_calc_credit($cust_bill_pkg, $sales);
+
+ if ($amount > 0) {
+ $salesnum_amount{$salesnum} ||= 0;
+ $salesnum_amount{$salesnum} += $amount;
+ push @{ $salesnum_pkgnums{$salesnum} ||= [] }, $pkgnum;
+ }
+ }
+
+ foreach my $salesnum (keys %salesnum_amount) {
+ my $amount = sprintf('%.2f', $salesnum_amount{$salesnum});
+ next if $amount < 0.005;
+
+ my $sales = $salesnum_sales{$salesnum};
+
+ my $sales_cust_main = $sales->sales_cust_main;
+ die "No customer record for sales person ". $sales->salesperson
+ unless $sales->sales_custnum;
+
+ my $reasonnum = $self->option('reasonnum');
+
+ my $desc = 'from invoice #'. $cust_bill->display_invnum .
+ ' ('. time2str($date_format, $cust_bill->_date) . ')';
+ # could also show custnum and pkgnums here?
+ my $error = $sales_cust_main->credit(
+ $amount,
+ \$reasonnum,
+ 'eventnum' => $cust_event->eventnum,
+ 'addlinfo' => $desc,
+ 'commission_salesnum' => $sales->salesnum,
+ );
+ die "Error crediting customer ". $sales_cust_main->custnum.
+ " for sales commission: $error"
+ if $error;
+ } # foreach $salesnum
+
+}
+
+1;
diff --git a/FS/FS/part_event/Action/bill_sales_credit_pkg_class.pm b/FS/FS/part_event/Action/bill_sales_credit_pkg_class.pm
new file mode 100644
index 000000000..91442b9e4
--- /dev/null
+++ b/FS/FS/part_event/Action/bill_sales_credit_pkg_class.pm
@@ -0,0 +1,11 @@
+package FS::part_event::Action::bill_sales_credit_pkg_class;
+
+use base qw( FS::part_event::Action::Mixin::pkg_sales_credit
+ FS::part_event::Action::Mixin::credit_bill
+ FS::part_event::Action::Mixin::credit_sales_pkg_class
+ FS::part_event::Action::bill_sales_credit
+ );
+
+sub description { "Credit the sales person based on their commission percentage for the package's class"; }
+
+1;
diff --git a/FS/FS/part_event/Action/pkg_agent_credit.pm b/FS/FS/part_event/Action/pkg_agent_credit.pm
index 494c40e3f..65f8c27d6 100644
--- a/FS/FS/part_event/Action/pkg_agent_credit.pm
+++ b/FS/FS/part_event/Action/pkg_agent_credit.pm
@@ -1,7 +1,8 @@
package FS::part_event::Action::pkg_agent_credit;
use strict;
-use base qw( FS::part_event::Action::pkg_referral_credit );
+use base qw( FS::part_event::Action::Mixin::credit_flat
+ FS::part_event::Action );
sub description { 'Credit the agent a specific amount'; }
@@ -18,7 +19,7 @@ sub do_action {
my $agent_cust_main = $agent->agent_cust_main;
#? or return "No customer record for agent ". $agent->agent;
- my $amount = $self->_calc_credit($cust_pkg);
+ my $amount = $self->_calc_credit($cust_pkg, $agent);
return '' unless $amount > 0;
my $reasonnum = $self->option('reasonnum');
diff --git a/FS/FS/part_event/Action/pkg_agent_credit_pkg_class.pm b/FS/FS/part_event/Action/pkg_agent_credit_pkg_class.pm
index 3dcf668f9..92c155627 100644
--- a/FS/FS/part_event/Action/pkg_agent_credit_pkg_class.pm
+++ b/FS/FS/part_event/Action/pkg_agent_credit_pkg_class.pm
@@ -1,7 +1,8 @@
package FS::part_event::Action::pkg_agent_credit_pkg_class;
use strict;
-use base qw( FS::part_event::Action::Mixin::credit_agent_pkg_class
+use base qw( FS::part_event::Action::Mixin::credit_pkg
+ FS::part_event::Action::Mixin::credit_agent_pkg_class
FS::part_event::Action::pkg_agent_credit );
sub description { 'Credit the agent an amount based on their commission percentage for the referred package class'; }
diff --git a/FS/FS/part_event/Action/pkg_employee_credit.pm b/FS/FS/part_event/Action/pkg_employee_credit.pm
index 64dd8b2c5..6cbe9bc4e 100644
--- a/FS/FS/part_event/Action/pkg_employee_credit.pm
+++ b/FS/FS/part_event/Action/pkg_employee_credit.pm
@@ -1,7 +1,8 @@
package FS::part_event::Action::pkg_employee_credit;
use strict;
-use base qw( FS::part_event::Action::pkg_referral_credit );
+use base qw( FS::part_event::Action::Mixin::credit_flat
+ FS::part_event::Action );
sub description { 'Credit the ordering employee a specific amount'; }
@@ -18,7 +19,7 @@ sub do_action {
my $employee_cust_main = $employee->user_cust_main;
#? or return "No customer record for employee ". $employee->username;
- my $amount = $self->_calc_credit($cust_pkg);
+ my $amount = $self->_calc_credit($cust_pkg, $employee);
return '' unless $amount > 0;
my $reasonnum = $self->option('reasonnum');
diff --git a/FS/FS/part_event/Action/pkg_referral_credit.pm b/FS/FS/part_event/Action/pkg_referral_credit.pm
index e7c92d650..9d7bbf8b3 100644
--- a/FS/FS/part_event/Action/pkg_referral_credit.pm
+++ b/FS/FS/part_event/Action/pkg_referral_credit.pm
@@ -1,7 +1,8 @@
package FS::part_event::Action::pkg_referral_credit;
use strict;
-use base qw( FS::part_event::Action );
+use base qw( FS::part_event::Action::Mixin::credit_flat
+ FS::part_event::Action );
sub description { 'Credit the referring customer a specific amount'; }
@@ -9,19 +10,6 @@ sub eventtable_hashref {
{ 'cust_pkg' => 1 };
}
-sub option_fields {
- (
- 'reasonnum' => { 'label' => 'Credit reason',
- 'type' => 'select-reason',
- 'reason_class' => 'R',
- },
- 'amount' => { 'label' => 'Credit amount',
- 'type' => 'money',
- },
- );
-
-}
-
sub do_action {
my( $self, $cust_pkg, $cust_event ) = @_;
@@ -35,7 +23,7 @@ sub do_action {
return 'Referring customer is cancelled'
if $referring_cust_main->status eq 'cancelled';
- my $amount = $self->_calc_credit($cust_pkg);
+ my $amount = $self->_calc_credit($cust_pkg, $referring_cust_main);
return '' unless $amount > 0;
my $reasonnum = $self->option('reasonnum');
@@ -53,10 +41,4 @@ sub do_action {
}
-sub _calc_credit {
- my( $self, $cust_pkg ) = @_;
-
- $self->option('amount');
-}
-
1;
diff --git a/FS/FS/part_event/Action/pkg_sales_credit.pm b/FS/FS/part_event/Action/pkg_sales_credit.pm
index e7551cda9..3c569cada 100644
--- a/FS/FS/part_event/Action/pkg_sales_credit.pm
+++ b/FS/FS/part_event/Action/pkg_sales_credit.pm
@@ -1,12 +1,15 @@
package FS::part_event::Action::pkg_sales_credit;
-use base qw( FS::part_event::Action::Mixin::pkg_sales_credit
- FS::part_event::Action::pkg_referral_credit );
+use base qw( FS::part_event::Action::Mixin::credit_flat
+ FS::part_event::Action );
use strict;
sub description { 'Credit the sales person a specific amount'; }
-#a little false laziness w/pkg_referral_credit
+sub eventtable_hashref {
+ { 'cust_pkg' => 1 };
+}
+
sub do_action {
my( $self, $cust_pkg, $cust_event ) = @_;
@@ -24,7 +27,7 @@ sub do_action {
my $sales_cust_main = $sales->sales_cust_main;
#? or return "No customer record for sales person ". $sales->salesperson;
- my $amount = $self->_calc_credit($cust_pkg);
+ my $amount = $self->_calc_credit($cust_pkg, $sales);
return '' unless $amount > 0;
my $reasonnum = $self->option('reasonnum');
diff --git a/FS/FS/part_event/Action/pkg_sales_credit_pkg.pm b/FS/FS/part_event/Action/pkg_sales_credit_pkg.pm
index 9b13cd872..bd165f1c8 100644
--- a/FS/FS/part_event/Action/pkg_sales_credit_pkg.pm
+++ b/FS/FS/part_event/Action/pkg_sales_credit_pkg.pm
@@ -1,4 +1,6 @@
package FS::part_event::Action::pkg_sales_credit_pkg;
+
+# yes, they must be in this order
use base qw( FS::part_event::Action::Mixin::pkg_sales_credit
FS::part_event::Action::Mixin::credit_pkg
FS::part_event::Action::pkg_sales_credit );
diff --git a/FS/FS/part_event/Action/pkg_sales_credit_pkg_class.pm b/FS/FS/part_event/Action/pkg_sales_credit_pkg_class.pm
index c69c004ba..53ffc6cff 100644
--- a/FS/FS/part_event/Action/pkg_sales_credit_pkg_class.pm
+++ b/FS/FS/part_event/Action/pkg_sales_credit_pkg_class.pm
@@ -1,8 +1,10 @@
package FS::part_event::Action::pkg_sales_credit_pkg_class;
use base qw( FS::part_event::Action::Mixin::pkg_sales_credit
+ FS::part_event::Action::Mixin::credit_pkg
FS::part_event::Action::Mixin::credit_sales_pkg_class
- FS::part_event::Action::pkg_sales_credit );
+ FS::part_event::Action::pkg_sales_credit
+ );
sub description { "Credit the package sales person an amount based on their commission percentage for the package's class"; }
diff --git a/FS/FS/part_export/send_email.pm b/FS/FS/part_export/send_email.pm
index 41f04093e..3e5142260 100644
--- a/FS/FS/part_export/send_email.pm
+++ b/FS/FS/part_export/send_email.pm
@@ -20,7 +20,8 @@ my %template_select = (
%templates = (0 => '',
map { $_->msgnum, $_->msgname }
qsearch({ table => 'msg_template',
- hashref => { disabled => 1 },
+ hashref => { disabled => { 'op' => '!=',
+ 'value' => 1 }},
order_by => 'ORDER BY msgnum ASC'
})
);
diff --git a/FS/FS/pay_batch/RBC.pm b/FS/FS/pay_batch/RBC.pm
index 4b11fdb89..a5c468367 100644
--- a/FS/FS/pay_batch/RBC.pm
+++ b/FS/FS/pay_batch/RBC.pm
@@ -108,7 +108,7 @@ $name = 'RBC';
sprintf("%3s",$trans_code).
sprintf("%10s",$client_num).
' '.
- sprintf("%-19s", $cust_pay_batch->paybatchnum).
+ sprintf("%-19s", $cust_pay_batch->cust_main->custnum).
'00'.
sprintf("%04s", $bankno).
sprintf("%05s", $branch).
diff --git a/FS/FS/quotation_pkg.pm b/FS/FS/quotation_pkg.pm
index 79cce80fa..33c761ef6 100644
--- a/FS/FS/quotation_pkg.pm
+++ b/FS/FS/quotation_pkg.pm
@@ -164,6 +164,26 @@ sub recur {
sprintf('%.2f', $recur);
}
+sub unitsetup {
+ my $self = shift;
+ return '0.00' if $self->waive_setup eq 'Y' || $self->{'_NO_SETUP_KLUDGE'};
+ my $part_pkg = $self->part_pkg;
+ my $setup = $part_pkg->option('setup_fee');
+
+ #XXX discounts
+ sprintf('%.2f', $setup);
+}
+
+sub unitrecur {
+ my $self = shift;
+ return '0.00' if $self->{'_NO_RECUR_KLUDGE'};
+ my $part_pkg = $self->part_pkg;
+ my $recur = $part_pkg->can('base_recur') ? $part_pkg->base_recur
+ : $part_pkg->option('recur_fee');
+ #XXX discounts
+ sprintf('%.2f', $recur);
+}
+
=item part_pkg_currency_option OPTIONNAME
Returns a two item list consisting of the currency of this quotation's customer
diff --git a/FS/FS/reason.pm b/FS/FS/reason.pm
index e6b20db8f..f28989a9b 100644
--- a/FS/FS/reason.pm
+++ b/FS/FS/reason.pm
@@ -56,6 +56,10 @@ suspensions but not others.
whether to bill the unsuspend package immediately ('') or to wait until
the customer's next invoice ('Y').
+=item unused_credit - 'Y' or ''. For suspension reasons only (for now).
+If enabled, the customer will be credited for their remaining time on
+suspension.
+
=back
=head1 METHODS
@@ -109,7 +113,6 @@ sub check {
|| $self->ut_number('reason_type')
|| $self->ut_foreign_key('reason_type', 'reason_type', 'typenum')
|| $self->ut_text('reason')
- || $self->ut_flag('disabled')
;
return $error if $error;
@@ -117,11 +120,13 @@ sub check {
$error = $self->ut_numbern('unsuspend_pkgpart')
|| $self->ut_foreign_keyn('unsuspend_pkgpart', 'part_pkg', 'pkgpart')
|| $self->ut_flag('unsuspend_hold')
+ || $self->ut_flag('unused_credit')
;
return $error if $error;
} else {
- $self->set('unsuspend_pkgpart' => '');
- $self->set('unsuspend_hold' => '');
+ foreach (qw(unsuspend_pkgpart unsuspend_hold unused_credit)) {
+ $self->set($_ => '');
+ }
}
$self->SUPER::check;
@@ -178,8 +183,6 @@ sub new_or_existing {
=head1 BUGS
-Here by termintes. Don't use on wooden computers.
-
=head1 SEE ALSO
L<FS::Record>, schema.html from the base documentation.
diff --git a/FS/FS/router.pm b/FS/FS/router.pm
index 4011bb097..c0c93dd32 100755
--- a/FS/FS/router.pm
+++ b/FS/FS/router.pm
@@ -200,6 +200,13 @@ sub delete {
Returns a list of FS::addr_block objects (address blocks) associated
with this object.
+=cut
+
+sub addr_block {
+ my $self = shift;
+ qsearch('addr_block', { routernum => $self->routernum });
+}
+
=item auto_addr_block
Returns a list of address blocks on which auto-assignment of IP addresses
diff --git a/FS/FS/svc_circuit.pm b/FS/FS/svc_circuit.pm
new file mode 100644
index 000000000..f705c68f4
--- /dev/null
+++ b/FS/FS/svc_circuit.pm
@@ -0,0 +1,230 @@
+package FS::svc_circuit;
+
+use strict;
+use base qw(
+ FS::svc_IP_Mixin
+ FS::MAC_Mixin
+ FS::svc_Common
+);
+use FS::Record qw( qsearch qsearchs );
+use FS::circuit_provider;
+use FS::circuit_type;
+use FS::circuit_termination;
+
+=head1 NAME
+
+FS::svc_circuit - Object methods for svc_circuit records
+
+=head1 SYNOPSIS
+
+ use FS::svc_circuit;
+
+ $record = new FS::svc_circuit \%hash;
+ $record = new FS::svc_circuit { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::svc_circuit object represents a telecom circuit service (other than
+an analog phone line, which is svc_phone, or a DSL Internet connection,
+which is svc_dsl). FS::svc_circuit inherits from FS::svc_IP_Mixin,
+FS::MAC_Mixin, and FS::svc_Common. The following fields are currently
+supported:
+
+=over 4
+
+=item svcnum - primary key; see also L<FS::cust_svc>
+
+=item typenum - circuit type (such as DS1, DS1-PRI, DS3, OC3, etc.); foreign
+key to L<FS::circuit_type>.
+
+=item providernum - circuit provider (telco); foreign key to
+L<FS::circuit_provider>.
+
+=item termnum - circuit termination type; foreign key to
+L<FS::circuit_termination>
+
+=item circuit_id - circuit ID string defined by the provider
+
+=item desired_due_date - the requested date for completion of the circuit
+order
+
+=item due_date - the provider's committed date for completion of the circuit
+order
+
+=item vendor_order_id - the provider's order number
+
+=item vendor_qual_id - the qualification number, if a qualification was
+performed
+
+=item vendor_order_type -
+
+=item vendor_order_status - the order status: ACCEPTED, PENDING, COMPLETED,
+etc.
+
+=item endpoint_ip_addr - the IP address of the endpoint equipment, if any.
+This will be validated as an IP address but not assigned from managed address
+space or checked for uniqueness.
+
+=item endpoint_mac_addr - the MAC address of the endpoint.
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new circuit service. To add the record to the database, see
+L<"insert">.
+
+=cut
+
+sub table { 'svc_circuit'; }
+
+sub table_info {
+ my %dis = ( disable_default => 1, disable_fixed => 1,
+ disabled_inventory => 1, disable_select => 1 );
+
+ tie my %fields, 'Tie::IxHash', (
+ 'svcnum' => 'Service',
+ 'providernum' => {
+ label => 'Provider',
+ type => 'select',
+ select_table => 'circuit_provider',
+ select_key => 'providernum',
+ select_label => 'provider',
+ disable_inventory => 1,
+ },
+ 'typenum' => {
+ label => 'Circuit type',
+ type => 'select',
+ select_table => 'circuit_type',
+ select_key => 'typenum',
+ select_label => 'typename',
+ disable_inventory => 1,
+ },
+ 'termnum' => {
+ label => 'Termination type',
+ type => 'select',
+ select_table => 'circuit_termination',
+ select_key => 'termnum',
+ select_label => 'termination',
+ disable_inventory => 1,
+ },
+ 'circuit_id' => { label => 'Circuit ID', %dis },
+ 'desired_due_date' => { label => 'Desired due date',
+ %dis
+ },
+ 'due_date' => { label => 'Due date',
+ %dis
+ },
+ 'vendor_order_id' => { label => 'Vendor order ID', %dis },
+ 'vendor_qual_id' => { label => 'Vendor qualification ID', %dis },
+ 'vendor_order_type' => {
+ label => 'Vendor order type',
+ disable_inventory => 1
+ }, # should be a select?
+ 'vendor_order_status' => {
+ label => 'Vendor order status',
+ disable_inventory => 1
+ }, # should also be a select?
+ 'endpoint_ip_addr' => {
+ label => 'Endpoint IP address',
+ },
+ 'endpoint_mac_addr' => {
+ label => 'Endpoint MAC address',
+ type => 'input-mac_addr',
+ disable_inventory => 1,
+ },
+ );
+ return {
+ 'name' => 'Circuit',
+ 'name_plural' => 'Circuits',
+ 'longname_plural' => 'Voice and data circuit services',
+ 'display_weight' => 72,
+ 'cancel_weight' => 85, # after svc_phone
+ 'fields' => \%fields,
+ };
+}
+
+=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 service. If there is
+an error, returns the error, otherwise returns false. Called by the insert
+and replace methods.
+
+=cut
+
+sub check {
+ my $self = shift;
+
+ my $mac_addr = uc($self->get('endpoint_mac_addr'));
+ $mac_addr =~ s/[\W_]//g;
+ $self->set('endpoint_mac_addr', $mac_addr);
+
+ my $error =
+ $self->ut_numbern('svcnum')
+ || $self->ut_number('typenum')
+ || $self->ut_number('providernum')
+ || $self->ut_text('circuit_id')
+ || $self->ut_numbern('desired_due_date')
+ || $self->ut_numbern('due_date')
+ || $self->ut_textn('vendor_order_id')
+ || $self->ut_textn('vendor_qual_id')
+ || $self->ut_textn('vendor_order_type')
+ || $self->ut_textn('vendor_order_status')
+ || $self->ut_ipn('endpoint_ip_addr')
+ || $self->ut_textn('endpoint_mac_addr')
+ ;
+
+ # no canonical values yet for vendor_order_status or _type
+
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=item label
+
+Returns the circuit ID.
+
+=cut
+
+sub label {
+ my $self = shift;
+ $self->get('circuit_id');
+}
+
+=back
+
+=head1 SEE ALSO
+
+L<FS::Record>
+
+=cut
+
+1;
+
diff --git a/FS/FS/svc_phone.pm b/FS/FS/svc_phone.pm
index 4ca8d82fa..bd35cbac4 100644
--- a/FS/FS/svc_phone.pm
+++ b/FS/FS/svc_phone.pm
@@ -189,6 +189,14 @@ sub table_info {
select_label => 'domain',
disable_inventory => 1,
},
+ 'circuit_svcnum' => { label => 'Circuit',
+ type => 'select',
+ select_table => 'svc_domain',
+ select_key => 'svcnum',
+ select_label => 'circuit_label',
+ disable_inventory => 1,
+ },
+
'sms_carrierid' => { label => 'SMS Carrier',
type => 'select',
select_table => 'cdr_carrier',
@@ -711,6 +719,8 @@ sub radius_groups {
=item sms_cdr_carrier
+Returns the L<FS::cdr_carrier> assigned as the SMS carrier for this phone.
+
=cut
sub sms_cdr_carrier {
@@ -721,6 +731,8 @@ sub sms_cdr_carrier {
=item sms_carriername
+Returns the name of the SMS carrier, or an empty string if there isn't one.
+
=cut
sub sms_carriername {
@@ -729,6 +741,29 @@ sub sms_carriername {
$cdr_carrier->carriername;
}
+=item svc_circuit
+
+Returns the L<FS::svc_circuit> assigned as the trunk for this phone line.
+
+=item circuit_label
+
+Returns the label of the circuit (the part_svc label followed by the
+circuit ID), or an empty string if there isn't one.
+
+=cut
+
+sub svc_circuit {
+ my $self = shift;
+ my $svcnum = $self->get('circuit_svcnum') or return '';
+ return FS::svc_circuit->by_key($svcnum);
+}
+
+sub circuit_label {
+ my $self = shift;
+ my $svc_circuit = $self->svc_circuit or return '';
+ return join(' ', $svc_circuit->part_svc->svc, $svc_circuit->circuit_id);
+}
+
=item phone_device
Returns any FS::phone_device records associated with this service.
diff --git a/FS/MANIFEST b/FS/MANIFEST
index 79a7dc523..105447c6b 100644
--- a/FS/MANIFEST
+++ b/FS/MANIFEST
@@ -821,3 +821,11 @@ FS/deploy_zone_vertex.pm
t/deploy_zone_vertex.t
FS/tax_status.pm
t/tax_status.t
+FS/circuit_type.pm
+t/circuit_type.t
+FS/circuit_provider.pm
+t/circuit_provider.t
+FS/circuit_termination.pm
+t/circuit_termination.t
+FS/svc_circuit.pm
+t/svc_circuit.t
diff --git a/FS/t/circuit_provider.t b/FS/t/circuit_provider.t
new file mode 100644
index 000000000..753a156d5
--- /dev/null
+++ b/FS/t/circuit_provider.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::circuit_provider;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/circuit_termination.t b/FS/t/circuit_termination.t
new file mode 100644
index 000000000..6f5127195
--- /dev/null
+++ b/FS/t/circuit_termination.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::circuit_termination;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/circuit_type.t b/FS/t/circuit_type.t
new file mode 100644
index 000000000..dbb6e0ac5
--- /dev/null
+++ b/FS/t/circuit_type.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::circuit_type;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/svc_circuit.t b/FS/t/svc_circuit.t
new file mode 100644
index 000000000..7fefcc04b
--- /dev/null
+++ b/FS/t/svc_circuit.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::svc_circuit;
+$loaded=1;
+print "ok 1\n";
diff --git a/bin/cdr-thinktel.import b/bin/cdr-thinktel.import
new file mode 100755
index 000000000..9afd34cc5
--- /dev/null
+++ b/bin/cdr-thinktel.import
@@ -0,0 +1,127 @@
+#!/usr/bin/perl
+
+use strict;
+use Getopt::Std;
+use Date::Format;
+use File::Temp 'tempdir';
+use Net::FTP;
+use FS::UID qw(adminsuidsetup datasrc dbh);
+use FS::cdr;
+use FS::cdr_batch;
+use FS::Record qw(qsearch qsearchs);
+use Date::Format 'time2str';
+use Date::Parse 'str2time';
+
+
+###
+# parse command line
+###
+
+use vars qw( $opt_d $opt_v $opt_c $opt_s $opt_e $opt_a );
+getopts('dvc:s:e:a');
+
+my ($user, $login, $password) = @ARGV;
+($user and $login and $password) or die &usage;
+
+my $dbh = adminsuidsetup $user;
+$FS::UID::AutoCommit = 0;
+
+# index already-downloaded batches
+my @previous = qsearch({
+ 'table' => 'cdr_batch',
+ 'hashref' => { 'cdrbatch' => {op=>'like', value=>'thinktel%'} },
+ 'order_by' => 'ORDER BY cdrbatch DESC',
+});
+my %exists = map {$_->cdrbatch => 1} @previous;
+
+my $tempdir = tempdir( CLEANUP => !$opt_v );
+
+my $format = 'thinktel';
+my $hostname = 'ucontrol.thinktel.ca';
+
+my $ftp = Net::FTP->new($hostname, Debug => $opt_d)
+ or die "Can't connect to $hostname: $@\n";
+
+$ftp->login($login, $password)
+ or die "Login failed: ".$ftp->message."\n";
+
+###
+# get the file list
+###
+
+warn "Retrieving directory listing\n" if $opt_v;
+
+$ftp->cwd('/');
+my @files = grep { $_ =~ /MetaSwitch/ } $ftp->ls();
+
+warn scalar(@files)." CDR files found.\n" if $opt_v;
+# apply date range
+if ( $opt_a ) {
+ my $most_recent = $previous[0];
+ if ($most_recent) {
+ if ($most_recent->cdrbatch =~ /^thinktel-(\d+)/) {
+ my $date = $1;
+ warn "limiting to dates > $date (from most recent batch)\n" if $opt_v;
+ @files = grep { /^(\d+)_/ && $1 > $date } @files;
+ }
+ } # else download them all
+}
+if ( $opt_s ) {
+ # start date
+ # normalize date format
+ $opt_s = time2str('%Y%m%d', str2time($opt_s)) if $opt_s =~ /\D/;
+ warn "limiting to dates > $opt_s\n" if $opt_v;
+ @files= grep { /^(\d+)_/ && $1 >= $opt_s } @files;
+}
+if ( $opt_e ) {
+ # end date
+ $opt_e = time2str('%Y%m%d', str2time($opt_e)) if $opt_e =~ /\D/;
+ warn "limiting to dates < $opt_e\n" if $opt_v;
+ @files= grep { /^(\d+)_/ && $1 < $opt_e } @files;
+}
+warn scalar(@files) ." files to be downloaded to '$tempdir'\n" if $opt_v;
+foreach my $file (@files) {
+
+ warn "downloading $file\n" if $opt_v;
+ $ftp->get($file, "$tempdir/$file");
+ warn "processing $file\n" if $opt_v;
+
+ my $batchname = "$format-$file";
+ if ($exists{$batchname}) {
+ warn "already imported $file\n";
+ next;
+ }
+ my $import_options = {
+ 'file' => "$tempdir/$file",
+ 'format' => $format,
+ 'batch_namevalue' => $batchname,
+ 'empty_ok' => 1,
+ };
+ $import_options->{'cdrtypenum'} = $opt_c if $opt_c;
+
+ my $error = FS::cdr::batch_import($import_options);
+
+ if ( $error ) {
+ die "error processing $file: $error\n";
+ }
+}
+warn "finished\n" if $opt_v;
+$dbh->commit;
+
+###
+# subs
+###
+
+sub usage {
+ "Usage: \n cdr-thinktel.import [ options ] user login password
+ Options:
+ -v: be verbose
+ -d: enable FTP debugging (very noisy)
+ -c num: apply a cdrtypenum to the imported CDRs
+ -s date: start date
+ -e date: end date
+ -a: automatically choose start date from most recently downloaded batch
+
+";
+}
+
diff --git a/debian/rules b/debian/rules
index 033d89264..07d391466 100755
--- a/debian/rules
+++ b/debian/rules
@@ -195,6 +195,7 @@ install-stamp: build-stamp
perl -p -i -e "\
s'${TMP}?''g;\
" ${TMP}-lib/usr/bin/* \
+ ${TMP}-webui/usr/local/etc/freeside/handler.pl
#RT Config
diff --git a/fs_selfservice/FS-SelfService/cgi/customer_order_pkg.html b/fs_selfservice/FS-SelfService/cgi/customer_order_pkg.html
index 5fcf77fac..f16de7761 100755
--- a/fs_selfservice/FS-SelfService/cgi/customer_order_pkg.html
+++ b/fs_selfservice/FS-SelfService/cgi/customer_order_pkg.html
@@ -1,5 +1,11 @@
<%= include('header', 'Purchase additional package') %>
-<%= include('order_pkg') %>
+<%= if (grep $_ eq 'Purchase additional package', @menu_disable){
+ $OUT .= "This functionality has been disabled";
+} else {
+
+include('order_pkg')
+
+} %>
<%= include('footer') %>
diff --git a/fs_selfservice/FS-SelfService/cgi/do_process_forgot_password.html b/fs_selfservice/FS-SelfService/cgi/do_process_forgot_password.html
index 35728e7ec..5a0104393 100644
--- a/fs_selfservice/FS-SelfService/cgi/do_process_forgot_password.html
+++ b/fs_selfservice/FS-SelfService/cgi/do_process_forgot_password.html
@@ -11,7 +11,7 @@
<%= if (!$error) {
$selfurl =~ s/\?.*//;
- $OUT .= "Your password has been changed. You can now <A HREF=\"$selfurl\">log in</A>.";
+ $OUT .= "Your password has been changed. You can now <A HREF=\"$selfurl?agentnum=$agentnum\">log in</A>.";
}
%>
diff --git a/httemplate/browse/circuit_provider.html b/httemplate/browse/circuit_provider.html
new file mode 100644
index 000000000..12f653251
--- /dev/null
+++ b/httemplate/browse/circuit_provider.html
@@ -0,0 +1,11 @@
+<& elements/browse-simple.html,
+ 'table' => 'circuit_provider',
+ 'title' => 'Circuit providers',
+ 'menubar' => [ 'Circuit types' => 'circuit_type.html',
+ 'Circuit terminations' => 'circuit_termination.html'
+ ],
+ 'name_singular' => 'provider',
+ 'name_header' => 'Provider name',
+ 'name_col' => 'provider',
+ 'acl' => 'Configuration',
+&>
diff --git a/httemplate/browse/circuit_termination.html b/httemplate/browse/circuit_termination.html
new file mode 100644
index 000000000..830ccf7fb
--- /dev/null
+++ b/httemplate/browse/circuit_termination.html
@@ -0,0 +1,11 @@
+<& elements/browse-simple.html,
+ 'table' => 'circuit_termination',
+ 'title' => 'Circuit terminations',
+ 'menubar' => [ 'Circuit types' => 'circuit_type.html',
+ 'Circuit providers' => 'circuit_provider.html'
+ ],
+ 'name_singular' => 'termination type',
+ 'name_header' => 'Termination type',
+ 'name_col' => 'termination',
+ 'acl' => 'Configuration',
+&>
diff --git a/httemplate/browse/circuit_type.html b/httemplate/browse/circuit_type.html
new file mode 100644
index 000000000..a145d54d9
--- /dev/null
+++ b/httemplate/browse/circuit_type.html
@@ -0,0 +1,11 @@
+<& elements/browse-simple.html,
+ 'table' => 'circuit_type',
+ 'title' => 'Circuit types',
+ 'menubar' => [ 'Circuit providers' => 'circuit_provider.html',
+ 'Circuit terminations' => 'circuit_termination.html'
+ ],
+ 'name_singular' => 'circuit type',
+ 'name_header' => 'Circuit type',
+ 'name_col' => 'typename',
+ 'acl' => 'Configuration',
+&>
diff --git a/httemplate/browse/elements/browse-simple.html b/httemplate/browse/elements/browse-simple.html
new file mode 100644
index 000000000..cfa27e882
--- /dev/null
+++ b/httemplate/browse/elements/browse-simple.html
@@ -0,0 +1,57 @@
+<& browse.html,
+ 'query' => { 'table' => $table },
+ 'count_query' => "SELECT COUNT(*) FROM $table",
+ 'header' => [ '#', $opt{name_header} ],
+ 'fields' => [ $table_key, $opt{name_col} ],
+ 'links' => [ '', '' ],
+ 'link_onclicks' => [ '', $sub_edit_popup ],
+ 'disableable' => 1,
+ 'disabled_statuspos' => 2,
+ 'html_init' => $html_init,
+ %opt,
+&>
+<%doc>
+A simple wrapper around search/elements/search.html for browsing/editing
+tables that only have a primary key, a 'disabled' field, and one other column
+which is the object's name or description. Usage:
+
+<& browse-simple.html,
+ # required
+ 'table' => 'mytable',
+ 'title' => 'My Things',
+ 'name_singular' => 'thing',
+ 'name_col' => 'thingname',
+ 'name_header' => 'Thing name'
+ 'acl' => 'Configure things',
+&>
+
+</%doc>
+<%init>
+my %opt = @_;
+
+my $table = delete $opt{table};
+my $name_singular = $opt{name_singular};
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right($opt{acl});
+
+my $table_key = dbdef->table($table)->primary_key;
+my $sub_edit_popup = sub {
+ my $pkey = $_[0]->get($table_key);
+ include('/elements/popup_link_onclick.html',
+ 'action' => $p."edit/$table.html?$pkey",
+ 'actionlabel' => "Edit $name_singular",
+ 'width' => 350,
+ 'height' => 220,
+ );
+};
+
+my $html_init = include('/elements/popup_link.html',
+ 'action' => $p."edit/$table.html?",
+ 'actionlabel' => "Add $name_singular",
+ 'width' => 350,
+ 'height' => 220,
+ 'label' => "Add a new $name_singular",
+) . '<BR>';
+
+</%init>
diff --git a/httemplate/browse/part_pkg-fcc.html b/httemplate/browse/part_pkg-fcc.html
index e3fd97ccf..bdfb99a59 100755
--- a/httemplate/browse/part_pkg-fcc.html
+++ b/httemplate/browse/part_pkg-fcc.html
@@ -180,9 +180,11 @@ if ( $cgi->param('redirect') ) {
my $html_init =
include('/elements/init_overlib.html') .
include('/elements/input-fcc_options.html', js_only => 1) .
- include('.style');
+ include('.style') .
+ include('.script');
-my $html_form = qq!<FORM ACTION="${p}edit/process/bulk-part_pkg-fcc.html" METHOD="POST">
+my $html_form = qq!<FORM ACTION="${p}edit/process/bulk-part_pkg-fcc.html" METHOD="POST" NAME="bulk-part_pkg-fcc">
+ <INPUT TYPE="hidden" NAME="jump">
( show class: !.
include('/elements/select-pkg_class.html',
#'curr_value' => $classnum,
@@ -193,20 +195,13 @@ my $html_form = qq!<FORM ACTION="${p}edit/process/bulk-part_pkg-fcc.html" METHOD
'disable_empty' => 1,
).
' )
- <BR><BR>' .
- qq!<SCRIPT TYPE="text/javascript">
- function filter_change() {
- window.location = '! . $cgi->self_url . qq!?classnum='
- + document.getElementById('classnum').value;
- }
- </SCRIPT>!;
+ <BR><BR>';
+
# restore this only after creating $html_form
$cgi->param('classnum', $classnum) if length($classnum);
-my $html_foot = qq!
- <INPUT TYPE="submit" VALUE="Save changes">
- </FORM>!;
+my $html_foot = '</FORM>';
my @menubar =
( 'Package definitions' => $p.'browse/part_pkg.cgi' );
@@ -224,3 +219,17 @@ my @menubar =
}
</style>
</%def>
+<%def .script>
+<script type="text/javascript">
+ function finish_edit_fcc(id) {
+ cClick();
+ document.forms['bulk-part_pkg-fcc']['jump'].value = id;
+ document.forms['bulk-part_pkg-fcc'].submit(); //immediately save/refresh
+ }
+
+ function filter_change() {
+ window.location = '! . $cgi->self_url . qq!?classnum='
+ + document.getElementById('classnum').value;
+ }
+</script>
+</%def>
diff --git a/httemplate/docs/part_svc-table.html b/httemplate/docs/part_svc-table.html
index 8d3711d23..5e8d9e5d1 100644
--- a/httemplate/docs/part_svc-table.html
+++ b/httemplate/docs/part_svc-table.html
@@ -23,6 +23,7 @@
<LI><B>svc_broadband</B>: Wireless broadband
<LI><B>svc_cable</B>: Cable
<LI><B>svc_dish</B>: DISH Network
+ <LI><B>svc_circuit</B>: Phone circuits other than DSL
</UL>
</TD>
<TD VALIGN="top">
diff --git a/httemplate/edit/circuit_provider.html b/httemplate/edit/circuit_provider.html
new file mode 100644
index 000000000..6c8dcedac
--- /dev/null
+++ b/httemplate/edit/circuit_provider.html
@@ -0,0 +1,21 @@
+<& elements/edit.html,
+ 'popup' => 1,
+ 'table' => 'circuit_provider',
+ 'name_singular' => 'provider',
+ 'labels' => \%labels,
+ 'fields' => \@fields,
+&>
+<%init>
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my @fields = (
+ 'provider',
+ { field => 'disabled', type => 'checkbox', value => 'Y' }
+);
+my %labels = (
+ 'providernum' => '',
+ 'provider' => 'Provider name',
+ 'disabled' => 'Disabled'
+);
+</%init>
diff --git a/httemplate/edit/circuit_termination.html b/httemplate/edit/circuit_termination.html
new file mode 100644
index 000000000..0317bced5
--- /dev/null
+++ b/httemplate/edit/circuit_termination.html
@@ -0,0 +1,21 @@
+<& elements/edit.html,
+ 'popup' => 1,
+ 'table' => 'circuit_termination',
+ 'name_singular' => 'termination type',
+ 'labels' => \%labels,
+ 'fields' => \@fields,
+&>
+<%init>
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my @fields = (
+ 'termination',
+ { field => 'disabled', type => 'checkbox', value => 'Y' }
+);
+my %labels = (
+ 'termnum' => '',
+ 'termination' => 'Termination type',
+ 'disabled' => 'Disabled'
+);
+</%init>
diff --git a/httemplate/edit/circuit_type.html b/httemplate/edit/circuit_type.html
new file mode 100644
index 000000000..897758897
--- /dev/null
+++ b/httemplate/edit/circuit_type.html
@@ -0,0 +1,21 @@
+<& elements/edit.html,
+ 'popup' => 1,
+ 'table' => 'circuit_type',
+ 'name_singular' => 'circuit type',
+ 'labels' => \%labels,
+ 'fields' => \@fields,
+&>
+<%init>
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my @fields = (
+ 'typename',
+ { field => 'disabled', type => 'checkbox', value => 'Y' }
+);
+my %labels = (
+ 'typenum' => '',
+ 'typename' => 'Circuit type',
+ 'disabled' => 'Disabled'
+);
+</%init>
diff --git a/httemplate/edit/credit-cust_bill_pkg.html b/httemplate/edit/credit-cust_bill_pkg.html
index 40faddc46..85f794317 100644
--- a/httemplate/edit/credit-cust_bill_pkg.html
+++ b/httemplate/edit/credit-cust_bill_pkg.html
@@ -80,9 +80,9 @@
<& /elements/tr-select-reason.html,
'field' => 'reasonnum',
'reason_class' => 'R',
- #XXX reconcile both this and show_taxes wanteding to enable this
+ #XXX reconcile both this and show_taxes wanting to enable this
'id' => 'select_reason',
- 'control_button' => "document.getElementById('credit_button')",
+ 'control_button' => 'credit_button',
'cgi' => $cgi,
&>
diff --git a/httemplate/edit/cust_credit.cgi b/httemplate/edit/cust_credit.cgi
index a3565f125..29801efef 100755
--- a/httemplate/edit/cust_credit.cgi
+++ b/httemplate/edit/cust_credit.cgi
@@ -24,7 +24,7 @@
<& /elements/tr-select-reason.html,
'field' => 'reasonnum',
'reason_class' => 'R',
- 'control_button' => "document.getElementById('confirm_credit_button')",
+ 'control_button' => 'confirm_credit_button',
'cgi' => $cgi,
&>
diff --git a/httemplate/edit/cust_main/bottomfixup.js b/httemplate/edit/cust_main/bottomfixup.js
index 5a8835f53..6a9deb92a 100644
--- a/httemplate/edit/cust_main/bottomfixup.js
+++ b/httemplate/edit/cust_main/bottomfixup.js
@@ -100,14 +100,13 @@ function copyelement(from, to) {
//alert(from + " (" + from.type + "): " + to.name + " => " + to.value);
}
-% # the value in pre+'censustract' is the confirmed censustract; if it's set,
-% # and the user hasn't changed it manually, skip this
+% # the value in pre+'censustract' is the confirmed censustract (either from
+% # the previous saved record, or from address standardization (if the backend
+% # supports it), or from an aborted previous submit. only need to reconfirm
+% # if it's empty.
function confirm_censustract(pre) {
var cf = document.CustomerForm;
- if ( cf.elements[pre+'censustract'].value == '' ||
- cf.elements[pre+'enter_censustract'].value !=
- cf.elements[pre+'censustract'].value )
- {
+ if ( cf.elements[pre+'censustract'].value == '' ) {
var address_info = form_address_info();
address_info[pre+'latitude'] = cf.elements[pre+'latitude'].value;
address_info[pre+'longitude'] = cf.elements[pre+'longitude'].value;
@@ -116,10 +115,13 @@ function confirm_censustract(pre) {
'<%$p%>/misc/confirm-censustract.html',
'q=' + encodeURIComponent(JSON.stringify(address_info)),
function() {
- overlib( OLresponseAJAX, CAPTION, 'Confirm censustract', STICKY,
- AUTOSTATUSCAP, CLOSETEXT, '', MIDX, 0, MIDY, 0, DRAGGABLE, WIDTH,
- 576, HEIGHT, 268, BGCOLOR, '#333399', CGCOLOR, '#333399',
- TEXTSIZE, 3 );
+ if ( OLresponseAJAX ) {
+ overlib( OLresponseAJAX, CAPTION, 'Confirm censustract', STICKY,
+ AUTOSTATUSCAP, CLOSETEXT, '', MIDX, 0, MIDY, 0, DRAGGABLE, WIDTH,
+ 576, HEIGHT, 268, BGCOLOR, '#333399', CGCOLOR, '#333399',
+ TEXTSIZE, 3 );
+ } else
+ submit_continue();
},
0);
} else submit_continue();
diff --git a/httemplate/edit/elements/part_svc_column.html b/httemplate/edit/elements/part_svc_column.html
index 64901a80f..6dcb602fe 100644
--- a/httemplate/edit/elements/part_svc_column.html
+++ b/httemplate/edit/elements/part_svc_column.html
@@ -249,7 +249,10 @@ that field.
</TR>
% }
% # special case: services with attached routers (false laziness...)
-% if ( $svcdb eq 'svc_acct' or $svcdb eq 'svc_broadband' or $svcdb eq 'svc_dsl' ) {
+% if ( $svcdb eq 'svc_acct'
+% or $svcdb eq 'svc_broadband'
+% or $svcdb eq 'svc_dsl'
+% or $svcdb eq 'svc_circuit' ) {
% push @fields, 'has_router';
<TR>
<TD COLSPAN=3 ALIGN="right">
diff --git a/httemplate/edit/elements/svc_Common.html b/httemplate/edit/elements/svc_Common.html
index fc29327ae..97b630f76 100644
--- a/httemplate/edit/elements/svc_Common.html
+++ b/httemplate/edit/elements/svc_Common.html
@@ -103,10 +103,42 @@
my $flag = $columndef->columnflag;
if ( $flag eq 'F' ) { #fixed
- $f->{'type'} = length($columndef->columnvalue)
- ? 'fixed'
- : 'hidden';
$f->{'value'} = $columndef->columnvalue;
+ if (length($columndef->columnvalue)) {
+
+ if ( $f->{'type'} =~ /^select-?(.*)/ ) {
+ # try to display this in a user-friendly manner
+ if ( $f->{'table'} ) { # find matching records
+ $f->{'value_col'} ||=
+ dbdef->table($f->{'table'})->primary_key;
+
+ my @values = split(',', $f->{'value'});
+ my @recs;
+ foreach (@values) {
+ push @recs, qsearchs( $f->{'table'},
+ { $f->{'value_col'} => $_ }
+ );
+ }
+ if ( @recs ) {
+ my $method = $f->{'name_col'};
+ if ( $f->{'multiple'} ) {
+ $f->{'formatted_value'} = [
+ map { $_->method } @recs
+ ];
+ } else { # there shouldn't be more than one...
+ $f->{'formatted_value'} = $recs[0]->$method;
+ }
+ } # if not, then just let tr-fixed display the
+ # values as-is
+
+ } # other select types probably don't matter
+ } # if it's a select
+
+ $f->{'type'} = 'fixed';
+
+ } else { # fixed, null
+ $f->{'type'} = 'hidden';
+ }
} elsif ( $flag eq 'A' ) { #auto assign from inventory
$f->{'type'} = 'hidden';
@@ -127,16 +159,14 @@
};
} elsif ( $flag eq 'S' #selectable choice
- && $f->{type} !~ /^select-svc(-domain|_pbx)$/ ) {
+ && $f->{type} !~ /^select-svc/ ) {
$f->{type} = 'select';
$f->{options} = [ split( /\s*,\s*/,
$columndef->columnvalue)
];
- }
+ } # shouldn't this be enforced for all 'S' fields?
- if ( $f->{'type'} eq 'select-svc_pbx'
- || $f->{'type'} eq 'select-svc-domain'
- )
+ if ( $f->{'type'} =~ /^select-svc/ )
{
$f->{'include_opt_callback'} =
sub { ( 'pkgnum' => $pkgnum,
diff --git a/httemplate/edit/part_pkg.cgi b/httemplate/edit/part_pkg.cgi
index af69f71f9..2ae9df3ec 100755
--- a/httemplate/edit/part_pkg.cgi
+++ b/httemplate/edit/part_pkg.cgi
@@ -895,6 +895,11 @@ my $javascript = <<'END';
}
}
+ function finish_edit_fcc(id) {
+ cClick();
+ show_fcc_options(id); // refresh the display
+ }
+
END
my $warning =
diff --git a/httemplate/edit/process/bulk-part_pkg-fcc.html b/httemplate/edit/process/bulk-part_pkg-fcc.html
index 4a0fb2a22..8ef330829 100644
--- a/httemplate/edit/process/bulk-part_pkg-fcc.html
+++ b/httemplate/edit/process/bulk-part_pkg-fcc.html
@@ -17,7 +17,7 @@
% }
<% $cgi->redirect($fsurl.'browse/part_pkg-fcc.html?redirect='.$session) %>
% } else {
-<% $cgi->redirect($fsurl.'browse/part_pkg-fcc.html?classnum='.$classnum) %>
+<% $cgi->redirect($fsurl.'browse/part_pkg-fcc.html?classnum='.$classnum.$jump) %>
% }
<%init>
my $curuser = $FS::CurrentUser::CurrentUser;
@@ -40,4 +40,8 @@ foreach my $param ($cgi->param) {
my $classnum = $cgi->param('classnum');
+my $jump = '';
+if ( $cgi->param('jump') =~ /^pkgpart(\d+)$/ ) {
+ $jump = '#'.$1;
+}
</%init>
diff --git a/httemplate/edit/process/circuit_provider.html b/httemplate/edit/process/circuit_provider.html
new file mode 100644
index 000000000..0a91a178d
--- /dev/null
+++ b/httemplate/edit/process/circuit_provider.html
@@ -0,0 +1,11 @@
+<& elements/process.html,
+ 'table' => 'circuit_provider',
+ 'viewall_dir' => 'browse',
+ 'popup_reload' => 'Updating',
+&>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+</%init>
diff --git a/httemplate/edit/process/circuit_termination.html b/httemplate/edit/process/circuit_termination.html
new file mode 100644
index 000000000..94d29c05c
--- /dev/null
+++ b/httemplate/edit/process/circuit_termination.html
@@ -0,0 +1,11 @@
+<& elements/process.html,
+ 'table' => 'circuit_termination',
+ 'viewall_dir' => 'browse',
+ 'popup_reload' => 'Updating',
+&>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+</%init>
diff --git a/httemplate/edit/process/circuit_type.html b/httemplate/edit/process/circuit_type.html
new file mode 100644
index 000000000..58f461e2c
--- /dev/null
+++ b/httemplate/edit/process/circuit_type.html
@@ -0,0 +1,11 @@
+<& elements/process.html,
+ 'table' => 'circuit_type',
+ 'viewall_dir' => 'browse',
+ 'popup_reload' => 'Updating',
+&>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+</%init>
diff --git a/httemplate/edit/process/credit-cust_bill_pkg.html b/httemplate/edit/process/credit-cust_bill_pkg.html
index 8e66368d4..75900bde5 100644
--- a/httemplate/edit/process/credit-cust_bill_pkg.html
+++ b/httemplate/edit/process/credit-cust_bill_pkg.html
@@ -27,19 +27,35 @@ foreach my $billpkgnum_setuprecur (@billpkgnum_setuprecurs) {
push @amounts, $amount;
}
-my $error = FS::cust_credit->credit_lineitems(
+my $reasonnum = $cgi->param('reasonnum');
+$reasonnum =~ /^(-?\d+)$/ or die "Illegal reasonnum";
+$reasonnum = $1;
+
+my $error;
+if ($reasonnum == -1) {
+ my $new_reason = FS::reason->new({
+ map { $_ => scalar( $cgi->param("select_reason_new_$_") ) }
+ qw( reason_type reason )
+ });
+ $error = $new_reason->insert;
+ $reasonnum = $new_reason->reasonnum;
+}
+
+if ( !$reasonnum ) {
+ $error ||= 'Reason required'
+}
+
+$error ||= FS::cust_credit->credit_lineitems(
#the lineitems to credit
'billpkgnums' => \@billpkgnums,
'setuprecurs' => \@setuprecurs,
'amounts' => \@amounts,
'apply' => ( $cgi->param('apply') eq 'yes' ),
+ 'reasonnum' => $reasonnum,
- #the credit
- 'newreasonnum' => scalar($cgi->param('newreasonnum')),
- 'newreasonnum_type' => scalar($cgi->param('newreasonnumT')),
map { $_ => scalar($cgi->param($_)) }
#fields('cust_credit')
- qw( custnum _date amount reason reasonnum addlinfo ), #pkgnum eventnum
+ qw( custnum _date amount addlinfo ), #pkgnum eventnum
);
</%init>
diff --git a/httemplate/edit/process/cust_credit.cgi b/httemplate/edit/process/cust_credit.cgi
index 245f31af7..e442d7fa6 100755
--- a/httemplate/edit/process/cust_credit.cgi
+++ b/httemplate/edit/process/cust_credit.cgi
@@ -1,5 +1,4 @@
%if ( $error ) {
-% $cgi->param('reasonnum', $reasonnum);
% $cgi->param('error', $error);
% $dbh->rollback if $oldAutoCommit;
%
@@ -37,19 +36,11 @@ my $oldAutoCommit = $FS::UID::AutoCommit;
local $FS::UID::AutoCommit = 0;
my $dbh = dbh;
-my $error = '';
-if ($reasonnum == -1) {
-
- $error = 'Enter a new reason (or select an existing one)'
- unless $cgi->param('newreasonnum') !~ /^\s*$/;
- my $reason = new FS::reason {
- 'reason_type' => scalar($cgi->param('newreasonnumT')),
- 'reason' => scalar($cgi->param('newreasonnum')),
- };
- $error ||= $reason->insert;
- $cgi->param('reasonnum', $reason->reasonnum)
- unless $error;
+my ($reasonnum, $error) = $m->comp('/misc/process/elements/reason');
+if (!$reasonnum) {
+ $error ||= 'Reason required'
}
+$cgi->param('reasonnum', $reasonnum) unless $error;
unless ($error) {
my $new = new FS::cust_credit ( {
diff --git a/httemplate/edit/process/elements/svc_Common.html b/httemplate/edit/process/elements/svc_Common.html
index 55ecc5f79..ca336a126 100644
--- a/httemplate/edit/process/elements/svc_Common.html
+++ b/httemplate/edit/process/elements/svc_Common.html
@@ -29,7 +29,7 @@ my $args_callback = sub {
map { $_ => $cgi->param("router_$_") }
qw( routernum routername blocknum )
});
- if (length($router->routername) == 0) {
+ if ($router->blocknum and length($router->routername) == 0) {
#sensible default
$router->set('routername', $svc->label);
}
diff --git a/httemplate/edit/process/part_event.html b/httemplate/edit/process/part_event.html
index 481439d53..bac69241c 100644
--- a/httemplate/edit/process/part_event.html
+++ b/httemplate/edit/process/part_event.html
@@ -62,12 +62,17 @@
my $value = join(',', $cgi->param( "$prefix$option" ) );
if ( $option eq 'reasonnum' && $value == -1 ) {
- $value = {
- 'typenum' => scalar( $cgi->param( "new$prefix${option}T" ) ),
- 'reason' => scalar( $cgi->param( "new$prefix${option}" ) ),
- };
+ my $reason_prefix = $object->action . '_' . $option . '_new_';
+ my $new_reason = FS::reason->new;
+ foreach ( qw( reason_type reason unsuspend pkgpart
+ unsuspend_hold unused_credit ) ) {
+ $new_reason->set($_, $cgi->param("$reason_prefix$_"));
+ }
+ warn Dumper $new_reason;
+ my $error = $new_reason->insert;
+ die "error creating reason: $error" if $error;
+ $value = $new_reason->reasonnum;
}
-
( $option => $value );
}
@{ $object->option_fields_listref };
diff --git a/httemplate/edit/process/svc_circuit.html b/httemplate/edit/process/svc_circuit.html
new file mode 100644
index 000000000..d28f91329
--- /dev/null
+++ b/httemplate/edit/process/svc_circuit.html
@@ -0,0 +1,11 @@
+<& elements/svc_Common.html,
+ table => 'svc_circuit',
+ edit_ext => 'html',
+ redirect => popurl(3)."view/svc_circuit.html?",
+&>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Provision customer service'); #something else more specific?
+
+</%init>
diff --git a/httemplate/edit/svc_circuit.cgi b/httemplate/edit/svc_circuit.cgi
new file mode 100644
index 000000000..3f9bad5b1
--- /dev/null
+++ b/httemplate/edit/svc_circuit.cgi
@@ -0,0 +1,54 @@
+<& elements/svc_Common.html,
+ 'table' => 'svc_circuit',
+ '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 $date_format = $conf->config('date_format') || '%m/%d/%Y';
+
+my @fields = (
+ { field => 'providernum',
+ type => 'select-table',
+ table => 'circuit_provider',
+ name_col => 'provider',
+ disable_empty => 1,
+ },
+ { field => 'typenum',
+ type => 'select-table',
+ table => 'circuit_type',
+ name_col => 'typename',
+ disable_empty => 1,
+ },
+ { field => 'termnum',
+ type => 'select-table',
+ table => 'circuit_termination',
+ name_col => 'termination',
+ disable_empty => 1,
+ },
+ { field => 'circuit_id',
+ size => 40,
+ },
+ { field => 'desired_due_date',
+ type => 'input-date-field',
+ },
+ { field => 'due_date',
+ type => 'input-date-field',
+ },
+ 'vendor_order_id',
+ 'vendor_qual_id',
+ 'vendor_order_status',
+ 'endpoint_ip_addr',
+ { field => 'endpoint_mac_addr',
+ type => 'input-mac_addr',
+ },
+);
+
+# needed: a new_callback to migrate vendor quals over to circuits
+
+#my ($svc_new_callback, $svc_edit_callback, $svc_error_callback);
+
+</%init>
diff --git a/httemplate/edit/svc_phone.cgi b/httemplate/edit/svc_phone.cgi
index f8582057e..f9c0d4005 100644
--- a/httemplate/edit/svc_phone.cgi
+++ b/httemplate/edit/svc_phone.cgi
@@ -2,17 +2,12 @@
'table' => 'svc_phone',
'fields' => [],
'begin_callback' => $begin_callback,
- 'svc_new_callback' => sub {
- my( $cgi, $svc_x, $part_svc, $cust_pkg, $fields, $opt ) = @_;
- $svc_x->locationnum($cust_pkg->locationnum) if $cust_pkg;
- },
- 'svc_edit_callback' => sub {
- my( $cgi, $svc_x, $part_svc, $cust_pkg, $fields, $opt) = @_;
- my $conf = new FS::Conf;
- $svc_x->sip_password('*HIDDEN*') unless $conf->exists('showpasswords');
- },
+ 'svc_new_callback' => $svc_callback,
+ 'svc_edit_callback' => $svc_callback,
+ 'svc_error_callback' => $svc_callback,
&>
<%init>
+my $conf = new FS::Conf;
my $begin_callback = sub {
my( $cgi, $fields, $opt ) = @_;
@@ -25,8 +20,6 @@ my $begin_callback = sub {
die "access denied"
unless $FS::CurrentUser::CurrentUser->access_right($right);
- my $conf = new FS::Conf;
-
push @$fields,
'countrycode',
{ field => 'phonenum',
@@ -149,7 +142,26 @@ my $begin_callback = sub {
}
-};
+}; # begin_callback
+# svc_edit_callback / svc_new_callback
+my $svc_callback = sub {
+ my ($cgi, $svc_x, $part_svc, $cust_pkg, $fields, $opt) = @_;
+ push @$fields, {
+ field => 'circuit_svcnum',
+ type => 'select-svc_circuit',
+ cust_pkg => $cust_pkg,
+ part_svc => $part_svc,
+ };
+
+ if ( $cust_pkg and not $svc_x->svcnum ) {
+ # new service, default to package location
+ $svc_x->set('locationnum', $cust_pkg->locationnum);
+ }
+
+ if ( not $conf->exists('showpasswords') and $svc_x->svcnum ) {
+ $svc_x->sip_password('*HIDDEN*');
+ }
+};
</%init>
diff --git a/httemplate/elements/input-fcc_options.html b/httemplate/elements/input-fcc_options.html
index fb2500fa5..064c647fc 100644
--- a/httemplate/elements/input-fcc_options.html
+++ b/httemplate/elements/input-fcc_options.html
@@ -80,7 +80,8 @@ function show_fcc_options(id) {
}
} // is_phone
if ( curr_values['is_voip'] ) {
- out += '<li><strong>VoIP</strong> telephone service</li>';
+ out += '<li><strong>VoIP</strong> telephone service over <strong>'
+ + media + '</strong></li>';
out += '<li><strong>' + curr_values['voip_sessions'] +
'</strong> sessions allowed</li>';
if ( curr_values['voip_lastmile'] ) {
diff --git a/httemplate/elements/location.html b/httemplate/elements/location.html
index 5cdc424a7..214a7d5f2 100644
--- a/httemplate/elements/location.html
+++ b/httemplate/elements/location.html
@@ -59,12 +59,7 @@ Example:
</TR>
% } else {
-
- <INPUT TYPE = "hidden"
- NAME = "<%$pre%>locationname"
- ID = "<%$pre%>locationname"
- VALUE = "<% $object->get('locationname') |h %>"
- >
+ <& hidden.html, field => $pre.'locationname', value => $object->get('locationname') &>
% }
@@ -102,10 +97,7 @@ Example:
% } else { # alternate format
- <INPUT TYPE = "hidden"
- NAME = "<%$pre%>address2"
- VALUE = "<% $object->get('address2') |h %>"
- >
+<& hidden.html, field => $pre.'address2', value => $object->get('address2') &>
<TR>
<<%$th%> ALIGN="right">Unit&nbsp;type&nbsp;and&nbsp;#</<%$th%>>
@@ -227,14 +219,14 @@ Example:
</TR>
% } else {
% foreach (qw(latitude longitude)) {
-<INPUT TYPE="hidden" NAME="<% $_ %>" ID="<% $_ %>" VALUE="<% $object->get($_) |h%>">
+<& hidden.html, field => $pre.$_, value => $object->get($_) &>
% }
% }
-<INPUT TYPE="hidden" NAME="<%$pre%>coord_auto" VALUE="<% $object->coord_auto %>">
-
-<INPUT TYPE="hidden" NAME="<%$pre%>geocode" VALUE="<% $object->geocode %>">
-<INPUT TYPE="hidden" NAME="<%$pre%>censustract" VALUE="<% $object->censustract %>">
-<INPUT TYPE="hidden" NAME="<%$pre%>censusyear" VALUE="<% $object->censusyear %>">
+%
+% foreach (qw(coord_auto geocode censustract censusyear)) {
+ <& hidden.html, field => $pre.$_, value => $object->get($_) &>
+% }
+%
% if ( $opt{enable_censustract} ) {
<TR>
<TD ALIGN="right">Census&nbsp;tract</TD>
@@ -259,7 +251,7 @@ Example:
</TD>
</TR>
% } else {
- <INPUT TYPE="hidden" ID="<%$pre%>" NAME="<%$pre%>district" VALUE="<% $object->district %>">
+ <& hidden.html, field => $pre.'district', value => $object->get('district') &>
% }
%# For address standardization:
@@ -267,11 +259,11 @@ Example:
%# to re-standardize
% foreach (qw(address1 city state country zip latitude
% longitude censustract district addr_clean) ) {
-<INPUT TYPE="hidden" NAME="old_<%$pre.$_%>" ID="old_<%$pre.$_%>" VALUE="<% $object->get($_) |h%>">
+<& hidden.html, field => 'old_'.$pre.$_, value => $object->get($_) &>
% }
%# Placeholders
-<INPUT TYPE="hidden" NAME="<%$pre%>cachenum" VALUE="">
-<INPUT TYPE="hidden" NAME="<%$pre%>addr_clean" VALUE="">
+<& hidden.html, field => $pre.'cachenum', value => '' &>
+<& hidden.html, field => $pre.'addr_clean', value => '' &>
<SCRIPT TYPE="text/javascript">
<&| /elements/onload.js &>
@@ -306,6 +298,26 @@ Example:
el.attachEvent('onchange', clear_coords);
}
}
+ function clear_censustract() {
+ // if the user manually edits the census tract, clear the 'hard' census
+ // tract field so that we can re-verify and present a confirmation popup
+
+ // get the ID of the hidden censustract field
+ var censustract_id = this.id.replace('enter_', '');
+ var el = document.getElementById(censustract_id);
+ if (el) {
+ el.value = '';
+ }
+ }
+ var el = document.getElementById('<%$pre%>enter_censustract');
+ if (el) {
+ if ( el.addEventListener ) {
+ el.addEventListener('change', clear_censustract);
+ } else if ( el.attachEvent ) {
+ el.attachEvent('onchange', clear_censustract);
+ }
+ }
+
</&>
</SCRIPT>
diff --git a/httemplate/elements/menu.html b/httemplate/elements/menu.html
index 3236bc14b..03ce20185 100644
--- a/httemplate/elements/menu.html
+++ b/httemplate/elements/menu.html
@@ -564,6 +564,12 @@ tie my %config_conferencing, 'Tie::IxHash',
'Quality levels' => [ $fsurl.'browse/conferencing_quality.html', '' ],
;
+tie my %config_circuit, 'Tie::IxHash',
+ 'Circuit types' => [ $fsurl.'browse/circuit_type.html', '' ],
+ 'Circuit providers' => [ $fsurl.'browse/circuit_provider.html', '' ],
+ 'Termination types' => [ $fsurl.'browse/circuit_termination.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' ];
@@ -584,6 +590,8 @@ $config_export_svc{'Conferencing'} = [ \%config_conferencing, '' ]
if $curuser->access_right('Configuration');
$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{'Hardware types'} = [ $fsurl.'browse/hardware_class.html', 'Set up hardware type catalog' ]
if $curuser->access_right('Configuration');
diff --git a/httemplate/elements/standardize_locations.js b/httemplate/elements/standardize_locations.js
index 817a2e357..ff7183b26 100644
--- a/httemplate/elements/standardize_locations.js
+++ b/httemplate/elements/standardize_locations.js
@@ -279,10 +279,7 @@ function setselect(el, value) {
function confirm_censustract() {
% if ( FS::Conf->new->exists('cust_main-require_censustract') ) {
var form = document.<% $formname %>;
- // this is the existing/confirmed censustract, not the manually entered one
- if ( form.elements['censustract'].value == '' ||
- form.elements['censustract'].value !=
- form.elements['enter_censustract'].value ) {
+ if ( form.elements['censustract'].value == '' ) {
var address_info = form_address_info();
address_info['latitude'] = form.elements['latitude'].value;
address_info['longitude'] = form.elements['longitude'].value;
@@ -290,10 +287,15 @@ function confirm_censustract() {
'<%$p%>/misc/confirm-censustract.html',
'q=' + encodeURIComponent(JSON.stringify(address_info)),
function() {
- overlib( OLresponseAJAX, CAPTION, 'Confirm censustract', STICKY,
- AUTOSTATUSCAP, CLOSETEXT, '', MIDX, 0, MIDY, 0, DRAGGABLE, WIDTH,
- 576, HEIGHT, 268, BGCOLOR, '#333399', CGCOLOR, '#333399',
- TEXTSIZE, 3 );
+ if ( OLresponseAJAX ) {
+ overlib( OLresponseAJAX, CAPTION, 'Confirm censustract', STICKY,
+ AUTOSTATUSCAP, CLOSETEXT, '', MIDX, 0, MIDY, 0, DRAGGABLE, WIDTH,
+ 576, HEIGHT, 268, BGCOLOR, '#333399', CGCOLOR, '#333399',
+ TEXTSIZE, 3 );
+ } else {
+ // no response
+ <% $post_censustract %>;
+ }
},
0);
} else {
diff --git a/httemplate/elements/tr-censustract.html b/httemplate/elements/tr-censustract.html
index bd014f11b..7a4b34943 100644
--- a/httemplate/elements/tr-censustract.html
+++ b/httemplate/elements/tr-censustract.html
@@ -1,4 +1,4 @@
-% if ($censustract) {
+% if ($location->country eq 'US' and $censustract) {
<TR>
<TD ALIGN="right"><% mt('Census tract') |h %></TD>
<TD COLSPAN=5>
diff --git a/httemplate/elements/tr-input-fcc_options.html b/httemplate/elements/tr-input-fcc_options.html
index 87117ef1e..1f63588ad 100644
--- a/httemplate/elements/tr-input-fcc_options.html
+++ b/httemplate/elements/tr-input-fcc_options.html
@@ -7,96 +7,9 @@
</STYLE>
<TR>
<TH COLSPAN=2>
- <& hidden.html, 'id' => $id, @_ &>
-%# <& input-text.html, 'id' => $id, @_ &> # XXX debugging
- <UL ID="<%$id%>_display_fcc_options" CLASS="fcc_options">
- </UL>
- <BUTTON TYPE="button" onclick="edit_fcc_options()">
- Edit
- </BUTTON>
-% # show some kind of useful summary of the FCC options here
+ <& input-fcc_options.html, 'id' => $id, @_ &>
</TH>
</TR>
-<SCRIPT TYPE="text/javascript">
-function edit_fcc_options() {
- <& popup_link_onclick.html,
- 'action' => $fsurl.'misc/part_pkg_fcc_options.html?id=' . $id,
- 'actionlabel' => 'FCC Form 477 options',
- 'width' => 760,
- 'height' => 600,
- &>
-}
-var technology_labels = <% encode_json(FS::part_pkg_fcc_option->technology_labels) %>;
-function show_fcc_options() {
- var curr_values = JSON.parse(document.getElementById('<% $id %>').value);
- // hardcoded for the same reasons as misc/part_pkg_fcc_options
- var out = '';
- var tech = curr_values['technology'];
- if ( tech ) {
- if (technology_labels[tech]) {
- tech = technology_labels[tech];
- } else {
- tech = 'Technology '+tech; // unknown?
- }
- }
- var media = curr_values['media'] || 'unknown media';
- media = media.toLowerCase();
- if ( curr_values['is_consumer'] ) {
- out += '<li><strong>Consumer-grade</strong></li>>';
- } else {
- out += '<li><strong>Business-grade</strong></li>';
- }
- if ( curr_values['is_broadband'] ) {
- out += '<li>Broadband via <strong>' + tech + '</strong>'
- + '<li><strong>' + curr_values['broadband_downstream']
- + 'Mbps </strong> down / '
- + '<strong>' + curr_values['broadband_upstream']
- + 'Mbps </strong> up</li>';
- }
- if ( curr_values['is_phone'] ) {
- if ( curr_values['phone_wholesale'] ) {
- out += '<li>Wholesale telephone</li>';
- if ( curr_values['phone_vges'] ) {
- out += '<li><strong>' + curr_values['phone_vges'] + '</strong>'
- + ' switched voice-grade lines</li>';
- }
- if ( curr_values['phone_circuits'] ) {
- out += '<li><strong>' + curr_values['phone_circuits'] + '</strong>'
- + ' unswitched circuits</li>';
- }
- } else {
- // enduser service
- out += '<li>Local telephone over <strong>' + media + '</strong></li>'
- + '<li><strong>' + curr_values['phone_lines']
- + '</strong> voice-grade lines</li>';
- if ( curr_values['phone_localloop'] == 'resale' ) {
- out += '<li><strong>Resold</strong> from another carrier</li>>';
- } else if ( curr_values['phone_localloop'] == 'leased' ) {
- out += '<li>Using <strong>leased circuits</strong> from another carrier</li>';
- } else if ( curr_values['phone_localloop'] == 'owned' ) {
- out += '<li>Using <strong>our own circuits</strong></li>';
- }
- if ( curr_values['phone_longdistance'] ) {
- out += '<li>Includes <strong>long-distance service</strong></li>';
- }
- }
- } // is_phone
- if ( curr_values['is_voip'] ) {
- out += '<li><strong>VoIP</strong> telephone service</li>';
- if ( curr_values['voip_ott'] ) {
- out += '<li>Using a <strong>separate</strong> last-mile connection</li>';
- } else {
- out += '<li><strong>Including</strong> last-mile connection</li>';
- }
- } // is_voip
-
- var out_ul = document.getElementById('<% $id %>_display_fcc_options');
- out_ul.innerHTML = out;
-}
-<&| onload.js &>
- show_fcc_options();
-</&>
-</SCRIPT>
<%init>
my %opt = @_;
my $id = $opt{id} || $opt{field};
diff --git a/httemplate/elements/tr-select-reason.html b/httemplate/elements/tr-select-reason.html
index b7a715b42..0df7c058a 100755
--- a/httemplate/elements/tr-select-reason.html
+++ b/httemplate/elements/tr-select-reason.html
@@ -29,109 +29,119 @@ Example:
</%doc>
+% # note style improvements.
+% # - no more conditionally included code here
+% # - callers are not expected to pass javascript fragments
+% # - no redundant checking of ACLs or parameters
+% # - form fields are grouped for easy management
+% # - use the standard select-table widget instead of ad hoc crap
<SCRIPT TYPE="text/javascript">
- function sh_add<% $func_suffix %>()
- {
- var hints = <% encode_json(\@hints) %>;
+ function <% $id %>_changed() {
+ var hints = <% encode_json(\%all_hints) %>;
var select_reason = document.getElementById('<% $id %>');
-% if ( $class eq 'S' ) {
document.getElementById('<% $id %>_hint').innerHTML =
- hints[select_reason.selectedIndex];
-% }
+ hints[select_reason.value] || '';
- if (select_reason.selectedIndex == 0){
- <% $controlledbutton ? $controlledbutton.'.disabled = true;' : ';' %>
- }else{
- <% $controlledbutton ? $controlledbutton.'.disabled = false;' : ';' %>
+ // toggle submit button state
+ var submit_button = document.getElementById(<% $opt{control_button} |js_string %>);
+ if (submit_button) {
+ submit_button.disabled = ( select_reason.value == 0 );
}
-%if ($curuser->access_right($add_access_right)){
-
- if (select_reason.selectedIndex ==
- (select_reason.length - 1)) {
- document.getElementById('new<% $id %>').disabled = false;
- document.getElementById('new<% $id %>').style.display = 'inline';
- document.getElementById('new<% $id %>Label').style.display = 'inline';
- document.getElementById('new<% $id %>T').disabled = false;
- document.getElementById('new<% $id %>T').style.display = 'inline';
- document.getElementById('new<% $id %>TLabel').style.display = 'inline';
+ // toggle visibility of 'new reason' fields
+ var new_fields = document.getElementById('<% $id %>_new_fields');
+ if ( select_reason.value == -1 ) {
+ new_fields.disabled = false;
+ new_fields.style.display = '';
} else {
- document.getElementById('new<% $id %>').disabled = true;
- document.getElementById('new<% $id %>').style.display = 'none';
- document.getElementById('new<% $id %>Label').style.display = 'none';
- document.getElementById('new<% $id %>T').disabled = true;
- document.getElementById('new<% $id %>T').style.display = 'none';
- document.getElementById('new<% $id %>TLabel').style.display = 'none';
+ new_fields.disabled = true;
+ new_fields.style.display = 'none';
}
-%}
-
}
+ <&| onload.js &> <% $id %>_changed(); </&>
</SCRIPT>
-<TR>
- <TD ALIGN="right"><% mt('Reason') |h %></TD>
- <TD>
- <SELECT id="<% $id %>" name="<% $name %>" onFocus="sh_add<% $func_suffix %>()" onChange="sh_add<% $func_suffix %>()">
- <OPTION VALUE="" <% ($init_reason eq '') ? 'SELECTED' : '' %>><% mt('Select Reason...') |h %></OPTION>
-% foreach my $reason (@reasons) {
- <OPTION VALUE="<% $reason->reasonnum %>" <% ($init_reason == $reason->reasonnum) ? 'SELECTED' : '' %>><% $reason->reasontype->type %> : <% $reason->reason %></OPTION>
-% }
-% if ($curuser->access_right($add_access_right)) {
- <OPTION VALUE="-1" <% ($init_reason == -1) ? 'SELECTED' : '' %>><% mt('Add new reason') |h %></OPTION>
-% }
-%
- </SELECT>
- </TD>
-</TR>
-
-% my @types = qsearch( 'reason_type', { 'class' => $class } );
-% if (scalar(@types) < 1) { # we should never reach this
-<TR>
- <TD ALIGN="right">
- <P><% mt('No reason types. Please add some.') |h %></P>
- </TD>
-</TR>
-% }elsif (scalar(@types) == 1) {
-<TR>
- <TD ALIGN="right">
- <P id="new<% $name %>TLabel" style="display:<% $display %>"><% mt('Reason Type') |h %></P>
- </TD>
- <TD>
- <P id="new<% $name %>T" disabled="<% $disabled %>" style="display:<% $display %>"><% $types[0]->type %>
- <INPUT type="hidden" name="new<% $name %>T" value="<% $types[0]->typenum %>">
- </TD>
-</TR>
-
-% }else{
-
-<TR>
- <TD ALIGN="right">
- <P id="new<% $id %>TLabel" style="display:<% $display %>"><% mt('Reason Type') |h %></P>
- </TD>
- <TD>
- <SELECT id="new<% $id %>T" name="new<% $name %>T" "<% $disabled %>" style="display:<% $display %>">
-% for my $type (@types) {
- <OPTION VALUE="<% $type->typenum %>" <% ($init_type == $type->typenum) ? 'SELECTED' : '' %>><% $type->type %></OPTION>
-% }
- </SELECT>
- </TD>
-</TR>
-% }
+%# sadly can't just use add_inline here, as we have non-text fields
+<& tr-select-table.html,
+ 'label' => 'Reason',
+ 'field' => $name,
+ 'id' => $id,
+ 'table' => 'reason',
+ 'records' => \@reasons,
+ 'label_callback' => sub { my $reason = shift;
+ $reason->type . ' : ' . $reason->reason },
+ 'disable_empty' => 1,
+ 'pre_options' => [ 0 => 'Select reason...' ],
+ 'post_options' => [ -1 => 'Add new reason' ],
+ 'curr_value' => $init_reason,
+ 'onchange' => $id.'_changed()',
+&>
+
+% # "add new reason" fields
+% # should be a <fieldset>, but that doesn't fit well into the table
+
+<TR id="<% $id %>_new_fields">
+ <TD COLSPAN=2>
+ <TABLE CLASS="inv" STYLE="text-align: left">
+
+ <& tr-input-text.html,
+ label => 'New reason',
+ field => $id.'_new_reason'
+ &>
+
+% my @types = qsearch( 'reason_type', { 'class' => $class } );
+% if (scalar(@types) < 1) { # we should never reach this
+ <TR>
+ <TD ALIGN="right">
+ <P><% mt('No reason types. Please add some.') |h %></P>
+ </TD>
+ </TR>
+% } elsif (scalar(@types) == 1) {
+ <& tr-fixed.html,
+ label => 'Reason type',
+ field => $id.'_new_reason_type',
+ curr_value => $types[0]->typenum,
+ formatted_value => $types[0]->type,
+ &>
+% } else { # more than one type, the normal case
+ <& tr-select-table.html,
+ label => 'Reason type',
+ field => $id.'_new_reason_type',
+ table => 'reason_type',
+ name_col => 'type',
+ hashref => { 'class' => $class },
+ disable_empty => 1,
+ &>
+% } # scalar(@types)
% if ( $class eq 'S' ) {
-<TR>
- <TD COLSPAN=2 ALIGN="center" id="<% $id %>_hint">
- </TD>
-</TR>
+ <& tr-checkbox.html,
+ label => 'Credit the unused portion of service when suspending',
+ field => $id.'_new_unused_credit',
+ value => 'Y'
+ &>
+ <& tr-select-part_pkg.html,
+ label => 'Charge this fee when unsuspending',
+ field => $id.'_new_unsuspend_pkgpart',
+ hashref => { disabled => '', freq => '0' },
+ empty_label => 'none',
+ &>
+ <& tr-checkbox.html,
+ label => 'Hold unsuspension fee until the next bill',
+ field => $id.'_new_unsuspend_hold',
+ value => 'Y',
+ &>
% }
+ </table>
+ </td>
+</tr>
+% # container for hints
<TR>
- <TD ALIGN="right">
- <P id="new<% $id %>Label" style="display:<% $display %>"><% mt('New Reason') |h %></P>
+ <TD COLSPAN=2 ALIGN="center" id="<% $id %>_hint" style="font-size:small">
</TD>
- <TD><INPUT id="new<% $id %>" name="new<% $name %>" type="text" value="<% $init_newreason |h %>" "<% $disabled %>" style="display:<% $display %>"></TD>
</TR>
<%init>
@@ -148,11 +158,8 @@ if ( $opt{'cgi'} ) {
$init_reason = $opt{'curr_value'};
}
-my $controlledbutton = $opt{'control_button'};
-
-( my $func_suffix = $name ) =~ s/\./_/g;
-
-my $id = $opt{'id'} || $func_suffix;
+my $id = $opt{'id'} || $name;
+$id =~ s/\./_/g; # for edit/part_event
my $add_access_right;
if ($class eq 'C') {
@@ -167,41 +174,21 @@ if ($class eq 'C') {
die "illegal class: $class";
}
-my( $display, $disabled ) = ( 'none', 'DISABLED' );
-my( $init_type, $init_newreason ) = ( '', '' );
-if ($init_reason == -1 || ref($init_reason) ) {
-
- $display = 'inline';
- $disabled = '';
-
- if ( ref($init_reason) ) {
- $init_type = $init_reason->{'typenum'};
- $init_newreason = $init_reason->{'reason'};
- $init_reason = -1;
- } elsif ( $opt{'cgi'} ) {
- $init_type = $opt{'cgi'}->param( "new${name}T" );
- $init_newreason = $opt{'cgi'}->param( "new$name" );
- }
-
-}
-
-my $extra_sql =
- "WHERE class = '$class' and (disabled = '' OR disabled is NULL)";
-
my @reasons = qsearch({
- table => 'reason',
- hashref => {},
- extra_sql => $extra_sql,
- addl_from => 'LEFT JOIN reason_type '.
- ' ON reason_type.typenum = reason.reason_type',
- order_by => 'ORDER BY reason_type.type ASC, reason.reason ASC',
+ 'table' => 'reason',
+ 'addl_from' => ' LEFT JOIN reason_type'.
+ ' ON (reason.reason_type = reason_type.typenum)',
+ 'hashref' => { disabled => '' },
+ 'extra_sql' => " AND reason_type.class = '$class'",
+ 'order_by' => ' ORDER BY type, reason',
});
-my @hints;
+my %all_hints;
if ( $class eq 'S' ) {
my $conf = FS::Conf->new;
- @hints = ( '' );
+ %all_hints = ( 0 => '', -1 => '' );
foreach my $reason (@reasons) {
+ my @hints;
if ( $reason->unsuspend_pkgpart ) {
my $part_pkg = FS::part_pkg->by_key($reason->unsuspend_pkgpart);
if ( $part_pkg ) {
@@ -225,15 +212,13 @@ if ( $class eq 'S' ) {
'<FONT COLOR="#ff0000">Unsuspend pkg #'.$reason->unsuspend_pkgpart.
' not found.</FONT>';
}
- } else { #no unsuspend_pkgpart
- push @hints, '';
}
+ if ( $reason->unused_credit ) {
+ push @hints, mt('The customer will be credited for unused time.');
+ }
+ $all_hints{ $reason->reasonnum } = join('<BR>', @hints);
}
- push @hints, ''; # for the "new reason" case
- @hints = map {'<FONT SIZE="-1">'.$_.'</FONT>'} @hints;
}
-
my $curuser = $FS::CurrentUser::CurrentUser;
-
</%init>
diff --git a/httemplate/elements/tr-select-svc_circuit.html b/httemplate/elements/tr-select-svc_circuit.html
new file mode 100644
index 000000000..fb55501c5
--- /dev/null
+++ b/httemplate/elements/tr-select-svc_circuit.html
@@ -0,0 +1,41 @@
+% if ( $columnflag eq 'F' ) { # no good reason for this, but support it anyway
+ <INPUT TYPE="hidden" NAME="circuit_svcnum" VALUE="<% $circuit_svcnum %>">
+% } else {
+ <& tr-select-table.html,
+ 'table' => 'svc_circuit',
+ 'name_col' => 'circuit_id',
+ 'empty_label' => ' ',
+ %select_hash,
+ %opt
+ &>
+% }
+<%init>
+
+my %opt = @_;
+
+my $circuit_svcnum;
+if ( $opt{'curr_value'} =~ /^(\d+)$/ ) {
+ $circuit_svcnum = $1;
+}
+
+# generally not the svcpart of the circuit service (or any circuit service)
+my $part_svc = $opt{'part_svc'}
+ || qsearchs('part_svc', { 'svcpart' => $opt{'svcpart'} });
+
+my $columnflag = $part_svc->part_svc_column('circuit_svcnum')->columnflag;
+
+my $cust_pkg = $opt{'cust_pkg'};
+my $custnum;
+$custnum = $cust_pkg->custnum if $cust_pkg;
+
+my %select_hash;
+if ( $custnum =~ /^(\d+)$/ ) {
+ %select_hash = (
+ 'addl_from' => ' LEFT JOIN cust_svc USING (svcnum)' .
+ ' LEFT JOIN cust_pkg USING (pkgnum)',
+ 'extra_sql' => " WHERE cust_pkg.custnum = $custnum".
+ " OR svcnum = $circuit_svcnum",
+ );
+}
+
+</%init>
diff --git a/httemplate/misc/cancel_cust.html b/httemplate/misc/cancel_cust.html
index ebf111935..85367026c 100644
--- a/httemplate/misc/cancel_cust.html
+++ b/httemplate/misc/cancel_cust.html
@@ -48,7 +48,7 @@ toggle(false);
'field' => 'reasonnum',
'reason_class' => 'C',
'cgi' => $cgi,
- 'control_button' => "document.getElementById('confirm_cancel_cust_button')",
+ 'control_button' => 'confirm_cancel_cust_button',
&>
</TABLE>
diff --git a/httemplate/misc/cancel_pkg.html b/httemplate/misc/cancel_pkg.html
index e2734e97d..c80b2b278 100755
--- a/httemplate/misc/cancel_pkg.html
+++ b/httemplate/misc/cancel_pkg.html
@@ -6,9 +6,9 @@
<INPUT TYPE="hidden" NAME="pkgnum" VALUE="<% $pkgnum %>">
<INPUT TYPE="hidden" NAME="method" VALUE="<% $method %>">
-<BR><BR>
+<BR>
<% emt(ucfirst($method)." [_1]", $part_pkg->pkg_comment(cust_pkg=>$cust_pkg) ) %>
-<% ntable("#cccccc", 2) %>
+<table style="background-color: #cccccc; border-spacing: 2; width: 100%">
% my $date_init = 0;
% if ($method eq 'expire' || $method eq 'adjourn' || $method eq 'resume') {
@@ -58,7 +58,7 @@
field => 'reasonnum',
reason_class => $class,
curr_value => $reasonnum,
- control_button => "document.getElementById('confirm_cancel_pkg_button')",
+ control_button => "confirm_cancel_pkg_button",
&>
% }
diff --git a/httemplate/misc/confirm-censustract.html b/httemplate/misc/confirm-censustract.html
index 880cade3a..024bc17c4 100644
--- a/httemplate/misc/confirm-censustract.html
+++ b/httemplate/misc/confirm-censustract.html
@@ -1,3 +1,10 @@
+% if ( !$error and !$new_tract ) {
+% # then set_censustract returned nothing
+% # because it's not relevant for this address
+% # so output nothing (forces confirm_censustract() to continue)
+% $m->clear_buffer;
+% $m->abort;
+% }
<CENTER><BR><B>
% if ( $error ) {
Census tract error
diff --git a/httemplate/misc/cust_main-cancel.cgi b/httemplate/misc/cust_main-cancel.cgi
index 2ae9f1021..a78a8b3dc 100755
--- a/httemplate/misc/cust_main-cancel.cgi
+++ b/httemplate/misc/cust_main-cancel.cgi
@@ -22,51 +22,43 @@ if ( $cgi->param('custnum') =~ /^(\d+)$/ ) {
$custnum = $1;
}
-#false laziness w/process/cancel_pkg.html
-#untaint reasonnum
-my $reasonnum = $cgi->param('reasonnum');
-$reasonnum =~ /^(-?\d+)$/ || die "Illegal reasonnum";
-$reasonnum = $1;
-
-if ($reasonnum == -1) {
- $reasonnum = {
- 'typenum' => scalar( $cgi->param('newreasonnumT') ),
- 'reason' => scalar( $cgi->param('newreasonnum' ) ),
- };
+#untaint reasonnum / create new reason
+my ($reasonnum, $error) = $m->comp('process/elements/reason');
+if (!$reasonnum) {
+ $error ||= 'Reason required'
}
-#eslaf
-
my $cust_main = qsearchs( {
'table' => 'cust_main',
'hashref' => { 'custnum' => $custnum },
'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql,
} );
-my @errors;
-if($cgi->param('now_or_later')) {
+if ( $error ) {
+ # do nothing
+} elsif ( $cgi->param('now_or_later') ) {
$expire = parse_datetime($expire);
if($expire) {
#warn "setting expire dates on custnum#$custnum\n";
my @pkgs = $cust_main->ncancelled_pkgs;
- @errors = grep {$_} map { $_->cancel(
+ my @errors = grep {$_} map { $_->cancel(
'reason' => $reasonnum,
'date' => $expire,
) } @pkgs;
+ $error = join(' / ', @errors);
}
else {
- @errors = ("error parsing expire date: ".$cgi->param('expire'));
+ $error = ("error parsing expire date: ".$cgi->param('expire'));
}
}
else {
warn "cancelling $cust_main";
- @errors = $cust_main->cancel(
+ $error = $cust_main->cancel(
'ban' => $ban,
'reason' => $reasonnum,
);
}
-my $error = join(' / ', @errors) if scalar(@errors);
if ( $error ) {
$cgi->param('error', $error);
diff --git a/httemplate/misc/cust_main-suspend.cgi b/httemplate/misc/cust_main-suspend.cgi
index 61851364e..7a501d61a 100755
--- a/httemplate/misc/cust_main-suspend.cgi
+++ b/httemplate/misc/cust_main-suspend.cgi
@@ -22,50 +22,39 @@ if ( $cgi->param('custnum') =~ /^(\d+)$/ ) {
$custnum = $1;
}
-#false laziness w/process/cancel_pkg.html
-
-#untaint reasonnum
-my $reasonnum = $cgi->param('reasonnum');
-$reasonnum =~ /^(-?\d+)$/ || die "Illegal reasonnum";
-$reasonnum = $1;
-
-if ($reasonnum == -1) {
- $reasonnum = {
- 'typenum' => scalar( $cgi->param('newreasonnumT') ),
- 'reason' => scalar( $cgi->param('newreasonnum' ) ),
- };
+#untaint reasonnum / create new reason
+my ($reasonnum, $error) = $m->comp('process/elements/reason');
+if (!$reasonnum) {
+ $error ||= 'Reason required';
}
-#eslaf
-
my $cust_main = qsearchs( {
'table' => 'cust_main',
'hashref' => { 'custnum' => $custnum },
'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql,
} );
-my @errors;
-if($cgi->param('now_or_later')) {
+if ( $error ) {
+ # do nothing
+} elsif ( $cgi->param('now_or_later') ) {
$adjourn = parse_datetime($adjourn);
if($adjourn) {
#warn "setting adjourn dates on custnum#$custnum\n";
my @pkgs = $cust_main->unsuspended_pkgs;
- @errors = grep {$_} map { $_->suspend(
+ my @errors = grep {$_} map { $_->suspend(
'reason' => $reasonnum,
'date' => $adjourn,
) } @pkgs;
+ $error = join(' / ', @errors);
+ } else {
+ $error = ("error parsing adjourn date: ".$cgi->param('adjourn'));
}
- else {
- @errors = ("error parsing adjourn date: ".$cgi->param('adjourn'));
- }
-}
-else {
+} else {
warn "suspending $cust_main";
- @errors = $cust_main->suspend(
+ $error = $cust_main->suspend(
'reason' => $reasonnum,
);
}
-my $error = join(' / ', @errors) if scalar(@errors);
if ( $error ) {
$cgi->param('error', $error);
diff --git a/httemplate/misc/part_pkg_fcc_options.html b/httemplate/misc/part_pkg_fcc_options.html
index 27b45e003..0db4e2503 100644
--- a/httemplate/misc/part_pkg_fcc_options.html
+++ b/httemplate/misc/part_pkg_fcc_options.html
@@ -156,8 +156,7 @@ function save_changes() {
}
parent_input.value = JSON.stringify(data);
// update the display
- parent.show_fcc_options(parent_input.id);
- parent.cClick(); //overlib
+ parent.finish_edit_fcc(parent_input.id);
}
function enable_fieldset(fieldset_id) {
diff --git a/httemplate/misc/process/cancel_pkg.html b/httemplate/misc/process/cancel_pkg.html
index a106b845a..47ceca23b 100755
--- a/httemplate/misc/process/cancel_pkg.html
+++ b/httemplate/misc/process/cancel_pkg.html
@@ -65,17 +65,12 @@ if ( $method eq 'suspend' ) { #or 'adjourn'
my $cust_pkg = qsearchs( 'cust_pkg', {'pkgnum'=>$pkgnum} );
-#untaint reasonnum
-my $reasonnum = $cgi->param('reasonnum');
-if ( $method !~ /^(unsuspend|uncancel)$/ ) {
- $reasonnum =~ /^(-?\d+)$/ or die "Illegal reasonnum";
- $reasonnum = $1;
-
- if ($reasonnum == -1) {
- $reasonnum = {
- 'typenum' => scalar( $cgi->param('newreasonnumT') ),
- 'reason' => scalar( $cgi->param('newreasonnum' ) ),
- };
+#untaint reasonnum, and set up new reason if appropriate
+my ($reasonnum, $error);
+if ($method ne 'resume' and $method ne 'uncancel') {
+ ($reasonnum, $error) = $m->comp('elements/reason');
+ if (!$reasonnum) {
+ $error ||= 'Reason required';
}
}
@@ -87,7 +82,7 @@ my $bill =
my $svc_fatal = ( $cgi->param('svc_not_fatal') ne 'Y' );
-my $error = $cust_pkg->$method( 'reason' => $reasonnum,
+$error ||= $cust_pkg->$method( 'reason' => $reasonnum,
'date' => $date,
'resume_date' => $resume_date,
'last_bill' => $last_bill,
diff --git a/httemplate/misc/process/elements/reason b/httemplate/misc/process/elements/reason
new file mode 100644
index 000000000..ae92a7528
--- /dev/null
+++ b/httemplate/misc/process/elements/reason
@@ -0,0 +1,17 @@
+<%init>
+#untaint reasonnum, and set up new reason if appropriate
+my $reasonnum = $cgi->param('reasonnum');
+$reasonnum =~ /^(-?\d+)$/ or die "Illegal reasonnum";
+$reasonnum = $1;
+
+my $error;
+if ($reasonnum == -1) {
+ my $new_reason = FS::reason->new({
+ map { $_ => scalar( $cgi->param("reasonnum_new_$_") ) }
+ qw( reason_type reason unsuspend_pkgpart unsuspend_hold unused_credit )
+ }); # not sanitizing them here, but check() will do it
+ $error = $new_reason->insert;
+ $reasonnum = $new_reason->reasonnum;
+}
+return ($reasonnum, $error);
+</%init>
diff --git a/httemplate/misc/suspend_cust.html b/httemplate/misc/suspend_cust.html
index 8eedd0a57..83d974300 100644
--- a/httemplate/misc/suspend_cust.html
+++ b/httemplate/misc/suspend_cust.html
@@ -40,7 +40,7 @@ toggle(false);
'field' => 'reasonnum',
'reason_class' => 'S',
'cgi' => $cgi,
- 'control_button' => "document.getElementById('confirm_suspend_cust_button')",
+ 'control_button' => 'confirm_suspend_cust_button',
&>
</TABLE>
diff --git a/httemplate/misc/xmlhttp-address_standardize.html b/httemplate/misc/xmlhttp-address_standardize.html
index 618265364..d0255a02a 100644
--- a/httemplate/misc/xmlhttp-address_standardize.html
+++ b/httemplate/misc/xmlhttp-address_standardize.html
@@ -43,6 +43,10 @@ foreach my $pre ( @prefixes ) {
last if !$all_same;
}
+ $all_same = 0 if ( length( $old{$pre.'censustract'} ) > 0 &&
+ length( $new{$pre.'censustract'} ) > 0 &&
+ $old{$pre.'censustract'} ne $new{$pre.'censustract'} );
+
$all_same = 0 if $new{$pre.'error'};
}
diff --git a/httemplate/search/477.html b/httemplate/search/477.html
index 244bfa1df..ff2ac8638 100644
--- a/httemplate/search/477.html
+++ b/httemplate/search/477.html
@@ -212,7 +212,7 @@ my $part_titles = FS::Report::FCC_477->parts;
</%def>
<%def .header_voip>
<TR CLASS="head">
- <TD ROWSPAN=2>State</TD>
+ <TD ROWSPAN=3>State</TD>
<TD COLSPAN=2>VoIP OTT</TD>
<TD COLSPAN=8>VoIP Non-OTT</TD>
</TR>
diff --git a/httemplate/search/svc_circuit.cgi b/httemplate/search/svc_circuit.cgi
new file mode 100755
index 000000000..c14c55fdc
--- /dev/null
+++ b/httemplate/search/svc_circuit.cgi
@@ -0,0 +1,65 @@
+<& elements/svc_Common.html,
+ 'title' => 'Circuit Search Results',
+ 'name' => 'circuit services',
+ 'query' => $query,
+ 'count_query' => $query->{'count_query'},
+ 'redirect' => [ popurl(2). "view/svc_circuit.html?", 'svcnum' ],
+ 'header' => [ '#',
+ 'Provider',
+ 'Type',
+ 'Termination',
+ 'Circuit ID',
+ 'IP Address',
+ FS::UI::Web::cust_header($cgi->param('cust_fields')),
+ ],
+ 'fields' => [ 'svcnum',
+ 'provider',
+ 'typename',
+ 'termination',
+ 'circuit_id',
+ 'ip_addr',
+ \&FS::UI::Web::cust_fields,
+ ],
+ 'links' => [ $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('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 )) {
+ $search_hash{$_} = $cgi->param($_) if $cgi->param($_);
+ }
+ foreach (qw(pkgpart routernum towernum sectornum)) {
+ $search_hash{$_} = [ $cgi->param($_) ] if $cgi->param($_);
+ }
+}
+
+my $query = FS::svc_circuit->search(\%search_hash);
+
+my $link = [ $p.'view/svc_circuit.html?', 'svcnum' ];
+
+</%init>
diff --git a/httemplate/view/cust_main.cgi b/httemplate/view/cust_main.cgi
index 4880ac3dc..833b6d08c 100755
--- a/httemplate/view/cust_main.cgi
+++ b/httemplate/view/cust_main.cgi
@@ -60,8 +60,8 @@ function areyousure(href, message) {
'actionlabel' => emt('Confirm Suspension'),
'color' => '#ff9900',
'cust_main' => $cust_main,
- 'width' => 616, #make room for reasons
- 'height' => 366,
+ 'width' => 768, #make room for reasons
+ 'height' => 450,
}
&> |
% }
@@ -91,7 +91,7 @@ function areyousure(href, message) {
'color' => '#ff0000',
'cust_main' => $cust_main,
'width' => 616, #make room for reasons
- 'height' => 366,
+ 'height' => 410,
}
&> |
% }
diff --git a/httemplate/view/cust_main/locations.html b/httemplate/view/cust_main/locations.html
index fdbbc396b..336c1aa98 100755
--- a/httemplate/view/cust_main/locations.html
+++ b/httemplate/view/cust_main/locations.html
@@ -35,16 +35,18 @@ table.location {
<TH COLSPAN=5>
<DIV CLASS="<% $loc->disabled ? 'loclabel disabled' : 'loclabel' %>">
<% $loc->location_label %>
-% if ( $loc->censustract ) {
- <BR>
- <FONT SIZE=-1>
- <% $loc->censustract %> (<% $loc->censusyear %> census)
- </FONT>
-% } elsif ( $conf->exists('cust_main-require_censustract') ) {
- <BR>
- <FONT SIZE=-1 COLOR="#ee3300">
- <% emt('Census tract unknown') %>
- </FONT>
+% if ( $loc->country eq 'US' ) { # only U.S. census tracts for now
+% if ( $loc->censustract ) {
+ <BR>
+ <FONT SIZE=-1>
+ <% $loc->censustract %> (<% $loc->censusyear %> census)
+ </FONT>
+% } elsif ( $conf->exists('cust_main-require_censustract') ) {
+ <BR>
+ <FONT SIZE=-1 COLOR="#ee3300">
+ <% emt('Census tract unknown') %>
+ </FONT>
+% }
% }
</DIV>
<DIV STYLE="display: inline; float:right;">
diff --git a/httemplate/view/cust_main/packages/location.html b/httemplate/view/cust_main/packages/location.html
index c5ef10ee4..99d91e514 100644
--- a/httemplate/view/cust_main/packages/location.html
+++ b/httemplate/view/cust_main/packages/location.html
@@ -30,16 +30,18 @@
&>
</FONT>
% }
-% if ( $loc->censustract ) {
- <BR>
- <FONT SIZE=-1>
- <% $loc->censustract %> (<% $loc->censusyear %> census)
- </FONT>
-% } elsif ( $opt{'cust_main-require_censustract'} ) {
- <BR>
- <FONT SIZE=-1 COLOR="#ee3300">
- <% emt('Census tract unknown') %>
- </FONT>
+% if ( $loc->country eq 'US' ) {
+% if ( $loc->censustract ) {
+ <BR>
+ <FONT SIZE=-1>
+ <% $loc->censustract %> (<% $loc->censusyear %> census)
+ </FONT>
+% } elsif ( $opt{'cust_main-require_censustract'} ) {
+ <BR>
+ <FONT SIZE=-1 COLOR="#ee3300">
+ <% emt('Census tract unknown') %>
+ </FONT>
+% }
% }
% if ( $default ) {
diff --git a/httemplate/view/cust_main/packages/status.html b/httemplate/view/cust_main/packages/status.html
index accdb4588..f760d6fcc 100644
--- a/httemplate/view/cust_main/packages/status.html
+++ b/httemplate/view/cust_main/packages/status.html
@@ -582,6 +582,7 @@ sub pkg_suspend_link {
'actionlabel' => emt('Suspend'),
'color' => '#FF9900',
'cust_pkg' => shift,
+ 'height' => 420,
)
}
@@ -592,6 +593,7 @@ sub pkg_adjourn_link {
'actionlabel' => emt('Adjourn'),
'color' => '#CC6600',
'cust_pkg' => shift,
+ 'height' => 445,
)
}
diff --git a/httemplate/view/elements/svc_Common.html b/httemplate/view/elements/svc_Common.html
index b12d2ddcd..6c5c90201 100644
--- a/httemplate/view/elements/svc_Common.html
+++ b/httemplate/view/elements/svc_Common.html
@@ -32,6 +32,15 @@ function areyousure(href) {
window.location.href = href;
}
</SCRIPT>
+<STYLE>
+ td.content {
+ background-color: #ffffff;
+ }
+ .error {
+ color: #ff0000;
+ font-weight: bold;
+ }
+</STYLE>
% if ( $custnum ) {
@@ -67,61 +76,20 @@ function areyousure(href) {
<% ntable("#cccccc") %><TR><TD><% ntable("#cccccc",2) %>
-% my @inventory_items = $svc_x->inventory_item;
% foreach my $f ( @$fields ) {
-%
-% my($field, $type, $value);
-% if ( ref($f) ) {
-% $field = $f->{'field'};
-% $type = $f->{'type'} || 'text';
-% if ( $f->{'value_callback'} ) {
-% my $hack_strict_refs = \&{ $f->{'value_callback'} };
-% $value = &$hack_strict_refs($svc_x);
-% } else {
-% $value = encode_entities($svc_x->$field);
-% }
-% } else {
-% $field = $f;
-% $type = 'text';
-% $value = encode_entities($svc_x->$field);
-% }
-%
-% my $columndef = $part_svc->part_svc_column($field);
-% if ( $columndef->columnflag =~ /^[MA]$/ && $columndef->columnvalue =~ /,/ )
-% {
-% # inventory-select field with multiple classes
-% # show the class name to disambiguate
-% my ($item) = grep { $_->svc_field eq $field } @inventory_items;
-% my $class = qsearchs('inventory_class', { classnum => $item->classnum });
-% $value .= ' <i>('. $class->classname . ')</i>' if $class;
-% }
-% unless ($columndef->columnflag eq 'F' && !length($columndef->columnvalue)) {
-
+% my ($field, $label, $value) = &{ $format_field }($f);
+% next if !$field;
<TR>
<TD ALIGN="right">
- <% ( $opt{labels} && exists $opt{labels}->{$field} )
- ? $opt{labels}->{$field}
- : $field
- %>
+ <% $label %>
</TD>
-% $value = time2str($date_format,$value)
-% if $type eq 'date' && $value;
-% $value = time2str("$date_format %H:%M",$value)
-% if $type eq 'datetime' && $value;
-% $value = $value eq 'Y' ? emt('Yes') : emt('No')
-% if $type eq 'checkbox';
-% $value .= ' ('. (Net::MAC::Vendor::lookup($value))->[0]. ')'
-% if $type =~ /mac_addr$/ && $value =~ /\w/i;
-% #eventually more options for <SELECT>, etc. fields
-
- <TD BGCOLOR="#ffffff"><% $value %><TD>
-
+ <TD CLASS="content">
+ <% $value %>
+ </TD>
</TR>
-% }
-%
-% }
+% }
% foreach (sort { $a cmp $b } $svc_x->virtual_fields) {
<% $svc_x->pvf($_)->widget('HTML', 'view', $svc_x->getfield($_)) %>
@@ -193,7 +161,7 @@ my $svc_x = qsearchs({
my $cust_svc = $svc_x->cust_svc;
my ($label, $value, $svcdb, $part_svc );
-my $labels = $opt{labels}; #not -> here
+my $labels = $opt{labels} || {};
if ( $cust_svc ) {
($label, $value, $svcdb) = $cust_svc->label;
@@ -227,7 +195,10 @@ if ($pkgnum) {
# attached routers
if ( my $router = qsearchs('router', { svcnum => $svc_x->svcnum }) ) {
- push @$fields, qw(router_routername router_block);
+ push @$fields,
+ 'router_routername',
+ 'router_block';
+
$labels->{'router_routername'} = 'Attached router';
$labels->{'router_block'} = 'Attached address block';
$svc_x->set('router_routername', $router->routername);
@@ -235,10 +206,100 @@ if ( my $router = qsearchs('router', { svcnum => $svc_x->svcnum }) ) {
if ( $block ) {
$svc_x->set('router_block', $block->cidr);
} else {
- $svc_x->set('router_block', '<i>(none)</i>');
+ $svc_x->set('router_block', '(none)');
}
}
+my @inventory_items = $svc_x->inventory_item;
+
+my $format_field = sub {
+ my $f = shift;
+ my($field, $type, $value);
+ if ( ref($f) ) {
+ $field = $f->{'field'};
+ $type = $f->{'type'} || 'text';
+ } else {
+ $field = $f;
+ $type = 'text';
+ }
+
+ my $columndef = $part_svc->part_svc_column($field);
+ # skip fields that are fixed and empty
+ if ( $columndef->columnflag eq 'F'
+ and length($columndef->columnvalue) == 0 ) {
+ return;
+ }
+
+ # things that override the column value: value_callback, select
+ if ( ref($f) and $f->{'value_callback'} ) {
+
+ my $hack_strict_refs = \&{ $f->{'value_callback'} };
+ $value = &$hack_strict_refs($svc_x);
+
+ } elsif ( $type eq 'select-table' ) {
+ # imitates the /elements/select-table interface
+ $value = $svc_x->$field;
+
+ my $value_col = $f->{'value_col'} ||
+ dbdef->table($f->{'table'})->primary_key;
+ my $name_col = $f->{'name_col'} or die 'name_col required';
+ # we don't yet support multiple-valued fields here
+ my $obj = qsearchs($f->{'table'}, { $value_col => $value });
+ if ( $obj ) {
+ $value = $obj->$name_col; # can be any method of the object
+ } else {
+ # show the raw value, but mark it as an error
+ $value = '<SPAN CLASS="error">' . $f->{'table'} . ' ' .
+ encode_entities($value) . '</SPAN>';
+ }
+
+ } else {
+ $value = encode_entities($svc_x->$field);
+ }
+
+ # inventory-select field with multiple classes
+ # show the class name to disambiguate
+ if ( $columndef->columnflag =~ /^[MA]$/ && $columndef->columnvalue =~ /,/ )
+ {
+ my ($item) = grep { $_->svc_field eq $field } @inventory_items;
+ my $class = qsearchs('inventory_class', { classnum => $item->classnum });
+ $value .= ' <i>('. $class->classname . ')</i>' if $class;
+ }
+
+ # formatting tweaks
+ if ( $type eq 'date' and $value ) {
+ $value = time2str($date_format,$value)
+ } elsif ( $type eq 'datetime' and $value ) {
+ $value = time2str("$date_format %H:%M",$value)
+ } elsif ( $type eq 'checkbox' ) {
+ $value = $value eq 'Y' ? emt('Yes') : emt('No');
+ } elsif ( $type eq 'mac_addr' and $value =~ /\w/) {
+ $value .= ' ('. (Net::MAC::Vendor::lookup($value))->[0]. ')'
+ }
+
+ # 'link' option
+ my $href;
+ if ( ref($f) and exists $f->{'link'} ) {
+ my $link = $f->{'link'};
+ if ( ref($link) eq 'CODE' ) {
+ $link = &{$link}($svc_x);
+ }
+ if ( ref($link) eq 'ARRAY' ) {
+ my ($base, $method) = @$link;
+ $href = $base . $svc_x->$method();
+ } elsif ( !ref($link) ) {
+ $href = $link;
+ }
+
+ if ( $href ) {
+ $value = qq!<A HREF="$href">$value</A>!;
+ }
+ }
+
+ my $label = $opt{labels}->{$field} || $field;
+ return ($field, $label, $value);
+};
+
&{ $opt{'svc_callback'} }( $cgi, $svc_x, $part_svc, $cust_pkg, $fields, \%opt )
if $opt{'svc_callback'};
</%init>
diff --git a/httemplate/view/svc_circuit.html b/httemplate/view/svc_circuit.html
new file mode 100644
index 000000000..c8d5d23ee
--- /dev/null
+++ b/httemplate/view/svc_circuit.html
@@ -0,0 +1,80 @@
+<& elements/svc_Common.html,
+ 'table' => 'svc_circuit',
+ 'labels' => \%labels,
+ 'fields' => \@fields,
+ 'html_foot' => sub { $self->call_method('.foot', @_) },
+&>
+<%method .foot>
+% my $svc_circuit = shift;
+% my $link = [ 'svc_phone.cgi?', 'svcnum' ];
+% if ( FS::svc_phone->count('circuit_svcnum = '.$svc_circuit->svcnum) ) {
+<& /search/elements/search.html,
+
+ 'title' => 'Provisioned phone services',
+ 'name_singular' => 'phone number',
+ 'query' => { 'table' => 'svc_phone',
+ 'hashref' => { 'circuit_svcnum' => $svc_circuit->svcnum },
+ 'addl_from' => ' LEFT JOIN cust_svc USING (svcnum)'.
+ ' LEFT JOIN part_svc USING (svcpart)',
+ 'select' => 'svc_phone.*, part_svc.*',
+ },
+ 'count_query' => 'SELECT COUNT(*) FROM svc_phone WHERE circuit_svcnum = '.
+ $svc_circuit->svcnum,
+ 'header' => [ '#', 'Service', 'Phone number', ],
+ 'fields' => [ 'svcnum', 'svc', 'phonenum' ],
+ 'links' => [ $link, $link, $link ],
+ 'align' => 'rlr',
+
+ 'html_form' => '<SPAN CLASS="fsinnerbox-title">Phone services</SPAN>',
+ 'nohtmlheader' => 1,
+ 'disable_total' => 1,
+ 'disable_maxselect' => 1,
+ 'really_disable_download' => 1,
+&>
+ <BR>
+% }
+</%method>
+<%init>
+
+my @fields = (
+ 'circuit_id',
+ { field => 'providernum',
+ type => 'select-table',
+ table => 'circuit_provider',
+ name_col => 'provider',
+ },
+ { field => 'typenum',
+ type => 'select-table',
+ table => 'circuit_type',
+ name_col => 'typename',
+ },
+ { field => 'termnum',
+ type => 'select-table',
+ table => 'circuit_termination',
+ name_col => 'termination',
+ },
+ qw( vendor_qual_id vendor_order_id vendor_order_type vendor_order_status
+ desired_due_date due_date
+ endpoint_ip_addr
+ ),
+ { field => 'endpoint_mac_addr', type => 'mac_addr' },
+);
+
+
+my %labels = (
+ circuit_id => 'Circuit ID',
+ providernum => 'Provider',
+ typenum => 'Circuit type',
+ termnum => 'Termination',
+ vendor_qual_id => 'Qualification ID',
+ vendor_order_id => 'Order ID',
+ vendor_order_type => 'Order type',
+ vendor_order_status => 'Order status',
+ desired_due_date => 'Desired due date',
+ due_date => 'Due date',
+ endpoint_ip_addr => 'Endpoint IP address',
+ endpoint_mac_addr => 'MAC address',
+);
+
+my $self = $m->request_comp;
+</%init>
diff --git a/httemplate/view/svc_phone.cgi b/httemplate/view/svc_phone.cgi
index 2a2ef243b..1c0fb39c5 100644
--- a/httemplate/view/svc_phone.cgi
+++ b/httemplate/view/svc_phone.cgi
@@ -1,7 +1,7 @@
<& elements/svc_Common.html,
'table' => 'svc_phone',
'fields' => \@fields,
- 'labels' => \%labels,
+ 'labels' => \%labels,
'html_foot' => $html_foot,
&>
<%init>
@@ -53,6 +53,11 @@ if ( $conf->exists('svc_phone-lnp') ) {
;
}
+$labels{circuit_label} = mt('Circuit');
+push @fields, { field => 'circuit_label',
+ link => [ $p.'view/svc_circuit.html?', 'circuit_svcnum' ]
+ };
+
my $html_foot = sub {
my $svc_phone = shift;
diff --git a/rt/lib/RT/CustomFields.pm b/rt/lib/RT/CustomFields.pm
index 7c7701580..5fdac15aa 100644
--- a/rt/lib/RT/CustomFields.pm
+++ b/rt/lib/RT/CustomFields.pm
@@ -121,8 +121,7 @@ sub LimitToChildType {
my $self = shift;
my $lookup = shift;
- $self->Limit( FIELD => 'LookupType', VALUE => "$lookup" );
- $self->Limit( FIELD => 'LookupType', ENDSWITH => "$lookup" );
+ $self->Limit( FIELD => 'LookupType', VALUE => "$lookup", OPERATOR => "ENDSWITH" );
}
@@ -137,8 +136,7 @@ sub LimitToParentType {
my $self = shift;
my $lookup = shift;
- $self->Limit( FIELD => 'LookupType', VALUE => "$lookup" );
- $self->Limit( FIELD => 'LookupType', STARTSWITH => "$lookup" );
+ $self->Limit( FIELD => 'LookupType', VALUE => "$lookup", OPERATOR => "STARTSWITH" );
}
=head2 LimitToObjectId
diff --git a/rt/share/html/Elements/ShowCustomFields b/rt/share/html/Elements/ShowCustomFields
index 3fe51c109..f632e68fe 100644
--- a/rt/share/html/Elements/ShowCustomFields
+++ b/rt/share/html/Elements/ShowCustomFields
@@ -49,27 +49,31 @@
% if ($Table) {
<table>
% }
-% while ( my $CustomField = $CustomFields->Next ) {
-% my $Values = $Object->CustomFieldValues( $CustomField->Id );
-% my $count = $Values->Count;
+% foreach my $set ($CustomFields, $HiddenCustomFields) {
+% $set->GotoFirstItem;
+% while ( my $CustomField = $set->Next ) {
+% my $Values = $Object->CustomFieldValues( $CustomField->Id );
+% my $count = $Values->Count;
+% next if $count == 0 and $CustomField->Disabled;
<tr id="CF-<%$CustomField->id%>-ShowRow">
<td class="label"><% $CustomField->Name %>:</td>
<td class="value">
-% unless ( $count ) {
+% if ( $count == 0 ) {
<i><&|/l&>(no value)</&></i>
-% } elsif ( $count == 1 ) {
-% $print_value->( $CustomField, $Values->First );
-% } else {
+% } elsif ( $count == 1 ) {
+% $print_value->( $CustomField, $Values->First );
+% } else {
<ul>
-% while ( my $Value = $Values->Next ) {
+% while ( my $Value = $Values->Next ) {
<li>
-% $print_value->( $CustomField, $Value );
+% $print_value->( $CustomField, $Value );
</li>
-% }
+% }
</ul>
-% }
+% }
</td>
</tr>
+% }
% }
% if ($Table) {
</table>
@@ -83,9 +87,15 @@ $m->callback(
CustomFields => $CustomFields,
);
+# kludge to allow "Support time" to be displayed even though it's been
+# removed
+my $HiddenCustomFields = RT::CustomFields->new($session{'CurrentUser'});
+$HiddenCustomFields->LimitToChildType(ref $Object);
+$HiddenCustomFields->Limit( FIELD => 'Type', VALUE => 'TimeValue' );
+$HiddenCustomFields->LimitToDeleted;
+
# don't print anything if there is no custom fields
-return unless $CustomFields->First;
-$CustomFields->GotoFirstItem;
+return unless $CustomFields->Count > 0 or $HiddenCustomFields->Count > 0;
my $print_value = sub {
my ($cf, $value) = @_;
@@ -127,5 +137,6 @@ my $print_value = sub {
<%ARGS>
$Object => undef
$CustomFields => $Object->CustomFields
+
$Table => 1
</%ARGS>