From 99c6f47aa88806bfdbdef64250050273318bf057 Mon Sep 17 00:00:00 2001 From: Mark Wells Date: Wed, 19 Nov 2014 16:41:12 -0800 Subject: allow old "support time" custom field to be viewed, but not edited; #30921 --- rt/lib/RT/CustomFields.pm | 6 ++---- rt/share/html/Elements/ShowCustomFields | 13 +++++++++++++ 2 files changed, 15 insertions(+), 4 deletions(-) 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..387497e29 100644 --- a/rt/share/html/Elements/ShowCustomFields +++ b/rt/share/html/Elements/ShowCustomFields @@ -52,6 +52,7 @@ % while ( my $CustomField = $CustomFields->Next ) { % my $Values = $Object->CustomFieldValues( $CustomField->Id ); % my $count = $Values->Count; +% next if $count == 0 and $CustomField->Disabled; <% $CustomField->Name %>: @@ -70,6 +71,10 @@ % } +% if ( $CustomFields->IsLast ) { +% # switch to the other result set +% $CustomFields = $HiddenCustomFields; +% } % } % if ($Table) { @@ -83,6 +88,13 @@ $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->Limit( FIELD => 'Disabled', VALUE => 1 ); + # don't print anything if there is no custom fields return unless $CustomFields->First; $CustomFields->GotoFirstItem; @@ -127,5 +139,6 @@ my $print_value = sub { <%ARGS> $Object => undef $CustomFields => $Object->CustomFields + $Table => 1 -- cgit v1.2.1 From 410b1169cc6ebada005ab2771469abd1c7e824d0 Mon Sep 17 00:00:00 2001 From: Jeremy Davis Date: Thu, 20 Nov 2014 14:54:46 -0500 Subject: Ticket #2916 Debian packages --- debian/rules | 1 + 1 file changed, 1 insertion(+) diff --git a/debian/rules b/debian/rules index 033d89264..dd7da1d16 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}/usr/local/etc/freeside/handler.pl #RT Config -- cgit v1.2.1 From 5a61f2f2e84350b0592fcf81feca02e82b73cc7f Mon Sep 17 00:00:00 2001 From: Mark Wells Date: Thu, 20 Nov 2014 16:10:53 -0800 Subject: also unhide support time on transactions, #30921 --- rt/share/html/Elements/ShowCustomFields | 34 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/rt/share/html/Elements/ShowCustomFields b/rt/share/html/Elements/ShowCustomFields index 387497e29..f632e68fe 100644 --- a/rt/share/html/Elements/ShowCustomFields +++ b/rt/share/html/Elements/ShowCustomFields @@ -49,31 +49,30 @@ % if ($Table) { % } -% while ( my $CustomField = $CustomFields->Next ) { -% my $Values = $Object->CustomFieldValues( $CustomField->Id ); -% my $count = $Values->Count; -% next if $count == 0 and $CustomField->Disabled; +% 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; -% if ( $CustomFields->IsLast ) { -% # switch to the other result set -% $CustomFields = $HiddenCustomFields; % } % } % if ($Table) { @@ -93,11 +92,10 @@ $m->callback( my $HiddenCustomFields = RT::CustomFields->new($session{'CurrentUser'}); $HiddenCustomFields->LimitToChildType(ref $Object); $HiddenCustomFields->Limit( FIELD => 'Type', VALUE => 'TimeValue' ); -$HiddenCustomFields->Limit( FIELD => 'Disabled', VALUE => 1 ); +$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) = @_; -- cgit v1.2.1 From 5836246bfede18c0e2eb616e0a18a296b167b98a Mon Sep 17 00:00:00 2001 From: Jeremy Davis Date: Fri, 21 Nov 2014 11:02:08 -0500 Subject: Ticket #32237 Purchase additional package is not completely disabled by the menu item --- fs_selfservice/FS-SelfService/cgi/customer_order_pkg.html | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) 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') %> -- cgit v1.2.1 From 24f7fad2612dcbe97b1cfaadf45f8f57d4a534c8 Mon Sep 17 00:00:00 2001 From: Jeremy Davis Date: Fri, 21 Nov 2014 14:04:30 -0500 Subject: Ticket #32285 Password reset login link not skinned --- fs_selfservice/FS-SelfService/cgi/do_process_forgot_password.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 log in."; + $OUT .= "Your password has been changed. You can now log in."; } %> -- cgit v1.2.1 From 6cc3e679908ad79f0080960d30b1cab689326a07 Mon Sep 17 00:00:00 2001 From: Mark Wells Date: Fri, 21 Nov 2014 16:33:04 -0800 Subject: fix several UI nits in FCC 477 report configuration, #24047 --- FS/FS/Report/FCC_477.pm | 4 +- httemplate/browse/part_pkg-fcc.html | 33 ++++++---- httemplate/edit/part_pkg.cgi | 5 ++ httemplate/edit/process/bulk-part_pkg-fcc.html | 6 +- httemplate/elements/input-fcc_options.html | 3 +- httemplate/elements/tr-input-fcc_options.html | 89 +------------------------- httemplate/misc/part_pkg_fcc_options.html | 3 +- httemplate/search/477.html | 2 +- 8 files changed, 38 insertions(+), 107 deletions(-) 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/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!
+my $html_form = qq! + ( show class: !. include('/elements/select-pkg_class.html', #'curr_value' => $classnum, @@ -193,20 +195,13 @@ my $html_form = qq! 1, ). ' ) -

' . - qq!!; +

'; + # restore this only after creating $html_form $cgi->param('classnum', $classnum) if length($classnum); -my $html_foot = qq! - - !; +my $html_foot = ''; my @menubar = ( 'Package definitions' => $p.'browse/part_pkg.cgi' ); @@ -224,3 +219,17 @@ my @menubar = } +<%def .script> + + 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; +} 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 += '
  • VoIP telephone service
  • '; + out += '
  • VoIP telephone service over ' + + media + '
  • '; out += '
  • ' + curr_values['voip_sessions'] + ' sessions allowed
  • '; if ( curr_values['voip_lastmile'] ) { 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 @@
    - <%init> my %opt = @_; my $id = $opt{id} || $opt{field}; 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/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 .header_voip> - + -- cgit v1.2.1 From 4a56e0d606cb6071a5830966687284b277f1fd2d Mon Sep 17 00:00:00 2001 From: Jeremy Davis Date: Mon, 24 Nov 2014 10:31:46 -0500 Subject: #32332 allow recurring packages to be removed from quotes --- FS/FS/Template_Mixin.pm | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/FS/FS/Template_Mixin.pm b/FS/FS/Template_Mixin.pm index 0928ee52f..7018223b4 100644 --- a/FS/FS/Template_Mixin.pm +++ b/FS/FS/Template_Mixin.pm @@ -2740,6 +2740,10 @@ 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), + 'preref_html' => ( $opt{preref_callback} + ? &{ $opt{preref_callback} }( $cust_bill_pkg ) + : '' + ), }; } -- cgit v1.2.1 From 52013c89319153749cd7c485fa4a67a957abcf3f Mon Sep 17 00:00:00 2001 From: Jeremy Davis Date: Mon, 24 Nov 2014 12:50:55 -0500 Subject: Ticket #32336 Unit prices not showing on quotations --- FS/FS/Template_Mixin.pm | 4 ++++ FS/FS/quotation_pkg.pm | 20 ++++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/FS/FS/Template_Mixin.pm b/FS/FS/Template_Mixin.pm index 7018223b4..2fae4643d 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,8 @@ 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/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 -- cgit v1.2.1 From 2167ca890dab7d87e7c253834cb734ffb9d90823 Mon Sep 17 00:00:00 2001 From: Jeremy Davis Date: Mon, 24 Nov 2014 13:25:58 -0500 Subject: Ticket #32230 Message templates not displaying in send email export --- FS/FS/part_export/send_email.pm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/FS/FS/part_export/send_email.pm b/FS/FS/part_export/send_email.pm index 41f04093e..f1c376fcb 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' }) ); -- cgit v1.2.1 From 936eb26609572b66e8dd0b5b9ef4201bacf1d416 Mon Sep 17 00:00:00 2001 From: Jeremy Davis Date: Mon, 24 Nov 2014 13:34:17 -0500 Subject: Ticket #32230 Message templates not displaying in send email export --- FS/FS/part_export/send_email.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FS/FS/part_export/send_email.pm b/FS/FS/part_export/send_email.pm index f1c376fcb..3e5142260 100644 --- a/FS/FS/part_export/send_email.pm +++ b/FS/FS/part_export/send_email.pm @@ -21,7 +21,7 @@ my %template_select = ( map { $_->msgnum, $_->msgname } qsearch({ table => 'msg_template', hashref => { disabled => { 'op' => '!=', - 'value' => 1 }, + 'value' => 1 }}, order_by => 'ORDER BY msgnum ASC' }) ); -- cgit v1.2.1 From 77be6115a8972c8e7917fc9f80e30a6ba571a552 Mon Sep 17 00:00:00 2001 From: Jeremy Davis Date: Tue, 25 Nov 2014 14:02:39 -0500 Subject: Debian packages --- debian/rules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/rules b/debian/rules index dd7da1d16..07d391466 100755 --- a/debian/rules +++ b/debian/rules @@ -195,7 +195,7 @@ install-stamp: build-stamp perl -p -i -e "\ s'${TMP}?''g;\ " ${TMP}-lib/usr/bin/* \ - ${TMP}/usr/local/etc/freeside/handler.pl + ${TMP}-webui/usr/local/etc/freeside/handler.pl #RT Config -- cgit v1.2.1 From 3cda3a85eb0daa5b8b9f4d30cd0896da6ccf4a9b Mon Sep 17 00:00:00 2001 From: Mark Wells Date: Tue, 25 Nov 2014 19:39:23 -0800 Subject: option to credit unused time on suspension as part of suspend reason, #31702 --- FS/FS/Schema.pm | 1 + FS/FS/cust_credit.pm | 21 +- FS/FS/cust_pkg.pm | 24 ++- FS/FS/reason.pm | 13 +- httemplate/edit/credit-cust_bill_pkg.html | 4 +- httemplate/edit/cust_credit.cgi | 2 +- httemplate/edit/process/credit-cust_bill_pkg.html | 26 ++- httemplate/edit/process/cust_credit.cgi | 17 +- httemplate/elements/tr-select-reason.html | 232 ++++++++++------------ httemplate/misc/cancel_cust.html | 2 +- httemplate/misc/cancel_pkg.html | 6 +- httemplate/misc/cust_main-cancel.cgi | 30 +-- httemplate/misc/cust_main-suspend.cgi | 37 ++-- httemplate/misc/process/cancel_pkg.html | 19 +- httemplate/misc/process/elements/reason | 17 ++ httemplate/misc/suspend_cust.html | 2 +- httemplate/view/cust_main.cgi | 6 +- httemplate/view/cust_main/packages/status.html | 2 + 18 files changed, 225 insertions(+), 236 deletions(-) create mode 100644 httemplate/misc/process/elements/reason diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm index 396c86622..715a603f8 100644 --- a/FS/FS/Schema.pm +++ b/FS/FS/Schema.pm @@ -5881,6 +5881,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' => [], 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), +=item reason - can be set to a cancellation reason (see L), 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 @@ -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/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, schema.html from the base documentation. 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/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 ); 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/elements/tr-select-reason.html b/httemplate/elements/tr-select-reason.html index b7a715b42..c5b3d6f6c 100755 --- a/httemplate/elements/tr-select-reason.html +++ b/httemplate/elements/tr-select-reason.html @@ -29,109 +29,119 @@ Example: +% # 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 - - - - - -% my @types = qsearch( 'reason_type', { 'class' => $class } ); -% if (scalar(@types) < 1) { # we should never reach this - - - -% }elsif (scalar(@types) == 1) { - - - - - -% }else{ - - - - - -% } +%# 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
    , but that doesn't fit well into the table + +
    + + +% # container for hints - - <%init> @@ -148,11 +158,7 @@ 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; my $add_access_right; if ($class eq 'C') { @@ -167,41 +173,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 +211,13 @@ if ( $class eq 'S' ) { 'Unsuspend pkg #'.$reason->unsuspend_pkgpart. ' not found.'; } - } 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('
    ', @hints); } - push @hints, ''; # for the "new reason" case - @hints = map {''.$_.''} @hints; } - my $curuser = $FS::CurrentUser::CurrentUser; - diff --git a/httemplate/misc/cancel_cust.html b/httemplate/misc/cancel_cust.html index 3259a03f6..e4bfdba76 100644 --- a/httemplate/misc/cancel_cust.html +++ b/httemplate/misc/cancel_cust.html @@ -50,7 +50,7 @@ STYLE="margin-left:auto; margin-right:auto"> 'field' => 'reasonnum', 'reason_class' => 'C', 'cgi' => $cgi, - 'control_button' => "document.getElementById('confirm_cancel_cust_button')", + 'control_button' => 'confirm_cancel_cust_button', &>
    <% $CustomField->Name %>: -% unless ( $count ) { +% if ( $count == 0 ) { <&|/l&>(no value) -% } elsif ( $count == 1 ) { -% $print_value->( $CustomField, $Values->First ); -% } else { +% } elsif ( $count == 1 ) { +% $print_value->( $CustomField, $Values->First ); +% } else {
      -% while ( my $Value = $Values->Next ) { +% while ( my $Value = $Values->Next ) {
    • -% $print_value->( $CustomField, $Value ); +% $print_value->( $CustomField, $Value );
    • -% } +% }
    -% } +% }
    - <& hidden.html, 'id' => $id, @_ &> -%# <& input-text.html, 'id' => $id, @_ &> # XXX debugging -
      -
    - -% # show some kind of useful summary of the FCC options here + <& input-fcc_options.html, 'id' => $id, @_ &>
    StateState VoIP OTT VoIP Non-OTT
    <% mt('Reason') |h %> - -
    -

    <% mt('No reason types. Please add some.') |h %>

    -
    -

    <% mt('Reason Type') |h %>

    -
    -

    <% $types[0]->type %> - -

    -

    <% mt('Reason Type') |h %>

    -
    - -
    + + + <& 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 + + + +% } 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-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', + &> % } +
    +

    <% mt('No reason types. Please add some.') |h %>

    +
    -
    +
    -

    <% mt('New Reason') |h %>

    +
    " style="display:<% $display %>">
    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 @@ -

    +
    <% emt(ucfirst($method)." [_1]", $part_pkg->pkg_comment(cust_pkg=>$cust_pkg) ) %> -<% ntable("#cccccc", 2) %> + % 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/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/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); + diff --git a/httemplate/misc/suspend_cust.html b/httemplate/misc/suspend_cust.html index e0d17f30b..3a49e136d 100644 --- a/httemplate/misc/suspend_cust.html +++ b/httemplate/misc/suspend_cust.html @@ -42,7 +42,7 @@ STYLE="margin-left:auto; margin-right:auto"> 'field' => 'reasonnum', 'reason_class' => 'S', 'cgi' => $cgi, - 'control_button' => "document.getElementById('confirm_suspend_cust_button')", + 'control_button' => 'confirm_suspend_cust_button', &>
    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/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, ) } -- cgit v1.2.1 From 79df9da17cf54366ef027a941c131cb1a016068e Mon Sep 17 00:00:00 2001 From: Mark Wells Date: Wed, 26 Nov 2014 14:54:02 -0800 Subject: Thinktel CDR format and download script, #32088 --- FS/FS/cdr/thinktel.pm | 42 ++++++++++++++++ bin/cdr-thinktel.import | 126 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 168 insertions(+) create mode 100644 FS/FS/cdr/thinktel.pm create mode 100644 bin/cdr-thinktel.import 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/bin/cdr-thinktel.import b/bin/cdr-thinktel.import new file mode 100644 index 000000000..ccbd78c39 --- /dev/null +++ b/bin/cdr-thinktel.import @@ -0,0 +1,126 @@ +#!/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 = $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 + +"; +} + -- cgit v1.2.1 From 0f0bc1ef7aafc6b3869c0f71ee2528c1c9897ce6 Mon Sep 17 00:00:00 2001 From: Mark Wells Date: Wed, 26 Nov 2014 16:55:41 -0800 Subject: don't look up or display census tracts for non-U.S. addresses, #32249 --- FS/FS/Misc/Geo.pm | 4 ++++ httemplate/edit/cust_main/bottomfixup.js | 11 +++++++---- httemplate/elements/standardize_locations.js | 13 +++++++++---- httemplate/elements/tr-censustract.html | 2 +- httemplate/misc/confirm-censustract.html | 7 +++++++ httemplate/view/cust_main/locations.html | 22 ++++++++++++---------- httemplate/view/cust_main/packages/location.html | 22 ++++++++++++---------- 7 files changed, 52 insertions(+), 29 deletions(-) diff --git a/FS/FS/Misc/Geo.pm b/FS/FS/Misc/Geo.pm index e41ba5d76..a387aca9d 100644 --- a/FS/FS/Misc/Geo.pm +++ b/FS/FS/Misc/Geo.pm @@ -42,6 +42,10 @@ sub get_censustract_ffiec { my $location = shift; my $year = shift; + if ( length($location->{country}) and uc($location->{country}) ne 'US' ) { + return ''; + } + warn Dumper($location, $year) if $DEBUG; my $url = 'http://www.ffiec.gov/Geocode/default.aspx'; diff --git a/httemplate/edit/cust_main/bottomfixup.js b/httemplate/edit/cust_main/bottomfixup.js index 5a8835f53..8aef1e72c 100644 --- a/httemplate/edit/cust_main/bottomfixup.js +++ b/httemplate/edit/cust_main/bottomfixup.js @@ -116,10 +116,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/elements/standardize_locations.js b/httemplate/elements/standardize_locations.js index 817a2e357..a4f13d78d 100644 --- a/httemplate/elements/standardize_locations.js +++ b/httemplate/elements/standardize_locations.js @@ -290,10 +290,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) { <% mt('Census tract') |h %> 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; +% }

    % if ( $error ) { Census tract error 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 {
    <% $loc->location_label %> -% if ( $loc->censustract ) { -
    - - <% $loc->censustract %> (<% $loc->censusyear %> census) - -% } elsif ( $conf->exists('cust_main-require_censustract') ) { -
    - - <% emt('Census tract unknown') %> - +% if ( $loc->country eq 'US' ) { # only U.S. census tracts for now +% if ( $loc->censustract ) { +
    + + <% $loc->censustract %> (<% $loc->censusyear %> census) + +% } elsif ( $conf->exists('cust_main-require_censustract') ) { +
    + + <% emt('Census tract unknown') %> + +% } % }
    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 @@ &> % } -% if ( $loc->censustract ) { -
    - - <% $loc->censustract %> (<% $loc->censusyear %> census) - -% } elsif ( $opt{'cust_main-require_censustract'} ) { -
    - - <% emt('Census tract unknown') %> - +% if ( $loc->country eq 'US' ) { +% if ( $loc->censustract ) { +
    + + <% $loc->censustract %> (<% $loc->censusyear %> census) + +% } elsif ( $opt{'cust_main-require_censustract'} ) { +
    + + <% emt('Census tract unknown') %> + +% } % } % if ( $default ) { -- cgit v1.2.1 From 1af8ff7f48f7259fc99f090c301c84b9680fdb4d Mon Sep 17 00:00:00 2001 From: Mark Wells Date: Thu, 27 Nov 2014 15:21:29 -0800 Subject: svc_circuit, #23879, #25933, #30830 --- FS/FS/AccessRight.pm | 1 + FS/FS/Mason.pm | 4 + FS/FS/Schema.pm | 74 ++++++++ FS/FS/UI/Web.pm | 19 +- FS/FS/addr_block.pm | 18 ++ FS/FS/circuit_provider.pm | 101 ++++++++++ FS/FS/circuit_termination.pm | 98 ++++++++++ FS/FS/circuit_type.pm | 98 ++++++++++ FS/FS/router.pm | 7 + FS/FS/svc_circuit.pm | 230 +++++++++++++++++++++++ FS/FS/svc_phone.pm | 35 ++++ FS/MANIFEST | 8 + FS/t/circuit_provider.t | 5 + FS/t/circuit_termination.t | 5 + FS/t/circuit_type.t | 5 + FS/t/svc_circuit.t | 5 + httemplate/browse/circuit_provider.html | 11 ++ httemplate/browse/circuit_termination.html | 11 ++ httemplate/browse/circuit_type.html | 11 ++ httemplate/browse/elements/browse-simple.html | 57 ++++++ httemplate/docs/part_svc-table.html | 1 + httemplate/edit/circuit_provider.html | 21 +++ httemplate/edit/circuit_termination.html | 21 +++ httemplate/edit/circuit_type.html | 21 +++ httemplate/edit/elements/part_svc_column.html | 5 +- httemplate/edit/elements/svc_Common.html | 46 ++++- httemplate/edit/process/circuit_provider.html | 11 ++ httemplate/edit/process/circuit_termination.html | 11 ++ httemplate/edit/process/circuit_type.html | 11 ++ httemplate/edit/process/elements/svc_Common.html | 2 +- httemplate/edit/process/svc_circuit.html | 11 ++ httemplate/edit/svc_circuit.cgi | 54 ++++++ httemplate/edit/svc_phone.cgi | 36 ++-- httemplate/elements/menu.html | 8 + httemplate/elements/tr-select-svc_circuit.html | 41 ++++ httemplate/search/svc_circuit.cgi | 65 +++++++ httemplate/view/elements/svc_Common.html | 163 +++++++++++----- httemplate/view/svc_circuit.html | 80 ++++++++ httemplate/view/svc_phone.cgi | 7 +- 39 files changed, 1341 insertions(+), 77 deletions(-) create mode 100644 FS/FS/circuit_provider.pm create mode 100644 FS/FS/circuit_termination.pm create mode 100644 FS/FS/circuit_type.pm create mode 100644 FS/FS/svc_circuit.pm create mode 100644 FS/t/circuit_provider.t create mode 100644 FS/t/circuit_termination.t create mode 100644 FS/t/circuit_type.t create mode 100644 FS/t/svc_circuit.t create mode 100644 httemplate/browse/circuit_provider.html create mode 100644 httemplate/browse/circuit_termination.html create mode 100644 httemplate/browse/circuit_type.html create mode 100644 httemplate/browse/elements/browse-simple.html create mode 100644 httemplate/edit/circuit_provider.html create mode 100644 httemplate/edit/circuit_termination.html create mode 100644 httemplate/edit/circuit_type.html create mode 100644 httemplate/edit/process/circuit_provider.html create mode 100644 httemplate/edit/process/circuit_termination.html create mode 100644 httemplate/edit/process/circuit_type.html create mode 100644 httemplate/edit/process/svc_circuit.html create mode 100644 httemplate/edit/svc_circuit.cgi create mode 100644 httemplate/elements/tr-select-svc_circuit.html create mode 100755 httemplate/search/svc_circuit.cgi create mode 100644 httemplate/view/svc_circuit.html 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/Schema.pm b/FS/FS/Schema.pm index 715a603f8..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' ], + }, ], }, @@ -6527,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/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!! 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/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::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 + +=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 + +=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 + +=cut + +1; + 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 + +=item typenum - circuit type (such as DS1, DS1-PRI, DS3, OC3, etc.); foreign +key to L. + +=item providernum - circuit provider (telco); foreign key to +L. + +=item termnum - circuit termination type; foreign key to +L + +=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 + +=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 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 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/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', +&> + + +<%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", +) . '
    '; + + 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 @@
  • svc_broadband: Wireless broadband
  • svc_cable: Cable
  • svc_dish: DISH Network +
  • svc_circuit: Phone circuits other than DSL 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' +); + 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' +); + 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' +); + 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. % } % # 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'; 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/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'); + + 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'); + + 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'); + + 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/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? + + 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); + + 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*'); + } +}; diff --git a/httemplate/elements/menu.html b/httemplate/elements/menu.html index f26882b1f..b4ecdc490 100644 --- a/httemplate/elements/menu.html +++ b/httemplate/elements/menu.html @@ -552,6 +552,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' ]; @@ -572,6 +578,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/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 + +% } 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", + ); +} + + 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' ]; + + 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; } + % if ( $custnum ) { @@ -67,61 +76,20 @@ function areyousure(href) { <% ntable("#cccccc") %><% 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 .= ' ('. $class->classname . ')' if $class; -% } -% unless ($columndef->columnflag eq 'F' && !length($columndef->columnvalue)) { - +% my ($field, $label, $value) = &{ $format_field }($f); +% next if !$field; - <% ( $opt{labels} && exists $opt{labels}->{$field} ) - ? $opt{labels}->{$field} - : $field - %> + <% $label %> -% $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 + <& hidden.html, field => $pre.'locationname', value => $object->get('locationname') &> % } @@ -102,10 +97,7 @@ Example: % } else { # alternate format - +<& hidden.html, field => $pre.'address2', value => $object->get('address2') &> <<%$th%> ALIGN="right">Unit type and #> @@ -227,14 +219,14 @@ Example: % } else { % foreach (qw(latitude longitude)) { - +<& hidden.html, field => $pre.$_, value => $object->get($_) &> % } % } - - - - - +% +% foreach (qw(coord_auto geocode censustract censusyear)) { + <& hidden.html, field => $pre.$_, value => $object->get($_) &> +% } +% % if ( $opt{enable_censustract} ) { Census tract @@ -259,7 +251,7 @@ Example: % } else { - + <& 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) ) { - +<& hidden.html, field => 'old_'.$pre.$_, value => $object->get($_) &> % } %# Placeholders - - +<& hidden.html, field => $pre.'cachenum', value => '' &> +<& hidden.html, field => $pre.'addr_clean', value => '' &> diff --git a/httemplate/elements/standardize_locations.js b/httemplate/elements/standardize_locations.js index a4f13d78d..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; 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'}; } -- cgit v1.2.1 From 06b7b4024abdd67573dcceb896f3e982d85eaffe Mon Sep 17 00:00:00 2001 From: Mark Wells Date: Tue, 2 Dec 2014 20:08:00 -0800 Subject: fix censustract lookup for new FFIEC interface, #32459 --- FS/FS/Misc/Geo.pm | 127 ++++++++++++++++++------------------------------------ 1 file changed, 41 insertions(+), 86 deletions(-) diff --git a/FS/FS/Misc/Geo.pm b/FS/FS/Misc/Geo.pm index a387aca9d..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) and a census map year, returns a census tract code (consisting of state, county, and tract @@ -41,6 +40,7 @@ sub get_censustract_ffiec { my $class = shift; my $location = shift; my $year = shift; + $year ||= 2013; if ( length($location->{country}) and uc($location->{country}) ne 'US' ) { return ''; @@ -48,102 +48,57 @@ sub get_censustract_ffiec { warn Dumper($location, $year) if $DEBUG; - my $url = 'http://www.ffiec.gov/Geocode/default.aspx'; - - 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 ) { - - $error = "either no __VIEWSTATE or __EVENTVALIDATION found"; + die "Census tract lookup error: ".$res->message; - } 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 { -- cgit v1.2.1 From 7c8aa3f08b006c9772889d95c5dcef0a6a69bf17 Mon Sep 17 00:00:00 2001 From: Jeremy Davis Date: Wed, 3 Dec 2014 12:41:12 -0500 Subject: Ticket #31495 Earthlink CDR --- FS/FS/cdr/earthlink.pm | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 FS/FS/cdr/earthlink.pm 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; + -- cgit v1.2.1 From bf50a8356a7344b4f75c7bc7f952019b98867f26 Mon Sep 17 00:00:00 2001 From: Jeremy Davis Date: Thu, 4 Dec 2014 09:51:07 -0500 Subject: Ticket #32088 Thinktel integration --- bin/cdr-thinktel.import | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) mode change 100644 => 100755 bin/cdr-thinktel.import diff --git a/bin/cdr-thinktel.import b/bin/cdr-thinktel.import old mode 100644 new mode 100755 index ccbd78c39..9afd34cc5 --- a/bin/cdr-thinktel.import +++ b/bin/cdr-thinktel.import @@ -52,7 +52,8 @@ $ftp->login($login, $password) warn "Retrieving directory listing\n" if $opt_v; $ftp->cwd('/'); -my @files = $ftp->ls(); +my @files = grep { $_ =~ /MetaSwitch/ } $ftp->ls(); + warn scalar(@files)." CDR files found.\n" if $opt_v; # apply date range if ( $opt_a ) { -- cgit v1.2.1