diff options
Diffstat (limited to 'httemplate')
211 files changed, 3698 insertions, 1175 deletions
diff --git a/httemplate/REST/1.0/phone_avail b/httemplate/REST/1.0/phone_avail index ef9d3e7f0..c6638f48f 100644 --- a/httemplate/REST/1.0/phone_avail +++ b/httemplate/REST/1.0/phone_avail @@ -17,7 +17,8 @@ if ( scalar(@exports) > 1 ) { die "no DID providing export attached to svcpart $svcpart"; } my $export = $exports[0]; - + +# XXX no error handling my $phonenums = $export->get_dids( map { $_ => scalar($cgi->param($_)) } qw( ratecenter state areacode exchange ) ); diff --git a/httemplate/browse/commission_schedule.html b/httemplate/browse/commission_schedule.html new file mode 100644 index 000000000..5a4f9840e --- /dev/null +++ b/httemplate/browse/commission_schedule.html @@ -0,0 +1,70 @@ +<& elements/browse.html, + 'title' => "Commission schedules", + 'name' => "commission schedules", + 'menubar' => [ 'Add a new schedule' => + $p.'edit/commission_schedule.html' + ], + 'query' => { 'table' => 'commission_schedule', }, + 'count_query' => 'SELECT COUNT(*) FROM commission_schedule', + 'header' => [ '#', + 'Name', + 'Rates', + ], + 'fields' => [ 'schedulenum', + 'schedulename', + $rates_sub, + ], + 'links' => [ $link, + $link, + '', + ], + 'disable_total' => 1, +&> +<%init> + +my $money_char = FS::Conf->new->config('money_char') || '$'; + +my $ordinal_sub = sub { + # correct from 1 to 12... + my $num = shift; + $num == 1 ? '1st' : + $num == 2 ? '2nd' : + $num == 3 ? '3rd' : + $num . 'th' +}; + +my $rates_sub = sub { + my $schedule = shift; + my @rates = sort { $a->cycle <=> $b->cycle } $schedule->commission_rate; + my @data; + my $basis = emt(lc( $FS::commission_schedule::basis_options{$schedule->basis} )); + foreach my $rate (@rates) { + my $desc = ''; + if ( $rate->amount > 0 ) { + $desc = $money_char . sprintf('%.2f', $rate->amount); + } + if ( $rate->percent > 0 ) { + $desc .= ' + ' if $desc; + $desc .= $rate->percent . '% ' . emt('of') . ' ' . $basis; + } + next if !$desc; + $desc = &$ordinal_sub($rate->cycle) . ' ' . emt('invoice') . + ': ' . $desc; + + push @data, + [ + { + 'data' => $desc, + 'align' => 'right', + } + ]; + } + \@data; +}; + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); + +my $link = [ $p.'edit/commission_schedule.html?', 'schedulenum' ]; + +</%init> diff --git a/httemplate/browse/log_email.html b/httemplate/browse/log_email.html index 0f64dd454..007ea6f74 100644 --- a/httemplate/browse/log_email.html +++ b/httemplate/browse/log_email.html @@ -21,7 +21,7 @@ ], 'fields' => [ 'logemailnum', sub { $_[0]->context || '(all)' }, - sub { $FS::Log::LEVELS[$_[0]->min_level] }, + sub { $FS::Log::LEVELS{$_[0]->min_level} }, 'msgname', 'to_addr', $actions, diff --git a/httemplate/browse/part_pkg_taxproduct/cch.html b/httemplate/browse/part_pkg_taxproduct/cch.html index b901bad9f..1f4f53d5f 100755 --- a/httemplate/browse/part_pkg_taxproduct/cch.html +++ b/httemplate/browse/part_pkg_taxproduct/cch.html @@ -97,7 +97,7 @@ $cgi->delete('tax_customer'); if ( $tax_group || $tax_item || $tax_provider || $tax_customer ) { - push @menubar, 'View all tax products' => $p.'browse/part_pkg_taxproduct.cgi'; + push @menubar, 'View all tax products' => $p.'browse/part_pkg_taxproduct/cch.html'; } $cgi->param('dummy', 1); diff --git a/httemplate/browse/part_pkg_taxproduct/suretax.html b/httemplate/browse/part_pkg_taxproduct/suretax.html index 667c07ee9..9c00c5c69 100755 --- a/httemplate/browse/part_pkg_taxproduct/suretax.html +++ b/httemplate/browse/part_pkg_taxproduct/suretax.html @@ -16,9 +16,6 @@ 'nohtmlheader' => 1, 'disable_total' => 1, &> -<style> -input { float: right} -</style> <script src="<% $fsurl %>elements/jquery.js"></script> <script> var category_labels = <% encode_json(\%category_labels) %>; @@ -69,20 +66,31 @@ function select_taxproduct(taxproductnum, description) { } </script> -<DIV STYLE="width: 50%"> +<BR> <FORM NAME="myform"> - <label for="new_taxproduct">New tax product code</label> - <input type="text" size="6" name="new_taxproduct" id="new_taxproduct"> - <br> - <label for="new_category_desc">Category</label> - <input type="text" name="new_category_desc" id="new_category_desc" disabled=1> - <br> - <label for="new_taxproduct_desc">Product</label> - <input type="text" name="new_taxproduct_desc" id="new_taxproduct_desc"> - <br> + <FONT SIZE="+1"><B><% emt('Add tax product') %></B></FONT> + <% ntable('#cccccc', 2) %> + <& /elements/tr-input-text.html, + 'label' => emt('Product code'), + 'field' => 'new_taxproduct', + 'id' => 'new_taxproduct', + 'size' => 6, + 'maxlength' => 6, + &> + <& /elements/tr-input-text.html, + 'label' => emt('Category'), + 'field' => 'new_category_desc', + 'id' => 'new_category_desc', + 'disabled' => 1 + &> + <& /elements/tr-input-text.html, + 'label' => emt('Product'), + 'field' => 'new_taxproduct_desc', + 'id' => 'new_taxproduct_desc', + &> + </table> <input type="button" id="new_taxproduct_submit" disabled=1 value="Add"> </FORM> -</DIV> <%shared> # populate dropdown diff --git a/httemplate/browse/part_svc.cgi b/httemplate/browse/part_svc.cgi index 88f8d8d19..b9474636d 100755 --- a/httemplate/browse/part_svc.cgi +++ b/httemplate/browse/part_svc.cgi @@ -112,16 +112,36 @@ function part_export_areyousure(href) { </TD> % } - <TD ROWSPAN=<% $rowspan %> CLASS="grid" BGCOLOR="<% $bgcolor %>"><A HREF="<% $url %>"> - <% $part_svc->svc %></A></TD> + <TD ROWSPAN=<% $rowspan %> CLASS="grid" BGCOLOR="<% $bgcolor %>"> + <A HREF="<% $url %>"> + <% $part_svc->svc %> + </A> +% # any alternate names of the service +% my %msgcat = map { $_->locale => $_ } $part_svc->part_svc_msgcat; +% my %labels = map { $_ => FS::Locales->description($_) } keys %msgcat; +% my @locales = sort { $labels{$a} cmp $labels{$b} } keys %msgcat; +% if ( @locales ) { + <BR> + <FONT SIZE="-1"> +% foreach my $locale (@locales) { + <% $labels{$locale} %>: <% $msgcat{$locale}->get('svc') %> + <BR> +% } + </FONT> +% } + </TD> <TD ROWSPAN=<% $rowspan %> CLASS="grid" BGCOLOR="<% $bgcolor %>"> <% $svcdb %></TD> <TD ROWSPAN=<% $rowspan %> CLASS="grid" BGCOLOR="<% $bgcolor %>"> - <FONT COLOR="#00CC00"><B><% $num_active_cust_svc{$part_svc->svcpart} %></B></FONT> <% $num_active_cust_svc{$part_svc->svcpart} ? svc_url( 'ahref' => 1, 'm' => $m, 'action' => 'search', 'part_svc' => $part_svc, 'query' => "svcpart=". $part_svc->svcpart ) : '<A NAME="zero">' %>active</A> - -% if ( $num_active_cust_svc{$part_svc->svcpart} ) { +% my $svcurl_active = svc_url( 'ahref' => 1, 'm' => $m, 'action' => 'search', 'part_svc' => $part_svc, 'query' => "svcpart=". $part_svc->svcpart . "&cancelled=0"); +% my $svcurl_cancel = svc_url( 'ahref' => 1, 'm' => $m, 'action' => 'search', 'part_svc' => $part_svc, 'query' => "svcpart=". $part_svc->svcpart . "&cancelled=1"); + <FONT COLOR="#00CC00"><B><% $num_cust_svc_active{$part_svc->svcpart} %></B></FONT> <% $num_cust_svc_active{$part_svc->svcpart} ? $svcurl_active : '' %>active<% $num_cust_svc_active{$part_svc->svcpart} ? '</A>' : '' %> +% if ( $num_cust_svc_cancelled{$part_svc->svcpart} ) { + <BR><FONT COLOR="#FF0000"><B><% $num_cust_svc_cancelled{$part_svc->svcpart} %></B></FONT> <% $svcurl_cancel %>cancelled</A> +% } +% if ( $num_cust_svc{$part_svc->svcpart} ) { <BR><FONT SIZE="-1">[ <A HREF="<%$p%>edit/bulk-cust_svc.html?svcpart=<% $part_svc->svcpart %>">change</A> ]</FONT> % } @@ -245,11 +265,25 @@ my @part_svc = qsearch('part_svc', \%search ); my $total = scalar(@part_svc); -my %num_active_cust_svc = map { $_->svcpart => $_->num_cust_svc } @part_svc; +## The Active/Cancelled distinction is a bit awkward, +## active currently includes unattached and suspended services, +## but we've previously referred to EVERY existing cust_svc as "Active", +## and we don't really want to know numbers by individual package status, +## so for now the UI will distinguish these as "Active" and "Cancelled", +## but please let's not go so far as to introduce the idea of "Service Status" + +my %num_cust_svc_active; +my %num_cust_svc_cancelled; +my %num_cust_svc; +foreach my $part_svc (@part_svc) { + $num_cust_svc{$part_svc->svcpart} = $part_svc->num_cust_svc; + $num_cust_svc_cancelled{$part_svc->svcpart} = $part_svc->num_cust_svc_cancelled; + $num_cust_svc_active{$part_svc->svcpart} = $num_cust_svc{$part_svc->svcpart} - $num_cust_svc_cancelled{$part_svc->svcpart}; +} if ( $cgi->param('orderby') eq 'active' ) { - @part_svc = sort { $num_active_cust_svc{$b->svcpart} <=> - $num_active_cust_svc{$a->svcpart} } @part_svc; + @part_svc = sort { $num_cust_svc{$b->svcpart} <=> + $num_cust_svc{$a->svcpart} } @part_svc; } elsif ( $cgi->param('orderby') eq 'svc' ) { @part_svc = sort { lc($a->svc) cmp lc($b->svc) } @part_svc; } diff --git a/httemplate/browse/tower.html b/httemplate/browse/tower.html index e2f9fd0bd..16e44c6c0 100644 --- a/httemplate/browse/tower.html +++ b/httemplate/browse/tower.html @@ -1,22 +1,25 @@ -<% include( 'elements/browse.html', - 'title' => 'Towers', - 'name' => 'towers', - 'menubar' => [ 'Add a new tower' => - $p.'edit/tower.html', - ], - 'query' => { 'table' => 'tower', }, - 'count_query' => 'SELECT COUNT(*) FROM tower', - 'disableable' => 1, - 'disabled_statuspos' => 1, - 'header' => [ 'Name', 'Location', 'Sectors', ], - 'fields' => [ $tower_sub, - $coord_sub, - $sector_sub, - ], - 'links' => [ ], - 'cell_style' => [ $tagdesc_style ], - ) -%> +<& elements/browse.html, + 'title' => 'Towers', + 'name' => 'towers', + 'menubar' => [ 'Add a new tower' => + $p.'edit/tower.html', + 'Sector coverage maps' => + $p.'search/sector.html', + 'Download CSV for towercoverage.com' => + $p.'misc/tower-export.html?format=tc' + ], + 'query' => { 'table' => 'tower', }, + 'count_query' => 'SELECT COUNT(*) FROM tower', + 'disableable' => 1, + 'disabled_statuspos' => 1, + 'header' => [ 'Name', 'Location', 'Sectors', ], + 'fields' => [ $tower_sub, + $coord_sub, + $sector_sub, + ], + 'links' => [ ], + 'cell_style' => [ $tagdesc_style ], +&> <%init> die "access denied" diff --git a/httemplate/config/config-view.cgi b/httemplate/config/config-view.cgi index a2e908847..248af37cb 100644 --- a/httemplate/config/config-view.cgi +++ b/httemplate/config/config-view.cgi @@ -7,7 +7,7 @@ Click on a configuration value to change it. % % if ( $cgi->param('showagent') ) { % $cgi->param('showagent', 0); - ( <a href="<% $cgi->self_url %>">hide agent overrides</a> ) + ( <a href="<% $cgi->self_url %>">show global configuration</a> ) % $cgi->param('showagent', 1); % } else { % $cgi->param('showagent', 1); @@ -58,24 +58,28 @@ invoice language options: % foreach my $section (@sections) { - <A NAME="<% $section || 'unclassified' %>"></A> + <A NAME="<% $section || 'misc' %>"></A> <FONT SIZE="-2"> % foreach my $nav_section (@sections) { % % if ( $section eq $nav_section ) { - [<A NAME="not<% $nav_section || 'unclassified' %>" style="background-color: #cccccc"><% ucfirst($nav_section || 'unclassified') %></A>] + <A NAME="not<% $nav_section || 'misc' %>" style="background-color: #cccccc"><% section_title($nav_section) %></A> % } else { - [<A HREF="#<% $nav_section || 'unclassified' %>"><% ucfirst($nav_section || 'unclassified') %></A>] + <A HREF="#<% $nav_section || 'misc' %>"><% section_title($nav_section) %></A> % } % +% unless ( $nav_section eq $sections[-1] ) { + | +% } +% % } </FONT><BR> <TABLE BGCOLOR="#cccccc" BORDER=1 CELLSPACING=0 CELLPADDING=0 BORDERCOLOR="#999999"> <tr> <th colspan="2" bgcolor="#dcdcdc"> - <% ucfirst($section || 'unclassified') %> + <% section_title($section) %> % if ( $curuser->option('show_confitem_counts') ) { (<% scalar( @{ $section_items{$section} } ) %> items) % } @@ -114,7 +118,10 @@ invoice language options: % foreach my $agent ( @agents ) { % my $agentnum = $agent ? $agent->agentnum : ''; % -% next if $section eq 'deprecated' && ! $conf->exists($i->key, $agentnum); +% next if $section eq 'deprecated' +% && ( ! $conf->exists($i->key, $agentnum) +% || $conf->config($i->key, $agentnum) eq '' +% ); % % my $label = $i->key; % $label = '['. $agent->agent. "] $label" @@ -125,16 +132,20 @@ invoice language options: % ";agentnum=$agentnum" . ($locale ? ";locale=$locale" : ''); <tr> - <td><% include('/elements/popup_link.html', - 'action' => $action, - 'width' => $width, - 'height' => $height, - 'actionlabel' => 'Enter configuration value', - 'label' => "<b>$label</b>", - 'aname' => $i->key, #agentnum - # if $cgi->param('showagent')? +% unless ( $cgi->param('showagent') ) { + <td><% include('/elements/popup_link.html', + 'action' => $action, + 'width' => $width, + 'height' => $height, + 'actionlabel' => 'Enter configuration value', + 'label' => "<b>$label</b>", + 'aname' => $i->key, ) - %>: <% $i->description %> + %>: +% } else { + <td><b><% $label %></b>: +% } + <% $i->description %> % if ( $agent && $cgi->param('showagent') ) { % my $confnum = $conf->conf( $i->key, $agent->agentnum, 1 )->confnum; (<A HREF="javascript:areyousure('delete this agent override', 'config-delete.cgi?confnum=<% $confnum %>;redirect=config_view_showagent')">delete agent override</A>) @@ -292,7 +303,7 @@ invoice language options: % if ( @add_agents ) { <tr> - <td> + <td COLSPAN=2> <FORM> Add <b><% $i->key %></b> override for <% include('/elements/select-agent.html', @@ -403,23 +414,36 @@ if ( $cgi->param('locale') =~ /^\w+_\w+$/ ) { } elsif ($page_agent) { $title = 'Agent Configuration for '. $page_agent->agent; $title .= ", $locale_desc" if $locale; +} elsif ( $cgi->param('showagent') ) { + $title = 'Agent Configuration Overrides' } else { $title = 'Global Configuration'; } +my $show_over = $page_agent || $cgi->param('showagent'); + my @config_items = grep { !defined($locale) or $_->per_locale } - grep { $page_agent ? $_->per_agent : 1 } - grep { $page_agent ? 1 : !$_->agentonly } + grep { $show_over ? $_->per_agent : 1 } + grep { $show_over ? 1 : !$_->agentonly } $conf->config_items; my @deleteable = qw( invoice_latexreturnaddress invoice_htmlreturnaddress ); my %deleteable = map { $_ => 1 } @deleteable; my @sections = (qw( - required billing taxation invoicing notification UI API self-service - ticketing network_monitoring username password session shell BIND - telephony - ), '', 'deprecated' + important + billing payments payment_batching credit_cards e-checks taxation + packages suspension cancellation + printing print_services + invoicing invoice_email invoice_balances invoice_templates quotations + notification UI addresses customer_number customer_fields reporting + localization scalability backup + signup self-service self-service_skinning + API ticketing appointments network_monitoring + services + username password + telephony RADIUS wireless_broadband shell BIND hosting + ), '', qw( development deprecated ) ); my %section_items = (); @@ -434,4 +458,10 @@ if ( $cgi->param('showagent') ) { @all_agents = qsearch('agent', { 'disabled' => '' } ); } +sub section_title { + my $sec = ucfirst(shift || 'misc'); + $sec =~ s/_/ /; + $sec; +} + </%init> diff --git a/httemplate/docs/about.html b/httemplate/docs/about.html index 4674af878..508648a19 100644 --- a/httemplate/docs/about.html +++ b/httemplate/docs/about.html @@ -56,7 +56,7 @@ GNU <b>Affero</b> General Public License.<BR> % unless ( $agentnum ) { <CENTER> - <FONT SIZE="-3">"Half the world's a desert / Cannibals eat human brains for dessert" - D. Zero</FONT> + <FONT SIZE="-3">"I've heard it too many times to ignore it / Its's something that I'm supposed to be" - K. Frog</FONT> </CENTER> % } diff --git a/httemplate/docs/credits.html b/httemplate/docs/credits.html index 87cb08d7e..596c627e4 100644 --- a/httemplate/docs/credits.html +++ b/httemplate/docs/credits.html @@ -29,7 +29,6 @@ <BR> <H3>Core Team</H3> -Jeremy Davis<BR> Nathan Kennedy<BR> Ivan Kohler<BR> Jonathan Prykop<BR> @@ -41,6 +40,7 @@ Mark Wells<BR> <H3>Core Emeritus</H3> Peter Bowen<BR> +Jeremy Davis<BR> Jeff Finucane<BR> Jason Hall<BR> Kristian Hoffman<BR> diff --git a/httemplate/edit/commission_schedule.html b/httemplate/edit/commission_schedule.html new file mode 100644 index 000000000..c76a3618e --- /dev/null +++ b/httemplate/edit/commission_schedule.html @@ -0,0 +1,53 @@ +<& elements/edit.html, + name_singular => 'schedule', + table => 'commission_schedule', + viewall_dir => 'browse', + fields => [ 'schedulename', + { field => 'reasonnum', + type => 'select-reason', + reason_class => 'R', + }, + { field => 'basis', + type => 'select', + options => [ keys %FS::commission_schedule::basis_options ], + labels => { %FS::commission_schedule::basis_options }, + }, + { type => 'tablebreak-tr-title', value => 'Billing cycles' }, + { field => 'commissionratenum', + type => 'commission_rate', + o2m_table => 'commission_rate', + m2_label => ' ', + m2_error_callback => $m2_error_callback, + colspan => 2, + }, + ], + labels => { 'schedulenum' => '', + 'schedulename' => 'Name', + 'basis' => 'Based on', + 'commissionratenum' => '', + }, +&> +<%init> + +my $m2_error_callback = sub { + my ($cgi, $object) = @_; + + my @rates; + foreach my $k ( grep /^commissionratenum\d+/, $cgi->param ) { + my $num = $cgi->param($k); + my $cycle = $cgi->param($k.'_cycle'); + my $amount = $cgi->param($k.'_amount'); + my $percent = $cgi->param($k.'_percent'); + if ($cycle > 0) { + push @rates, FS::commission_rate->new({ + 'commissionratenum' => $num, + 'cycle' => $cycle, + 'amount' => $amount, + 'percent' => $percent, + }); + } + } + @rates; +}; + +</%init> diff --git a/httemplate/edit/cust_main.cgi b/httemplate/edit/cust_main.cgi index 39cddc021..b314d2d6e 100755 --- a/httemplate/edit/cust_main.cgi +++ b/httemplate/edit/cust_main.cgi @@ -203,12 +203,19 @@ if ( $cgi->param('error') ) { my %locations; for my $pre (qw(bill ship)) { my %hash; - foreach ( FS::cust_main->location_fields ) { - $hash{$_} = scalar($cgi->param($pre.'_'.$_)); + foreach my $locfield ( FS::cust_main->location_fields ) { + # don't search on lat/long, string values can cause qsearchs to die + next if grep {$_ eq $locfield} qw(latitude longitude); + $hash{$locfield} = scalar($cgi->param($pre.'_'.$locfield)); } $hash{'custnum'} = $cgi->param('custnum'); $locations{$pre} = qsearchs('cust_location', \%hash) || FS::cust_location->new( \%hash ); + # now set lat/long, for redisplay of entered values + foreach my $locfield ( qw(latitude longitude) ) { + my $locvalue = scalar($cgi->param($pre.'_'.$locfield)); + $locations{$pre}->set($locfield,$locvalue); + } } if ( $same ) { $locations{ship} = $locations{bill}; diff --git a/httemplate/edit/cust_pay.cgi b/httemplate/edit/cust_pay.cgi index 5d74365e7..116eeebd6 100755 --- a/httemplate/edit/cust_pay.cgi +++ b/httemplate/edit/cust_pay.cgi @@ -12,7 +12,7 @@ <% small_custview($custnum, $conf->config('countrydefault')) %> % } -<FORM NAME="PaymentForm" ACTION="<% popurl(1) %>process/cust_pay.cgi" METHOD=POST onSubmit="document.PaymentForm.submit.disabled=true"> +<FORM NAME="PaymentForm" ACTION="<% popurl(1) %>process/cust_pay.cgi" METHOD=POST onSubmit="document.PaymentForm.submitButton.disabled=true"> <INPUT TYPE="hidden" NAME="link" VALUE="<% $link %>"> <INPUT TYPE="hidden" NAME="linknum" VALUE="<% $linknum %>"> <INPUT TYPE="hidden" NAME="payby" VALUE="<% $payby %>"> @@ -114,7 +114,7 @@ </TABLE> <BR> -<INPUT TYPE="submit" VALUE="<% mt('Post payment') |h %>"> +<INPUT NAME="submitButton" TYPE="submit" VALUE="<% mt('Post payment') |h %>"> </FORM> diff --git a/httemplate/edit/cust_pay_pending.html b/httemplate/edit/cust_pay_pending.html index 0056bb925..7d480f319 100644 --- a/httemplate/edit/cust_pay_pending.html +++ b/httemplate/edit/cust_pay_pending.html @@ -4,6 +4,10 @@ <CENTER><FONT SIZE="+1"><B>Are you sure you want to delete this pending payment?</B></FONT></CENTER> +% } elsif (( $action eq 'complete' ) and $authorized) { + + <CENTER><FONT SIZE="+1"><B>Payment was authorized but not captured. Contact <% $cust_pay_pending->processor || 'the payment gateway' %> to establish the final disposition of this transaction.</B></FONT></CENTER> + % } elsif ( $action eq 'complete' ) { <CENTER><FONT SIZE="+1"><B>No response was received from <% $cust_pay_pending->processor || 'the payment gateway' %> for this transaction. Check <% $cust_pay_pending->processor || 'the payment gateway' %>'s reporting and determine if this transaction completed successfully.</B></FONT></CENTER> @@ -97,8 +101,6 @@ % } else { -%# if ( $action eq 'complete' ) { - <INPUT TYPE="hidden" NAME="action" VALUE=""> <TR> @@ -106,18 +108,25 @@ <BUTTON TYPE="button" onClick="document.pendingform.action.value = 'insert_cust_pay'; document.pendingform.submit();"><!--IMG SRC="<%$p%>images/tick.png" ALT=""-->Yes, transaction completed sucessfully.</BUTTON> </TD> -% if ( $action eq 'complete' ) { +% if ( $action eq 'complete' ) { <TD> </TD> +% if ($authorized) { + <TD ALIGN="center"> + <BUTTON TYPE="button" onClick="document.pendingform.action.value = 'reverse'; document.pendingform.submit();"><!--IMG SRC="<%$p%>images/cross.png" ALT=""-->No, transaction was reversed</BUTTON> + </TD> +% } else { <TD ALIGN="center"> <BUTTON TYPE="button" onClick="document.pendingform.action.value = 'decline'; document.pendingform.submit();"><!--IMG SRC="<%$p%>images/cross.png" ALT=""-->No, transaction was declined</BUTTON> </TD> +% } <TD> </TD> <TD ALIGN="center"> <BUTTON TYPE="button" onClick="document.pendingform.action.value = 'delete'; document.pendingform.submit();"><!--IMG SRC="<%$p%>images/cross.png" ALT=""-->No, transaction was not received</BUTTON> </TD> - </TR> % } + </TR> + <TR><TD COLSPAN=5></TD></TR> <TR> @@ -156,6 +165,8 @@ my $cust_pay_pending = }) or die 'unknown paypendingnum'; +my $authorized = ($cust_pay_pending->status eq 'authorized') ? 1 : 0; + my $conf = new FS::Conf; my $money_char = $conf->config('money_char') || '$'; diff --git a/httemplate/edit/cust_refund.cgi b/httemplate/edit/cust_refund.cgi index bfcbfe725..32da4543e 100755 --- a/httemplate/edit/cust_refund.cgi +++ b/httemplate/edit/cust_refund.cgi @@ -10,7 +10,7 @@ <% small_custview($custnum, $conf->config('countrydefault')) %> % } -<FORM NAME="RefundForm" ACTION="<% $p1 %>process/cust_refund.cgi" METHOD=POST onSubmit="document.RefundForm.submit.disabled=true"> +<FORM NAME="RefundForm" ACTION="<% $p1 %>process/cust_refund.cgi" METHOD=POST onSubmit="document.RefundForm.submitButton.disabled=true"> <INPUT TYPE="hidden" NAME="popup" VALUE="<% $link %>"> <INPUT TYPE="hidden" NAME="refundnum" VALUE=""> <INPUT TYPE="hidden" NAME="custnum" VALUE="<% $custnum %>"> @@ -116,7 +116,7 @@ </TABLE> <BR> -<INPUT TYPE="submit" ID="confirm_refund_button" VALUE="<% mt('Post refund') |h %>" DISABLED> +<INPUT TYPE="submit" NAME="submitButton" ID="confirm_refund_button" VALUE="<% mt('Post refund') |h %>" DISABLED> </FORM> diff --git a/httemplate/edit/elements/ApplicationCommon.html b/httemplate/edit/elements/ApplicationCommon.html index acc3368b8..a531eaad4 100644 --- a/httemplate/edit/elements/ApplicationCommon.html +++ b/httemplate/edit/elements/ApplicationCommon.html @@ -3,46 +3,45 @@ Examples: #cust_bill_pay - include('elements/ApplicationCommon.html', - 'form_action' => 'process/cust_bill_pay.cgi', + <& elements/ApplicationCommon.html, + 'form_action' => 'process/cust_bill_pay.cgi, 'src_table' => 'cust_pay', 'src_thing' => 'payment', 'dst_table' => 'cust_bill', 'dst_thing' => 'invoice', - ) + &> #cust_credit_bill - include('elements/ApplicationCommon.html', - 'form_action' => 'process/cust_credit_bill.cgi', + <& elements/ApplicationCommon.html', + 'form_action' => 'process/cust_credit_bill.cgi, 'src_table' => 'cust_credit', 'src_thing' => 'credit', 'dst_table' => 'cust_bill', 'dst_thing' => 'invoice', - ) + &> #cust_pay_refund - include('elements/ApplicationCommon.html', - 'form_action' => 'process/cust_pay_refund.cgi', + <& elements/ApplicationCommon.html', + 'form_action' => 'process/cust_pay_refund.cgi, 'src_table' => 'cust_pay', 'src_thing' => 'payment', 'dst_table' => 'cust_refund', 'dst_thing' => 'refund', - ) + &> #cust_credit_refund - include('elements/ApplicationCommon.html', + <& elements/ApplicationCommon.html, 'form_action' => 'process/cust_credit_refund.cgi', 'src_table' => 'cust_credit', 'src_thing' => 'credit', 'dst_table' => 'cust_refund', 'dst_thing' => 'refund', - ) + &> </%doc> +<& /elements/header-popup.html, "Apply $src_thing$to", '', 'onLoad="myOnLoadFunction();"' &> -<% include('/elements/header-popup.html', "Apply $src_thing$to", '', 'onLoad="myOnLoadFunction();"') %> - -<% include('/elements/error.html') %> +<& /elements/error.html &> <P ID="ErrorMessage"></P> <FORM ACTION="<% $p1. $opt{'form_action'} %>" NAME="ApplicationForm" ID="ApplicationForm" METHOD=POST> @@ -158,6 +157,7 @@ function changed(what) { % } % $desc .= ' (default)'; % } +% $total_owed = sprintf('%.2f', $total_owed + 0.00000001 ); #so 1.005 rounds to 1.01 % if ( $total_owed > 0 ) { <% &{$row_generator}($key, $cbp, $desc, $total_owed, $amount, '') %> % } @@ -212,11 +212,10 @@ Apply to: </TR> </TABLE> </CENTER> -<% include( '/elements/xmlhttp.html', +<& /elements/xmlhttp.html, 'url' => $p.'misc/xmlhttp-calculate_taxes.html', 'subs' => [ 'calculate_taxes' ], - ) - %> +&> <SCRIPT TYPE="text/javascript"> function show_taxes(arg) { @@ -392,8 +391,7 @@ function myOnLoadFunction () { </SCRIPT> -<% include('/elements/footer.html') %> - +<& /elements/footer-popup.html &> <%init> my %opt = @_; @@ -427,6 +425,8 @@ $can_change_credit = 1 my $to = $dst_table eq 'cust_refund' ? ' to Refund' : ''; +$m->comp('/elements/handle_uri_query'); + my($src_pkeyvalue, $amount, $dst_pkeyvalue, $src_amount); if ( $cgi->param('error') ) { $src_pkeyvalue = $cgi->param($src_pkey); diff --git a/httemplate/edit/elements/edit.html b/httemplate/edit/elements/edit.html index bbc9797dc..8dd15dcfb 100644 --- a/httemplate/edit/elements/edit.html +++ b/httemplate/edit/elements/edit.html @@ -650,7 +650,7 @@ Example: var newrow = <% include(@layer_opt, html_only=>1) |js_string %>; % #until the rest have html/js_only -% if ( $type eq 'selectlayers' || $type =~ /^select-cgp_rule_/ ) { +% if ( ($type eq 'selectlayers') || ($type eq 'selectlayersx') || ($type =~ /^select-cgp_rule_/) ) { var newfunc = <% include(@layer_opt, js_only=>1) |js_string %>; % } else { var newfunc = ''; diff --git a/httemplate/edit/elements/part_svc_column.html b/httemplate/edit/elements/part_svc_column.html index bc679e577..816f3428b 100644 --- a/httemplate/edit/elements/part_svc_column.html +++ b/httemplate/edit/elements/part_svc_column.html @@ -132,7 +132,10 @@ my %communigate_fields = ( % } elsif ( $def->{'type'} eq 'select' ) { % % if ( $def->{'select_table'} ) { - <& /elements/select-table.html, +% # set the 'select_svc' flag to enable two-step selection of services +% my $comp = '/elements/select-table.html'; +% $comp = '/elements/select-svc.html' if $def->{'select_svc'}; + <& $comp, 'field' => $name, 'id' => $name.'_select', 'table' => $def->{'select_table'}, @@ -264,8 +267,9 @@ my %communigate_fields = ( <& /elements/progress-init.html, $svcdb, #form name [ # form fields to send - qw(svc svcpart classnum selfservice_access disabled preserve exportnum), - @fields + 'ALL' +# qw(svc svcpart classnum selfservice_access disabled preserve exportnum), +# @fields ], 'process/part_svc.cgi', # target $p.'browse/part_svc.cgi', # redirect landing diff --git a/httemplate/edit/log_email.html b/httemplate/edit/log_email.html index 709a24069..b79aba986 100644 --- a/httemplate/edit/log_email.html +++ b/httemplate/edit/log_email.html @@ -8,10 +8,16 @@ 'labels' => { '' => '(all)', map { $_ => $_ } @contexts }, 'curr_value' => scalar($cgi->param('context')), }, + { 'field' => 'context_height', + 'type' => 'checkbox', + 'postfix' => 'Only match most specific context', + 'value' => 1, + 'curr_value' => scalar($cgi->param('context_height')), + }, { 'field' => 'min_level', 'type' => 'select', - 'options' => [ 0..7 ], - 'labels' => { map {$_ => $FS::Log::LEVELS[$_]} 0..7 }, + 'options' => [ &FS::Log::levelnums ], + 'labels' => { &FS::Log::levelmap }, 'curr_value' => scalar($cgi->param('min_level')), }, 'to_addr', @@ -24,6 +30,7 @@ ], 'labels' => { 'context' => 'Context', + 'context_height' => '', 'min_level' => 'Min. Level', 'to_addr' => 'To', 'msgnum' => 'Message', @@ -44,6 +51,10 @@ die "access denied" unless $FS::CurrentUser::CurrentUser->access_right([ 'View system logs', 'Configuration' ]); my $msgnum = $cgi->param('msgnum'); + +# XXX This attempt to set a default message isn't working, not sure why +# $msgnum gets set correctly, but isn't selected in the popup window...fix later + unless ($msgnum) { my ($msg_template) = qsearch('msg_template',{ msgname => 'System log' }); # doesn't seem worth having a config just for the default selected template diff --git a/httemplate/edit/msgcat.html b/httemplate/edit/msgcat.html index 4e2edd6d8..5ca118ad7 100644 --- a/httemplate/edit/msgcat.html +++ b/httemplate/edit/msgcat.html @@ -3,7 +3,7 @@ table => 'msgcat', fields => [ { field=>'msgcode', type=>'fixed' }, { field=>'locale', type=>'fixed' }, - 'msg', + { field => 'msg', type => 'text', size => 60 }, ], labels => { 'msgnum' => 'String', 'msgcode' => 'Code', diff --git a/httemplate/edit/part_event.html b/httemplate/edit/part_event.html index 47b8c1ac8..c8072e9f9 100644 --- a/httemplate/edit/part_event.html +++ b/httemplate/edit/part_event.html @@ -31,7 +31,7 @@ value => 'Event Conditions', }, { field => 'conditionname', - type => 'selectlayers', + type => 'selectlayersx', options => [ keys %all_conditions ], labels => \%condition_labels, onchange => 'condition_changed(what);', @@ -51,7 +51,7 @@ value => 'Event Action', }, { field => 'action', - type => 'selectlayers', + type => 'selectlayersx', options => [ keys %all_actions ], labels => \%action_labels, onchange => 'action_changed(what);', diff --git a/httemplate/edit/part_pkg.cgi b/httemplate/edit/part_pkg.cgi index 80a61f813..7fe659f94 100755 --- a/httemplate/edit/part_pkg.cgi +++ b/httemplate/edit/part_pkg.cgi @@ -92,7 +92,7 @@ { type => 'columnstart' }, { field => 'pkg', - type => 'text', + type => 'input-locale-text', size => 40, #32 maxlength => 50, }, @@ -495,42 +495,6 @@ my $recur_show_zero_disabled = 1; my $pkgpart = ''; -my $splice_locale_fields = sub { - my( $fields, $pkey_value_callback, $pkg_value_callback ) = @_; - - my $n = 0; - my @locale_fields = ( - map { - my $pkey_value= $pkey_value_callback ? &$pkey_value_callback($_) : ''; - my $pkg_value = $pkg_value_callback - ? $pkg_value_callback eq 'cgiparam' - ? $cgi->param('pkgpartmsgnum'. $n. '_pkg') - : &$pkg_value_callback($_) - : ''; - ( - { field => 'pkgpartmsgnum'. $n, - type => 'hidden', - value => $pkey_value, - }, - { field => 'pkgpartmsgnum'. $n. '_locale', - type => 'hidden', - value => $_, - }, - { field => 'pkgpartmsgnum'. $n++. '_pkg', - type => 'text', - size => 40, - #maxlength => 50, - value => $pkg_value, - }, - ); - - } - @locales - ); - splice(@$fields, 7, 0, @locale_fields); #XXX 7 is arbitrary above - -}; - my $error_callback = sub { my($cgi, $object, $fields, $opt ) = @_; @@ -579,16 +543,6 @@ my $error_callback = sub { $pkgpart = $object->pkgpart; - &$splice_locale_fields( - $fields, - sub { - my $locale = shift; - my $part_pkg_msgcat = $object->part_pkg_msgcat($locale); - $part_pkg_msgcat ? $part_pkg_msgcat->pkgpartmsgnum : ''; - }, - 'cgiparam' - ); - if ( $cgi->param('error') =~ / is suggested with / ) { #yeah, detection is a shitty kludge, but we don't have exception objects $opt->{form_init} = '<INPUT TYPE="checkbox" NAME="part_pkg_restrict_soft_override" VALUE="Y"> Override suggestion<BR><BR>'; @@ -665,20 +619,6 @@ my $edit_callback = sub { $pkgpart = $object->pkgpart; - &$splice_locale_fields( - $fields, - sub { - my $locale = shift; - my $part_pkg_msgcat = $object->part_pkg_msgcat($locale); - $part_pkg_msgcat ? $part_pkg_msgcat->pkgpartmsgnum : ''; - }, - sub { - my $locale = shift; - my $part_pkg_msgcat = $object->part_pkg_msgcat($locale); - $part_pkg_msgcat ? $part_pkg_msgcat->pkg : ''; - } - ); - }; my $new_callback = sub { @@ -692,8 +632,6 @@ my $new_callback = sub { $options{'suspend_bill'}=1 if $conf->exists('part_pkg-default_suspend_bill'); - &$splice_locale_fields($fields, '', ''); - }; my $clone_callback = sub { @@ -732,17 +670,6 @@ my $clone_callback = sub { foreach keys %part_pkg_currency; } - $recur_disabled = $object->freq ? 0 : 1; - - &$splice_locale_fields( - $fields, - '', - sub { - my $locale = shift; - my $part_pkg_msgcat = $object->part_pkg_msgcat($locale); - $part_pkg_msgcat ? $part_pkg_msgcat->pkg : ''; - } - ); }; my $discount_error_callback = sub { @@ -1061,6 +988,16 @@ my $html_bottom = sub { : '' ). '>'; + } elsif ( $href->{$field}{'type'} =~ /^select-rt-/ ) { + + $html .= include('/elements/'.$href->{$field}{'type'}.'.html', + 'name' => $layer.'__'.$field, + 'curr_value' => $options{$field}, + map { $_ => $href->{$field}{$_} } + grep { $_ !~ /^(name|type|parse)$/ } + keys %{ $href->{$field} } + ); + } elsif ( $href->{$field}{'type'} eq 'select-rate' ) { $html .= include('/elements/select-rate.html', diff --git a/httemplate/edit/part_referral.html b/httemplate/edit/part_referral.html index e9fd79452..04287d632 100755 --- a/httemplate/edit/part_referral.html +++ b/httemplate/edit/part_referral.html @@ -3,11 +3,13 @@ 'table' => 'part_referral', 'fields' => [ 'referral', { field=>'agentnum', type=>'select-agent', }, + 'title', { field=>'disabled', type=>'checkbox', value=>'Y' } , ], 'labels' => { 'refnum' => 'Ad Source', 'referral' => 'Advertising source', 'agentnum' => 'Agent', + 'title' => 'External ID', 'disabled' => 'Disabled', }, 'viewall_dir' => 'browse', diff --git a/httemplate/edit/part_svc.cgi b/httemplate/edit/part_svc.cgi index a07fc6005..fed21256f 100755 --- a/httemplate/edit/part_svc.cgi +++ b/httemplate/edit/part_svc.cgi @@ -36,9 +36,26 @@ } </STYLE> <SCRIPT TYPE="text/javascript"> +// copy all fields from the outer form (svc and its localizations, plus +// preserve, selfservice_access, etc.) into the inner form, creating hidden +// inputs if needed function fixup_submit(layer) { - document.forms[layer].submit.disabled = true; - fixup(document.forms[layer]); + var layer_form = $(document.forms[layer]); + var main_form = $(document.forms['SvcEditMain']); + var data = main_form.serializeArray(); + for (var i = 0; i < data.length; i++) { + var input = layer_form.children('[name=' + data[i].name + ']'); + if (input[0]) { + input.prop('value', data[i].value); + } else { + $( '<input type="hidden">' ) + .attr('name', data[i].name) + .prop('value', data[i].value) + .appendTo(layer_form); + } + } + layer_form[0]['submit'].disabled = true; + //fixup(document.forms[layer]); window[layer+'process'].call(); } @@ -141,19 +158,26 @@ window.onload = function() { </SCRIPT> -<FORM NAME="dummy"> +<FORM NAME="SvcEditMain"> <FONT CLASS="fsinnerbox-title">Service Part #<% $part_svc->svcpart ? $part_svc->svcpart : "(NEW)" %></FONT> <TABLE CLASS="fsinnerbox"> -<TR> - <TD ALIGN="right">Service</TD> - <TD><INPUT TYPE="text" NAME="svc" VALUE="<% $hashref->{svc} %>"></TD> -<TR> +<& /elements/tr-input-locale-text.html, + 'object' => $part_svc, + 'cgi' => $cgi, + 'field' => 'svc', + 'label' => 'Service', + 'curr_value' => $hashref->{svc}, +&> +%#<TR> +%# <TD ALIGN="right">Service</TD> +%# <TD><INPUT TYPE="text" NAME="svc" VALUE="<% $hashref->{svc} %>"></TD> +%#<TR> <& /elements/tr-select-part_svc_class.html, curr_value=>$hashref->{classnum} &> <TR> - <TD ALIGN="right">Self-service access</TD> + <TH ALIGN="right">Self-service access</TD> <TD> <SELECT NAME="selfservice_access"> % tie my %selfservice_access, 'Tie::IxHash', #false laziness w/browse/part_svc @@ -172,12 +196,12 @@ window.onload = function() { <TR> - <TD ALIGN="right">Disable new orders</TD> + <TH ALIGN="right">Disable new orders</TD> <TD><INPUT TYPE="checkbox" NAME="disabled" VALUE="Y"<% $hashref->{disabled} eq 'Y' ? ' CHECKED' : '' %>></TD> </TR> <TR> - <TD ALIGN="right">Preserve this service on package cancellation</TD> + <TH ALIGN="right">Preserve this service on package cancellation</TD> <TD><INPUT TYPE="checkbox" NAME="preserve" VALUE="Y"<% $hashref->{'preserve'} eq 'Y' ? ' CHECKED' : '' %>> </TD> </TR> @@ -240,12 +264,12 @@ my $widget = new HTML::Widgets::SelectLayers( #'selected_layer' => $p_svcdb, 'selected_layer' => $hashref->{svcdb} || 'svc_acct', 'options' => \%svcdb, - 'form_name' => 'dummy', + 'form_name' => 'SvcEditMain', #'form_action' => 'process/part_svc.cgi', 'form_action' => 'part_svc.cgi', #self - 'form_elements' => [qw( svc svcpart classnum selfservice_access - disabled preserve - )], +# 'form_elements' => [qw( svc svcpart classnum selfservice_access +# disabled preserve +# )], 'html_between' => $help, 'layer_callback' => sub { include('elements/part_svc_column.html', diff --git a/httemplate/edit/payment_gateway.html b/httemplate/edit/payment_gateway.html index a85ba57af..b44b31513 100644 --- a/httemplate/edit/payment_gateway.html +++ b/httemplate/edit/payment_gateway.html @@ -47,28 +47,36 @@ my %modules = ( 'AuthorizeNet', 'BankOfAmerica', #deprecated? 'Beanstream', + 'Braintree', 'Capstone', + 'CardFortress', 'Cardstream', 'CashCow', 'CyberSource', 'eSec', 'eSelectPlus', 'ElavonVirtualMerchant', + 'eWay', 'Exact', 'FirstDataGlobalGateway', + 'GlobalPayments', 'iAuthorizer', 'Ingotz', 'InternetSecure', - 'IPaymentTPG', + #'IPaymentTPG', 'IPPay', 'Iridium', 'Jettis', 'Jety', 'LinkPoint', + 'Litle', 'MerchantCommerce', 'Network1Financial', + 'NMI', 'OCV', + 'Ogone', 'OpenECHO', + 'PaperlessTrans', 'PayConnect', 'PayflowPro', 'PaymenTech', @@ -84,26 +92,31 @@ my %modules = ( 'Skipjack', 'StGeorge', 'SurePay', + 'SynapseGateway', 'TCLink', 'TransactionCentral', 'TransFirsteLink', + 'USAePay', 'Vanco', 'viaKLIX', 'VirtualNet', + 'vSecureProcessing', 'WesternACH', 'WorldPay', ], 'Business::OnlineThirdPartyPayment' => [ #'eWayShared', support currently broken - #'Interswitchng', - 'PayPal', 'FCMB', + #'Interswitchng', #incomplete? + 'PayPal', ], 'Business::BatchPayment' => [ + 'BillBuddy', + 'CardFortress', 'KeyBank', 'Paymentech', + 'RBC', 'TD_EFT', - 'BillBuddy', ], ); @@ -132,7 +145,7 @@ my $fields = [ { field => 'gateway_module', type => 'select', # does it even make sense to list all modules here? - options => [ sort { lc($a) cmp lc ($b) } + options => [ sort { lc($a) cmp lc($b) } map { @$_ } values %modules ], }, 'gateway_username', diff --git a/httemplate/edit/process/bulk-cust_main_county.html b/httemplate/edit/process/bulk-cust_main_county.html index b7ff40fa7..b5a0258b1 100644 --- a/httemplate/edit/process/bulk-cust_main_county.html +++ b/httemplate/edit/process/bulk-cust_main_county.html @@ -12,7 +12,7 @@ <% include('/elements/header-popup.html', "Taxes ${action}ed") %> <SCRIPT TYPE="text/javascript"> - window.top.location.reload(); + topreload(); </SCRIPT> </BODY> diff --git a/httemplate/edit/process/bulk-cust_svc-pkgnum.html b/httemplate/edit/process/bulk-cust_svc-pkgnum.html index f5cf7dd07..3c273069a 100644 --- a/httemplate/edit/process/bulk-cust_svc-pkgnum.html +++ b/httemplate/edit/process/bulk-cust_svc-pkgnum.html @@ -7,7 +7,7 @@ <% header(emt("Services moved")) %> <SCRIPT TYPE="text/javascript"> - window.top.location.reload(); + topreload(); </SCRIPT> </BODY> </HTML> diff --git a/httemplate/edit/process/cgp_rule-simplified.html b/httemplate/edit/process/cgp_rule-simplified.html index 60769d4e6..24515d551 100644 --- a/httemplate/edit/process/cgp_rule-simplified.html +++ b/httemplate/edit/process/cgp_rule-simplified.html @@ -4,7 +4,7 @@ % } else { #success XXX better msg talking about vacation vs. redirect all <% include('/elements/header-popup.html', 'Rule updated') %> <SCRIPT TYPE="text/javascript"> - window.top.location.reload(); + topreload(); </SCRIPT> </BODY> diff --git a/httemplate/edit/process/change-cust_pkg.html b/httemplate/edit/process/change-cust_pkg.html index 308ea8ffd..54cafbf18 100644 --- a/httemplate/edit/process/change-cust_pkg.html +++ b/httemplate/edit/process/change-cust_pkg.html @@ -5,7 +5,7 @@ <% header(emt("Package changed")) %> <SCRIPT TYPE="text/javascript"> - window.top.location.reload(); + topreload(); </SCRIPT> </BODY> </HTML> diff --git a/httemplate/edit/process/commission_schedule.html b/httemplate/edit/process/commission_schedule.html new file mode 100644 index 000000000..50e0371da --- /dev/null +++ b/httemplate/edit/process/commission_schedule.html @@ -0,0 +1,36 @@ +<& elements/process.html, + 'table' => 'commission_schedule', + 'viewall_dir' => 'browse', + 'process_o2m' => { + 'table' => 'commission_rate', + 'fields' => [qw( cycle amount percent )], + }, + 'precheck_callback' => $precheck, + 'debug' => 1, +&> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); + +my $precheck = sub { + my $cgi = shift; + $cgi->param('reasonnum') =~ /^(-?\d+)$/ or die "Illegal reasonnum"; + + my ($reasonnum, $error) = $m->comp('/misc/process/elements/reason'); + if (!$reasonnum) { + $error ||= 'Reason required' + } + $cgi->param('reasonnum', $reasonnum) unless $error; + + # remove rate entries with no cycle selected + foreach my $k (grep /^commissionratenum\d+$/, $cgi->param) { + if (! $cgi->param($k.'_cycle') ) { + $cgi->delete($k); + } + } + + $error; +}; + +</%init> diff --git a/httemplate/edit/process/credit-cust_bill_pkg.html b/httemplate/edit/process/credit-cust_bill_pkg.html index 75900bde5..12b68c0f5 100644 --- a/httemplate/edit/process/credit-cust_bill_pkg.html +++ b/httemplate/edit/process/credit-cust_bill_pkg.html @@ -3,7 +3,7 @@ %} else { <& /elements/header-popup.html, 'Credit successful' &> <SCRIPT TYPE="text/javascript"> - window.top.location.reload(); + topreload(); </SCRIPT> </BODY></HTML> % } diff --git a/httemplate/edit/process/cust_credit-pkgnum.html b/httemplate/edit/process/cust_credit-pkgnum.html index 8941cbc73..56f7989a4 100755 --- a/httemplate/edit/process/cust_credit-pkgnum.html +++ b/httemplate/edit/process/cust_credit-pkgnum.html @@ -4,7 +4,7 @@ %} else { <% header(emt('Credit package changed')) %> <SCRIPT TYPE="text/javascript"> - window.top.location.reload(); + topreload(); </SCRIPT> </BODY></HTML> diff --git a/httemplate/edit/process/cust_credit.cgi b/httemplate/edit/process/cust_credit.cgi index 39c6f1997..5d3028777 100755 --- a/httemplate/edit/process/cust_credit.cgi +++ b/httemplate/edit/process/cust_credit.cgi @@ -16,7 +16,7 @@ % <% header(emt('Credit successful')) %> <SCRIPT TYPE="text/javascript"> - window.top.location.reload(); + topreload(); </SCRIPT> </BODY></HTML> diff --git a/httemplate/edit/process/cust_location-censustract.html b/httemplate/edit/process/cust_location-censustract.html index bc9cd4f31..6edaca3fd 100644 --- a/httemplate/edit/process/cust_location-censustract.html +++ b/httemplate/edit/process/cust_location-censustract.html @@ -5,7 +5,7 @@ <% header("Census tract changed") %> <SCRIPT TYPE="text/javascript"> - window.top.location.reload(); + topreload(); </SCRIPT> </BODY> </HTML> diff --git a/httemplate/edit/process/cust_location.cgi b/httemplate/edit/process/cust_location.cgi index fd1b8740e..3a2388111 100644 --- a/httemplate/edit/process/cust_location.cgi +++ b/httemplate/edit/process/cust_location.cgi @@ -5,7 +5,7 @@ <% header("Location changed") %> <SCRIPT TYPE="text/javascript"> - window.top.location.reload(); + topreload(); </SCRIPT> </BODY> </HTML> diff --git a/httemplate/edit/process/cust_main.cgi b/httemplate/edit/process/cust_main.cgi index d590fdef0..74f8f2382 100755 --- a/httemplate/edit/process/cust_main.cgi +++ b/httemplate/edit/process/cust_main.cgi @@ -1,5 +1,15 @@ % if ( $error ) { % $cgi->param('error', $error); +% # workaround for create_uri_query's mangling of unicode characters, +% # false laziness with FS::Record::ut_coord +% use charnames ':full'; +% for my $pre (qw(bill ship)) { +% foreach (qw( latitude longitude)) { +% my $coord = $cgi->param($pre.'_'.$_); +% $coord =~ s/\N{DEGREE SIGN}\s*$//; +% $cgi->param($pre.'_'.$_, $coord); +% } +% } % my $query = $m->scomp('/elements/create_uri_query', 'secure'=>1); <% $cgi->redirect(popurl(2). "cust_main.cgi?$query" ) %> % @@ -76,8 +86,8 @@ if ( ($cgi->param('same') || '') eq 'Y' ) { # but explicitly avoid setting ship_ fields my $new = new FS::cust_main ( { - map { ( $_, scalar($cgi->param($_)) ) } (fields('cust_main')), - map { ( "ship_$_", '' ) } (FS::cust_main->location_fields) + (map { ( $_, scalar($cgi->param($_)) ) } (fields('cust_main'))), + (map { ( "ship_$_", '' ) } (FS::cust_main->location_fields)) } ); warn Dumper( $new ) if $DEBUG > 1; @@ -188,6 +198,15 @@ if ( $cgi->param('residential_commercial') eq 'Residential' ) { } +# kind of a hack, but some tax data vendors require a status and others +# don't. +my $vendor = $conf->config('tax_data_vendor'); +if ( $vendor eq 'avalara' or $vendor eq 'suretax' ) { + if ( ! $cgi->param('taxstatusnum') ) { + $error ||= 'Tax status required'; + } +} + #perhaps this stuff should go to cust_main.pm if ( $new->custnum eq '' or $duplicate_of ) { diff --git a/httemplate/edit/process/cust_main_attach.cgi b/httemplate/edit/process/cust_main_attach.cgi index 09c18adcb..569500246 100644 --- a/httemplate/edit/process/cust_main_attach.cgi +++ b/httemplate/edit/process/cust_main_attach.cgi @@ -9,7 +9,7 @@ % $act = 'deleted' if($attachnum and $delete); <% header('Attachment ' . $act ) %> <SCRIPT TYPE="text/javascript"> - window.top.location.reload(); + topreload(); </SCRIPT> </BODY></HTML> % } diff --git a/httemplate/edit/process/cust_main_county-add.cgi b/httemplate/edit/process/cust_main_county-add.cgi index fc8956b0c..fcc138f49 100755 --- a/httemplate/edit/process/cust_main_county-add.cgi +++ b/httemplate/edit/process/cust_main_county-add.cgi @@ -1,7 +1,7 @@ <% include('/elements/header-popup.html', 'Addition successful' ) %> <SCRIPT TYPE="text/javascript"> - window.top.location.reload(); + topreload(); </SCRIPT> </BODY> diff --git a/httemplate/edit/process/cust_main_county-expand.cgi b/httemplate/edit/process/cust_main_county-expand.cgi index a10827621..42e46734a 100755 --- a/httemplate/edit/process/cust_main_county-expand.cgi +++ b/httemplate/edit/process/cust_main_county-expand.cgi @@ -1,7 +1,7 @@ <% include('/elements/header-popup.html', 'Addition successful' ) %> <SCRIPT TYPE="text/javascript"> - window.top.location.reload(); + topreload(); </SCRIPT> </BODY> diff --git a/httemplate/edit/process/cust_main_note.cgi b/httemplate/edit/process/cust_main_note.cgi index 53e616a43..bb52db8f3 100755 --- a/httemplate/edit/process/cust_main_note.cgi +++ b/httemplate/edit/process/cust_main_note.cgi @@ -4,7 +4,7 @@ %} else { <% header('Note ' . ($notenum ? 'updated' : 'added') ) %> <SCRIPT TYPE="text/javascript"> - window.top.location.reload(); + topreload(); </SCRIPT> </BODY></HTML> % } diff --git a/httemplate/edit/process/cust_pay-no_auto_apply.cgi b/httemplate/edit/process/cust_pay-no_auto_apply.cgi index ccbd2d7b5..4a5ee841a 100644 --- a/httemplate/edit/process/cust_pay-no_auto_apply.cgi +++ b/httemplate/edit/process/cust_pay-no_auto_apply.cgi @@ -15,7 +15,7 @@ Requires 'Apply payment' acl. <P STYLE="font-weight: bold;"><% emt($message) %></P> <P><% emt('Please wait while the page reloads.') %></P> <SCRIPT TYPE="text/javascript"> -window.top.location.reload(); +topreload(); </SCRIPT> % } diff --git a/httemplate/edit/process/cust_pay-pkgnum.html b/httemplate/edit/process/cust_pay-pkgnum.html index d9a92a1de..cefe970fe 100755 --- a/httemplate/edit/process/cust_pay-pkgnum.html +++ b/httemplate/edit/process/cust_pay-pkgnum.html @@ -4,7 +4,7 @@ %} else { <% header(emt('Payment package changed')) %> <SCRIPT TYPE="text/javascript"> - window.top.location.reload(); + topreload(); </SCRIPT> </BODY></HTML> diff --git a/httemplate/edit/process/cust_pay.cgi b/httemplate/edit/process/cust_pay.cgi index 56d3f2ff1..15b26f9c6 100755 --- a/httemplate/edit/process/cust_pay.cgi +++ b/httemplate/edit/process/cust_pay.cgi @@ -14,7 +14,7 @@ % <% header(emt('Payment entered')) %> <SCRIPT TYPE="text/javascript"> - window.top.location.reload(); + topreload(); </SCRIPT> </BODY></HTML> @@ -69,6 +69,6 @@ push @rights, 'Post cash payment' if $new->payby eq 'CASH'; die "access denied" unless $FS::CurrentUser::CurrentUser->access_right(\@rights); -my $error ||= $new->insert( 'manual' => 1 ); +my $error = $new->insert( 'manual' => 1 ); </%init> diff --git a/httemplate/edit/process/cust_pay_pending.html b/httemplate/edit/process/cust_pay_pending.html index 1bad6cffe..80bd14aaf 100644 --- a/httemplate/edit/process/cust_pay_pending.html +++ b/httemplate/edit/process/cust_pay_pending.html @@ -3,7 +3,7 @@ <FONT SIZE="+1" COLOR="#ff0000">Error: <% $error |h %></FONT> % } else { <SCRIPT TYPE="text/javascript"> - window.top.location.reload(); + topreload(); </SCRIPT> % } </BODY> @@ -59,6 +59,15 @@ if ( $action eq 'delete' ) { $title = 'Pending payment completed (decline)'; } +} elsif ( $action eq 'reverse' ) { + + $error = $cust_pay_pending->reverse; + if ( $error ) { + $title = 'Error reversing pending payment'; + } else { + $title = 'Pending payment completed (reverse)'; + } + } else { die "unknown action $action"; diff --git a/httemplate/edit/process/cust_pkg_detail.html b/httemplate/edit/process/cust_pkg_detail.html index 132ff63c5..25fabd930 100644 --- a/httemplate/edit/process/cust_pkg_detail.html +++ b/httemplate/edit/process/cust_pkg_detail.html @@ -6,7 +6,7 @@ % } else { <% header($action) %> <SCRIPT TYPE="text/javascript"> - window.top.location.reload(); + topreload(); </SCRIPT> </BODY></HTML> % } diff --git a/httemplate/edit/process/cust_pkg_discount.html b/httemplate/edit/process/cust_pkg_discount.html index 143611ef9..963546363 100644 --- a/httemplate/edit/process/cust_pkg_discount.html +++ b/httemplate/edit/process/cust_pkg_discount.html @@ -5,7 +5,7 @@ <% header("Discount applied") %> <SCRIPT TYPE="text/javascript"> - window.top.location.reload(); + topreload(); </SCRIPT> </BODY> </HTML> diff --git a/httemplate/edit/process/cust_pkg_quantity.html b/httemplate/edit/process/cust_pkg_quantity.html index fb2657252..b60595583 100644 --- a/httemplate/edit/process/cust_pkg_quantity.html +++ b/httemplate/edit/process/cust_pkg_quantity.html @@ -5,7 +5,7 @@ <& /elements/header-popup.html, "Quantity changed" &> <SCRIPT TYPE="text/javascript"> - window.top.location.reload(); + topreload(); </SCRIPT> </BODY> </HTML> diff --git a/httemplate/edit/process/cust_pkg_salesnum.html b/httemplate/edit/process/cust_pkg_salesnum.html index aab37416a..c1cb26813 100644 --- a/httemplate/edit/process/cust_pkg_salesnum.html +++ b/httemplate/edit/process/cust_pkg_salesnum.html @@ -5,7 +5,7 @@ <& /elements/header-popup.html, "Sales Person changed" &> <SCRIPT TYPE="text/javascript"> - window.top.location.reload(); + topreload(); </SCRIPT> </BODY> </HTML> diff --git a/httemplate/edit/process/cust_refund.cgi b/httemplate/edit/process/cust_refund.cgi index 8977ced20..d4236bcdf 100755 --- a/httemplate/edit/process/cust_refund.cgi +++ b/httemplate/edit/process/cust_refund.cgi @@ -7,7 +7,7 @@ % <% header('Refund entered') %> <SCRIPT TYPE="text/javascript"> - window.top.location.reload(); + topreload(); </SCRIPT> </BODY></HTML> diff --git a/httemplate/edit/process/cust_tax_adjustment.html b/httemplate/edit/process/cust_tax_adjustment.html index 204b5b9f7..fe232757f 100644 --- a/httemplate/edit/process/cust_tax_adjustment.html +++ b/httemplate/edit/process/cust_tax_adjustment.html @@ -4,7 +4,7 @@ % } else { <% header("Tax adjustment added") %> <SCRIPT TYPE="text/javascript"> - //window.top.location.reload(); + //topreload(); parent.cClick(); </SCRIPT> </BODY></HTML> diff --git a/httemplate/edit/process/detach-cust_pkg.html b/httemplate/edit/process/detach-cust_pkg.html index 782ffa5e0..34c580560 100644 --- a/httemplate/edit/process/detach-cust_pkg.html +++ b/httemplate/edit/process/detach-cust_pkg.html @@ -5,7 +5,7 @@ <% header(emt("Package detached")) %> <SCRIPT TYPE="text/javascript"> - window.top.location.reload(); + topreload(); </SCRIPT> </BODY> </HTML> diff --git a/httemplate/edit/process/domain_record.cgi b/httemplate/edit/process/domain_record.cgi index 8369f7114..9d869d547 100755 --- a/httemplate/edit/process/domain_record.cgi +++ b/httemplate/edit/process/domain_record.cgi @@ -3,7 +3,7 @@ %} elsif ( $recnum ) { #editing <% header('Nameservice record changed') %> <SCRIPT TYPE="text/javascript"> - window.top.location.reload(); + topreload(); </SCRIPT> </BODY></HTML> %} else { #adding diff --git a/httemplate/edit/process/elements/ApplicationCommon.html b/httemplate/edit/process/elements/ApplicationCommon.html index c7bdd3ea2..67fa89196 100644 --- a/httemplate/edit/process/elements/ApplicationCommon.html +++ b/httemplate/edit/process/elements/ApplicationCommon.html @@ -21,11 +21,12 @@ Examples: </%doc> %if ( $error ) { % $cgi->param('error', $error); -<% $cgi->redirect(popurl(2). $opt{error_redirect}. '?'. $cgi->query_string ) %> +% my $query = $m->scomp('/elements/create_uri_query'); +<% $cgi->redirect(popurl(2). $opt{error_redirect}. "?$query") %> %} else { <% header("$src_thing application$to sucessful") %> <SCRIPT TYPE="text/javascript"> - window.top.location.reload(); + topreload(); </SCRIPT> </BODY> </HTML> diff --git a/httemplate/edit/process/elements/process.html b/httemplate/edit/process/elements/process.html index fd12c61d9..76722c960 100644 --- a/httemplate/edit/process/elements/process.html +++ b/httemplate/edit/process/elements/process.html @@ -62,6 +62,8 @@ Example: 'fields' => [qw( fieldname fieldname2 )], }, + 'process_locale' => 'fieldname', # update entries in the _msgcat table + 'process_upload' => { 'process' => 'misc/mytable-import.html', # fields to pass to the back end job, besides the @@ -186,7 +188,7 @@ process(); <% include('/elements/header-popup.html', $opt{'popup_reload'} ) %> <SCRIPT TYPE="text/javascript"> - window.top.location.reload(); + topreload(); </SCRIPT> </BODY> @@ -363,12 +365,21 @@ foreach my $value ( @values ) { } - if ( !$error && $opt{'process_o2m'} ) { - - my @process_o2m = ref($opt{'process_o2m'}) eq 'ARRAY' - ? @{ $opt{'process_o2m'} } - : ( $opt{'process_o2m'} ); + my @process_o2m; + if ( $opt{'process_o2m'} ) { + @process_o2m = ref($opt{'process_o2m'}) eq 'ARRAY' + ? @{ $opt{'process_o2m'} } + : ( $opt{'process_o2m'} ); + } + if ( $opt{'process_locale'} ) { + push @process_o2m, + { + 'table' => $table . '_msgcat', + 'fields' => [ 'locale', $opt{'process_locale'} ], + }; + } + if ( !$error ) { foreach my $process_o2m (@process_o2m) { diff --git a/httemplate/edit/process/part_event.html b/httemplate/edit/process/part_event.html index bac69241c..0293af886 100644 --- a/httemplate/edit/process/part_event.html +++ b/httemplate/edit/process/part_event.html @@ -39,8 +39,8 @@ split(/\0/, $value) }; } elsif ( $info->{'type'} eq 'freq' ) { - $value = '0' if !length($value); - $value .= $params->{$cgi_field.'_units'}; + $value = '0' if !length($value) and !$info->{'allow_blank'}; + $value .= $params->{$cgi_field.'_units'} if length($value); } #warn "value of $cgi_field is $value\n"; diff --git a/httemplate/edit/process/part_pkg.cgi b/httemplate/edit/process/part_pkg.cgi index b8042026a..c4d150ba1 100755 --- a/httemplate/edit/process/part_pkg.cgi +++ b/httemplate/edit/process/part_pkg.cgi @@ -9,6 +9,7 @@ 'edit_ext' => 'cgi', 'precheck_callback' => $precheck_callback, 'args_callback' => $args_callback, + 'process_locale' => 'pkg', 'process_m2m' => \@process_m2m, 'process_o2m' => \@process_o2m, ) @@ -310,10 +311,6 @@ foreach my $amount_param ( grep /^usagepricepart(\d+)_amount$/, $cgi->param ) { my @process_o2m = ( { - 'table' => 'part_pkg_msgcat', - 'fields' => [qw( locale pkg )], - }, - { 'table' => 'part_pkg_usageprice', 'fields' => [qw( price currency action target amount )], diff --git a/httemplate/edit/process/prospect_main.html b/httemplate/edit/process/prospect_main.html index 7c8cc276e..b2ae88eba 100644 --- a/httemplate/edit/process/prospect_main.html +++ b/httemplate/edit/process/prospect_main.html @@ -1,5 +1,6 @@ <% include('elements/process.html', 'table' => 'prospect_main', + 'precheck_callback' => $precheck, 'args_callback' => $args_callback, 'agent_virt' => 1, 'process_o2m' => { @@ -11,6 +12,16 @@ %> <%init> +my $precheck = sub { + my $cgi = shift; + my $vendor = FS::Conf->new->config('tax_data_vendor'); + if ( $vendor eq 'avalara' or $vendor eq 'suretax' ) { + if ( ! $cgi->param('taxstatusnum') ) { + return 'Tax status required'; + } + } +}; + my $args_callback = sub { my( $cgi, $object ) = @_; diff --git a/httemplate/edit/process/quick-charge.cgi b/httemplate/edit/process/quick-charge.cgi index 8ee182141..00d17c8b1 100644 --- a/httemplate/edit/process/quick-charge.cgi +++ b/httemplate/edit/process/quick-charge.cgi @@ -1,10 +1,10 @@ % if ( $error ) { % $cgi->param('error', $error ); -<% $cgi->redirect($p.'quick-charge.html?'. $cgi->query_string) %> +<% $cgi->redirect($redirect) %> % } else { <% header(emt($message)) %> <SCRIPT TYPE="text/javascript"> - window.top.location.reload(); + topreload(); </SCRIPT> </BODY></HTML> % } @@ -157,4 +157,14 @@ if ( $param->{'pkgnum'} =~ /^(\d+)$/ ) { #modifying an existing one-time charge } +my $redirect; +if ( $error ) { + $cgi->param('error', $error ); + $redirect = $p.'quick-charge.html?'. $cgi->query_string; +} elsif ( $quotation ) { + $redirect = $fsurl.'view/quotation.html?' . $quotation->quotationnum; +} else { + $redirect = $fsurl.'view/cust_main.cgi?custnum=' . $cust_main->custnum . ';show=last'; +} + </%init> diff --git a/httemplate/edit/process/quotation_convert.html b/httemplate/edit/process/quotation_convert.html index dc00a88d3..26b5294f7 100644 --- a/httemplate/edit/process/quotation_convert.html +++ b/httemplate/edit/process/quotation_convert.html @@ -10,9 +10,12 @@ my $quotation = qsearchs( 'quotation' => { quotationnum => scalar( $cgi->param('quotationnum') ), } ) or die 'unknown quotationnum'; +my $params = {}; +$$params{'onhold'} = $cgi->param('onhold') ? 1 : 0; + my $cust_main = $quotation->cust_main; if ( $cust_main ) { - my $error = $quotation->order; + my $error = $quotation->order(undef,$params); errorpage($error) if $error; #i should be part of the order transaction @@ -20,7 +23,7 @@ if ( $cust_main ) { $quotation->replace; } else { - $cust_main = $quotation->convert_cust_main; + $cust_main = $quotation->convert_cust_main( $params ); errorpage($cust_main) unless ref($cust_main);# eq 'FS::cust_main'; } diff --git a/httemplate/edit/process/quotation_pkg_detail.html b/httemplate/edit/process/quotation_pkg_detail.html index 9e4ac3222..5728832b2 100644 --- a/httemplate/edit/process/quotation_pkg_detail.html +++ b/httemplate/edit/process/quotation_pkg_detail.html @@ -6,7 +6,7 @@ % } else { <% header($action) %> <SCRIPT TYPE="text/javascript"> - window.top.location.reload(); + topreload(); </SCRIPT> </BODY></HTML> % } @@ -26,8 +26,16 @@ my $quotation_pkg = qsearchs({ 'LEFT JOIN cust_main USING ( custnum )', 'hashref' => { 'quotationpkgnum' => $pkgnum }, 'extra_sql' => ' AND '. $curuser->agentnums_sql, +}) +|| qsearchs({ + 'table' => 'quotation_pkg', + 'addl_from' => 'LEFT JOIN quotation USING ( quotationnum )'. + 'LEFT JOIN prospect_main USING ( prospectnum )', + 'hashref' => { 'quotationpkgnum' => $pkgnum }, + 'extra_sql' => ' AND '. $curuser->agentnums_sql, }); + my @orig_details = $quotation_pkg->details(); my $action = 'Quotation details'. diff --git a/httemplate/edit/process/svc_dsl.html b/httemplate/edit/process/svc_dsl.html index 436ca766b..489c86916 100644 --- a/httemplate/edit/process/svc_dsl.html +++ b/httemplate/edit/process/svc_dsl.html @@ -25,7 +25,7 @@ my $precheck_callback = sub { my $psc = $part_svc->part_svc_column('password'); if ( $psc->columnflag eq 'F' ) { # enforce it here and skip password validation - $cgi->param('password', $psc->columnvalue; + $cgi->param('password', $psc->columnvalue); } else { my $newpass = $cgi->param('password'); if ( $old and $newpass ne $old->password ) { diff --git a/httemplate/edit/process/tower.html b/httemplate/edit/process/tower.html index 02362db6a..d14ac56f8 100644 --- a/httemplate/edit/process/tower.html +++ b/httemplate/edit/process/tower.html @@ -4,6 +4,7 @@ process_o2m => { 'table' => 'tower_sector', 'fields' => [qw( sectorname ip_addr height freq_mhz direction width + downtilt v_width margin sector_range )], }, diff --git a/httemplate/edit/prospect_main.html b/httemplate/edit/prospect_main.html index fb6751532..6aefe80d0 100644 --- a/httemplate/edit/prospect_main.html +++ b/httemplate/edit/prospect_main.html @@ -34,8 +34,10 @@ { 'field' => 'contactnum', 'type' => 'contact', 'colspan' => 7, - 'o2m_table' => 'contact', - 'm2_label' => 'Contact', + 'prospectnum' => $prospectnum, + 'm2m_method' => 'prospect_contact', + 'm2m_dstcol' => 'contactnum', + 'm2_label' => 'Contact', 'm2_error_callback' => $m2_error_callback, }, @@ -50,6 +52,7 @@ }, { 'field' => 'taxstatusnum', 'type' => 'select-tax_status', + 'required' => 1, 'empty_label' => ' ', }, ], @@ -68,18 +71,25 @@ my $conf = new FS::Conf; my $prospectnum; if ( $cgi->param('error') ) { - $prospectnum = scalar($cgi->param('prospectnum')); + $cgi->param('prospectnum') =~ /^(\d*)$/ or die 'illegal prospectnum'; + $prospectnum = $1; die "access denied" unless $curuser->access_right(($prospectnum ? 'Edit' : 'New'). ' prospect'); } elsif ( $cgi->keywords ) { #editing + my($query) = $cgi->keywords; + $query =~ /^(\d+)$/ or die 'no prospectnum'; + $prospectnum = $1; + die "access denied" unless $curuser->access_right('Edit prospect'); } else { #new prospect + $prospectnum = ''; + die "access denied" unless $curuser->access_right('New prospect'); diff --git a/httemplate/edit/quick-charge.html b/httemplate/edit/quick-charge.html index 14c3f51e6..2bbbe43d8 100644 --- a/httemplate/edit/quick-charge.html +++ b/httemplate/edit/quick-charge.html @@ -1,4 +1,4 @@ -<& /elements/header-popup.html, mt('One-time charge'), '', +<& /elements/header.html, mt('One-time charge'), '', ( ($quotationnum || $cgi->param('error')) ? '' : 'onload="addRow()"' ), &> @@ -93,6 +93,18 @@ function bill_now_changed (what) { </SCRIPT> +<P> +% if ( $cust_main ) { +<& /elements/small_custview.html, + $cust_main->custnum, + scalar($conf->config('countrydefault')), + 1, #no balance +&> +% } else { +<& /elements/small_prospect_view.html, $prospect_main &> +% } +</P> + <FORM ACTION = "process/quick-charge.cgi" NAME = "QuickChargeForm" ID = "QuickChargeForm" @@ -183,7 +195,10 @@ function bill_now_changed (what) { <& /elements/tr-select-taxclass.html, 'curr_value' => $part_pkg->get('taxclass') &> - <& /elements/tr-select-taxproduct.html, 'label' => emt('Tax product'), 'onclick' => 'parent.taxproductmagic(this);', 'curr_value' => $part_pkg->get('taxproductnum') &> + <& /elements/tr-select-taxproduct.html, + 'label' => emt('Tax product'), + 'curr_value' => $part_pkg->get('taxproductnum') + &> % } % } else { # new one-time charge @@ -307,9 +322,14 @@ function bill_now_changed (what) { <& /elements/tr-select-taxclass.html, 'curr_value' => $cgi->param('taxclass') &> -<& /elements/tr-select-taxproduct.html, 'label' => emt('Tax product'), 'onclick' => 'parent.taxproductmagic(this);', 'curr_value' => $cgi->param('taxproductnum') &> +<& /elements/tr-select-taxproduct.html, + 'label' => emt('Tax product'), + 'curr_value' => $cgi->param('taxproductnum') +&> -<& /elements/tr-select-taxoverride.html, 'onclick' => 'parent.taxoverridemagic(this);', 'curr_value' => $cgi->param('tax_override') &> +<& /elements/tr-select-taxoverride.html, + 'curr_value' => $cgi->param('tax_override') +&> % } # if !$cust_pkg diff --git a/httemplate/edit/quotation_pkg_detail.html b/httemplate/edit/quotation_pkg_detail.html index 036bffdde..9aa50ec38 100644 --- a/httemplate/edit/quotation_pkg_detail.html +++ b/httemplate/edit/quotation_pkg_detail.html @@ -61,6 +61,13 @@ my $quotation_pkg = qsearchs({ 'LEFT JOIN cust_main USING ( custnum )', 'hashref' => { 'quotationpkgnum' => $pkgnum }, 'extra_sql' => ' AND '. $curuser->agentnums_sql, +}) +|| qsearchs({ + 'table' => 'quotation_pkg', + 'addl_from' => 'LEFT JOIN quotation USING ( quotationnum )'. + 'LEFT JOIN prospect_main USING ( prospectnum )', + 'hashref' => { 'quotationpkgnum' => $pkgnum }, + 'extra_sql' => ' AND '. $curuser->agentnums_sql, }); my $part_pkg = $quotation_pkg->part_pkg; diff --git a/httemplate/edit/svc_acct.cgi b/httemplate/edit/svc_acct.cgi index ca26c6cf0..7be5eabb7 100755 --- a/httemplate/edit/svc_acct.cgi +++ b/httemplate/edit/svc_acct.cgi @@ -31,7 +31,7 @@ </TR> <% include('/elements/tr-td-label.html', - 'label' => mt('Username'), + 'label' => $part_svc->part_svc_column('username')->columnlabel || mt('Username'), 'required' => $part_svc->part_svc_column('username')->required ) %> % if ( $svcnum && $conf->exists('svc_acct-no_edit_username') ) { <TD BGCOLOR="#eeeeee"><% $svc_acct->username() %></TD> @@ -46,7 +46,7 @@ %if ( $part_svc->part_svc_column('_password')->columnflag ne 'F' ) { % #XXX eventually should require "Edit Password" ACL <% include('/elements/tr-td-label.html', - 'label' => mt('Password'), + 'label' => $part_svc->part_svc_column('_password')->columnlabel || mt('Password'), 'required' => $part_svc->part_svc_column('_password')->required ) %> <TD> <INPUT TYPE="text" ID="clear_password" NAME="clear_password" VALUE="<% $password %>" SIZE=<% $pmax2 %> MAXLENGTH=<% $pmax %>> @@ -68,7 +68,7 @@ % && $part_svc->part_svc_column('sec_phrase')->columnflag ne 'F' ) { <% include('/elements/tr-td-label.html', - 'label' => mt('Security phrase'), + 'label' => $part_svc->part_svc_column('sec_phrase')->columnlabel || mt('Security phrase'), 'required' => $part_svc->part_svc_column('sec_phrase')->required ) %> <TD> <INPUT TYPE="text" NAME="sec_phrase" VALUE="<% $sec_phrase %>" SIZE=32> @@ -106,7 +106,7 @@ % ); <% include('/elements/tr-td-label.html', - 'label' => mt('Domain'), + 'label' => $part_svc->part_svc_column('domsvc')->columnlabel || mt('Domain'), 'required' => $part_svc->part_svc_column('domsvc')->required ) %> <TD> <SELECT NAME="domsvc" SIZE=1> @@ -142,6 +142,7 @@ 'curr_value' => $svc_acct->pbxsvc, 'part_svc' => $part_svc, 'cust_pkg' => $cust_pkg, + 'label' => $part_svc->part_svc_column('pbxsvc')->columnlabel || 'PBX', &> %#pop @@ -153,7 +154,7 @@ % } else { <% include('/elements/tr-td-label.html', - 'label' => mt('Access number'), + 'label' => $part_svc->part_svc_column('popnum')->columnlabel || mt('Access number'), 'required' => $part_svc->part_svc_column('popnum')->required ) %> <TD><% FS::svc_acct_pop::popselector($popnum) %></TD> </TR> @@ -167,6 +168,7 @@ #'part_svc' => $part_svc, #'cust_pkg' => $cust_pkg, 'required' => $part_svc->part_svc_column('sectornum')->required, + 'label' => $part_svc->part_svc_column('sectornum')->columnlabel || mt('Tower sector'), &> %} else { <INPUT TYPE="hidden" NAME="sectornum" VALUE="<% $svc_acct->sectornum %>"> @@ -188,10 +190,10 @@ % if ( length($svc_acct->$xid()) ) { <% include('/elements/tr-td-label.html', - 'label' => uc($xid), + 'label' => $part_svc->part_svc_column($xid)->columnlabel || uc($xid), 'required' => $part_svc->part_svc_column($xid)->required ) %> - <TR> - <TD ALIGN="right"><% uc($xid) %></TD> +%# <TR> +%# <TD ALIGN="right"><% uc($xid) %></TD> <TD BGCOLOR="#eeeeee"><% $svc_acct->$xid() %></TD> <TD> </TD> @@ -202,7 +204,7 @@ % } else { <% include('/elements/tr-td-label.html', - 'label' => uc($xid), + 'label' => $part_svc->part_svc_column($xid)->columnlabel || uc($xid), 'required' => $part_svc->part_svc_column($xid)->required ) %> <TD> <INPUT TYPE="text" NAME="<% $xid %>" SIZE=8 MAXLENGTH=6 VALUE="<% $svc_acct->$xid() %>"> @@ -221,7 +223,7 @@ <% include('/elements/tr-td-label.html', - 'label' => mt('Real Name'), + 'label' => $part_svc->part_svc_column('finger')->columnlabel || mt('Real Name'), 'required' => $part_svc->part_svc_column('finger')->required ) %> <TD> <INPUT TYPE="text" NAME="finger" VALUE="<% $svc_acct->finger %>"> @@ -240,7 +242,7 @@ <% include('/elements/tr-td-label.html', - 'label' => mt('Home directory'), + 'label' => $part_svc->part_svc_column('dir')->columnlabel || mt('Home directory'), 'required' => $part_svc->part_svc_column('dir')->required ) %> <TD><INPUT TYPE="text" NAME="dir" VALUE="<% $svc_acct->dir %>"></TD> </TR> @@ -258,7 +260,7 @@ <% include('/elements/tr-td-label.html', - 'label' => mt('Shell'), + 'label' => $part_svc->part_svc_column('shell')->columnlabel || mt('Shell'), 'required' => $part_svc->part_svc_column('shell')->required ) %> <TD> <SELECT NAME="shell" SIZE=1> @@ -289,7 +291,9 @@ 'object' => $svc_acct, 'ip_field' => 'slipip', 'required' => $part_svc->part_svc_column('routernum')->required, + 'label' => $part_svc->part_svc_column('routernum')->columnlabel, 'ip_addr_required' => $part_svc->part_svc_column('slipip')->required, + 'ip_addr_label' => $part_svc->part_svc_column('slipip')->columnlabel, &> % } else { % # don't expose these to the user--they're only useful in the other case @@ -299,7 +303,7 @@ <INPUT TYPE="hidden" NAME="slipip" VALUE="<% $svc_acct->slipip %>"> % } else { <% include('/elements/tr-td-label.html', - 'label' => mt('IP'), + 'label' => $part_svc->part_svc_column('slipip')->columnlabel || mt('IP'), 'required' => $part_svc->part_svc_column('slipip')->required ) %> <TD><INPUT TYPE="text" NAME="slipip" VALUE="<% $svc_acct->slipip %>"></TD> </TR> @@ -346,7 +350,7 @@ <% include('/elements/tr-td-label.html', - 'label' => mt('RADIUS groups'), + 'label' => $part_svc->part_svc_column('usergroup')->columnlabel || mt('RADIUS groups'), 'required' => $part_svc->part_svc_column('usergroup')->required ) %> % if ( $part_svc_usergroup->columnflag eq 'F' ) { <TD BGCOLOR="#eeeeee"><% join('<BR>', @groupnames) %></TD> diff --git a/httemplate/edit/svc_acct/communigate.html b/httemplate/edit/svc_acct/communigate.html index 370bfb0e7..544d00be3 100644 --- a/httemplate/edit/svc_acct/communigate.html +++ b/httemplate/edit/svc_acct/communigate.html @@ -46,7 +46,7 @@ % if ( $part_svc->part_svc_column('quota')->columnflag eq 'F' ) { <INPUT TYPE="hidden" NAME="quota" VALUE="<% $svc_acct->quota %>"> % } else { -% my $quota_label = $communigate ? 'Mail storage limit' : 'Quota'; +% my $quota_label = $communigate ? 'Mail storage limit' : ($part_svc->part_svc_column('quota')->columnlabel || 'Quota'); <% include('/elements/tr-td-label.html', 'label' => $quota_label, 'required' => $part_svc->part_svc_column('quota')->required ) %> diff --git a/httemplate/edit/tower.html b/httemplate/edit/tower.html index fa3838dcf..377a33e9b 100644 --- a/httemplate/edit/tower.html +++ b/httemplate/edit/tower.html @@ -26,9 +26,9 @@ 'default_ip_addr' => 'Tower IP address', 'latitude' => 'Latitude', 'longitude' => 'Longitude', - 'altitude' => 'Altitude', - 'height' => 'Height', - 'veg_height' => 'Vegetation height', + 'altitude' => 'Altitude (feet)', + 'height' => 'Height (feet)', + 'veg_height' => 'Vegetation height (feet)', 'color' => 'Color', }, &> @@ -38,7 +38,7 @@ my $m2_error_callback = sub { # reconstruct the list my ($cgi, $object) = @_; my @fields = qw( - sectorname ip_addr height freq_mhz direction width sector_range + sectorname ip_addr height freq_mhz direction width tilt v_width margin sector_range ); map { diff --git a/httemplate/elements/checkboxes.html b/httemplate/elements/checkboxes.html index ad9d691b9..b07b6545f 100644 --- a/httemplate/elements/checkboxes.html +++ b/httemplate/elements/checkboxes.html @@ -27,7 +27,7 @@ Example: </%doc> -<TABLE CELLSPACING=0 CELLPADDING=0> +<TABLE CELLSPACING=0 CELLPADDING=0 <% $style %>> % unless ( $opt{'disable_links'} ) { @@ -108,4 +108,8 @@ $opt{'error_checked_callback'} ||= sub { $cgi->param($opt{'element_name_prefix'}. $name ); }; +my $style = ''; +if ($opt{'style'}) { + $style = 'STYLE="' . $opt{'style'} . '"'; +} </%init> diff --git a/httemplate/elements/commission_rate.html b/httemplate/elements/commission_rate.html new file mode 100644 index 000000000..071ebb1e3 --- /dev/null +++ b/httemplate/elements/commission_rate.html @@ -0,0 +1,68 @@ +% unless ( $opt{'js_only'} ) { + + <INPUT TYPE="hidden" NAME="<%$name%>" ID="<%$id%>" VALUE="<% $curr_value %>"> + + <& select.html, + field => "${name}_cycle", + options => [ '', 1 .. 12 ], + option_labels => { + '' => '', + 1 => '1st', + 2 => '2nd', + 3 => '3rd', + map { $_ => $_.'th' } 4 .. 12 + }, + onchange => $onchange, + curr_value => $commission_rate->get("cycle"), + &> + <B><% $money_char %></B> + <& input-text.html, + field => "${name}_amount", + size => 8, + curr_value => $commission_rate->get("amount") + || '0.00', + 'text-align' => 'right' + &> + <B> + </B> + <& input-text.html, + field => "${name}_percent", + size => 8, + curr_value => $commission_rate->get("percent") + || '0', + 'text-align' => 'right' + &><B>%</B> +% } +<%init> + +my( %opt ) = @_; + +my $conf = new FS::Conf; +my $money_char = $conf->config('money_char') || '$'; + +my $name = $opt{'field'} || 'commissionratenum'; +my $id = $opt{'id'} || 'commissionratenum'; + +my $curr_value = $opt{'curr_value'} || $opt{'value'}; + +my $onchange = ''; +if ( $opt{'onchange'} ) { + $onchange = $opt{'onchange'}; + $onchange .= '(this)' unless $onchange =~ /\(\w*\);?$/; + $onchange =~ s/\(what\);/\(this\);/g; #ugh, terrible hack. all onchange + #callbacks should act the same + $onchange = 'onChange="'. $onchange. '"'; +} + +my $commission_rate; +if ( $curr_value ) { + $commission_rate = qsearchs('commission_rate', { 'commissionratenum' => $curr_value } ); +} else { + $commission_rate = new FS::commission_rate {}; +} + +foreach my $field (qw( amount percent cycle)) { + my $value = $cgi->param("${name}_${field}"); + $commission_rate->set($field, $value) if $value; +} + +</%init> diff --git a/httemplate/elements/create_uri_query b/httemplate/elements/create_uri_query index 414d53ba4..ce6249e0e 100644 --- a/httemplate/elements/create_uri_query +++ b/httemplate/elements/create_uri_query @@ -18,7 +18,7 @@ my $query = $cgi->query_string; if ( length($query) > 1920 || $opt{secure} ) { #stupid IE 2083 URL limit - my $session = random_id(9); + my $session = int(rand(4294967296)); #XXX my $pref = new FS::access_user_pref({ 'usernum' => $FS::CurrentUser::CurrentUser->usernum, 'prefname' => "redirect$session", diff --git a/httemplate/elements/cust_payby.html b/httemplate/elements/cust_payby.html index c7d4549df..f30d18557 100644 --- a/httemplate/elements/cust_payby.html +++ b/httemplate/elements/cust_payby.html @@ -68,7 +68,7 @@ ID = "<%$id%>_paycvv" SIZE = 2 MAXLENGTH = 4 - VALUE = "<% scalar($cgi->param($name.'_paycvv')) %>" + VALUE = "<% scalar($cgi->param($name.'_paycvv')) || ('*' x length($cust_payby->paycvv)) %>" onChange = "<% $onchange %>" > <BR><FONT SIZE="-1"><% mt('CVV2') |h %> (<A HREF="javascript:void(0);" onClick="overlib( OLiframeContent('<%$p%>docs/cvv2.html', 480, 275, 'cvv2_popup' ), CAPTION, 'CVV2 Help', STICKY, AUTOSTATUSCAP, CLOSECLICK, DRAGGABLE ); return false;"><% mt('help') |h %></A>)</FONT> @@ -298,8 +298,17 @@ if ( $curr_value ) { $cust_payby = new FS::cust_payby {}; } my $sel_payby = $cgi->param($name.'_payby') || $cust_payby->payby; -$sel_payby = 'CARD' if $sel_payby eq 'DCRD' || $sel_payby eq ''; -$sel_payby = 'CHEK' if $sel_payby eq 'DCHK'; +# convert DCRD to CARD + no weight, and the same for DCHK/CHEK +if ($sel_payby eq 'DCRD') { + $sel_payby = 'CARD'; + $cust_payby->weight(''); +} elsif ($sel_payby eq 'DCHK') { + $sel_payby = 'CHEK'; + $cust_payby->weight(''); +} elsif (!$sel_payby) { + # default + $sel_payby = 'CARD'; +} my @payby = FS::payby->cust_payby; my %conf_payby = map { $_=>1 } $conf->config('payby'); diff --git a/httemplate/elements/error.html b/httemplate/elements/error.html index f65785d10..f9664bd65 100644 --- a/httemplate/elements/error.html +++ b/httemplate/elements/error.html @@ -1,4 +1,5 @@ % if ( $cgi->param('error') ) { +% $m->notes('error', $cgi->param('error')); <FONT SIZE="+1" COLOR="#ff0000"><% mt("Error: [_1]", $cgi->param('error')) |h %></FONT> <BR><BR> % } diff --git a/httemplate/elements/errorpage.html b/httemplate/elements/errorpage.html index 0f9808da0..d001bfa89 100644 --- a/httemplate/elements/errorpage.html +++ b/httemplate/elements/errorpage.html @@ -1,3 +1,5 @@ +% my $error = $_[0]; +% $m->notes('error', $error); <& /elements/header.html, mt("Error") &> % while (@_) { @@ -5,6 +7,5 @@ <P><FONT SIZE="+1" COLOR="#ff0000"><% shift |h %></FONT> %} - % $m->flush_buffer(); % $HTML::Mason::Commands::m->abort(); diff --git a/httemplate/elements/freeside-menu.css b/httemplate/elements/freeside-menu.css index a66ebc0db..365b9d443 100644 --- a/httemplate/elements/freeside-menu.css +++ b/httemplate/elements/freeside-menu.css @@ -142,3 +142,13 @@ a:visited:hover.fsdarkbutton { overflow:visible; } + +/* elements/notify-tickets.html is in the menu area */ +.dot { + border-radius: 50%; + border: 1px solid black; + width: 1ex; + height: 1ex; + display: inline-block; + margin-top: 0.3ex; +} diff --git a/httemplate/elements/freeside.css b/httemplate/elements/freeside.css index 7bf374c84..cc104a196 100644 --- a/httemplate/elements/freeside.css +++ b/httemplate/elements/freeside.css @@ -235,7 +235,7 @@ div.fstabcontainer { .fsinnerbox th { font-weight:normal; font-size:80%; - valign: bottom; + vertical-align: bottom; color: #666666; } @@ -341,3 +341,5 @@ div.package-marker-change_from { border-left: solid #bbffbb 30px; display: inline-block; } + + diff --git a/httemplate/elements/header-full.html b/httemplate/elements/header-full.html new file mode 100644 index 000000000..850eaed8c --- /dev/null +++ b/httemplate/elements/header-full.html @@ -0,0 +1,242 @@ +<%doc> + +Example: + + <& /elements/header.html', + { + 'title' => 'Title', + 'menubar' => \@menubar, + 'etc' => '', #included in <BODY> tag, for things like onLoad= + 'head' => '', #included before closing </HEAD> tag + 'nobr' => 0, #1 for no <BR><BR> after the title + 'no_jquery' => #for use from RT, which loads its own + } + &> + + %#old-style + <& /elements/header.html, 'Title', $menubar, $etc, $head &> + +</%doc> +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> +%#<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +%# above is what RT declares, should we switch now? hopefully no glitches result +%# or just fuck it, XHTML died anyway, HTML 5 or bust? +<HTML> + <HEAD> + <TITLE> + <% encode_entities($title) || $title_noescape |n %> + </TITLE> + <!-- per RT, to prevent IE compatibility mode --> + <meta http-equiv="X-UA-Compatible" content="IE=edge" /> + <!-- The X-UA-Compatible <meta> tag above must be very early in <head> --> + <META HTTP-Equiv="Cache-Control" Content="no-cache"> + <META HTTP-Equiv="Pragma" Content="no-cache"> + <META HTTP-Equiv="Expires" Content="0"> +% if ( $mobile ) { + <META NAME="viewport" content="width=device-width height=device-height user-scalable=yes"> +% } + + <% include('menu.html', 'freeside_baseurl' => $fsurl, + 'position' => $menu_position, + 'nocss' => $nocss, + 'mobile' => $mobile, + ) |n + %> + +% unless ( $no_jquery ) { + <link rel="stylesheet" href="<% $fsurl %>elements/jquery-ui.min.css"> + <SCRIPT SRC="<% $fsurl %>elements/jquery.js"></SCRIPT> + <SCRIPT SRC="<% $fsurl %>elements/jquery-ui.min.js"></SCRIPT> +% if ( $FS::CurrentUser::CurrentUser->option('printtofit') ) { + <SCRIPT SRC="<% $fsurl %>elements/printtofit.js"></SCRIPT> +% } +% } + <% include('init_overlib.html') |n %> + <% include('rs_init_object.html') |n %> + <script type="text/javascript" src="<% $fsurl %>elements/topreload.js"></script> + <% $head |n %> + +%# announce our base path, and the Mason comp path of this page + <script type="text/javascript"> + window.fsurl = <% $fsurl |js_string %>; + window.request_comp_path = <% $m->request_comp->path |js_string %>; + </script> + + </HEAD> + <BODY BGCOLOR="#f8f8f8" <% $etc |n %> STYLE="margin-top:0; margin-bottom:0; margin-left:0px; margin-right:0px"> + <table width="100%" CELLPADDING=0 CELLSPACING=0 STYLE="padding-left:0px; padding-right:4px" CLASS="fshead"> + <tr> + <td BGCOLOR="#ffffff"><% $company_url ? qq(<A HREF="$company_url">) : '' |n %><IMG BORDER=0 ALT="freeside" HEIGHT="36" SRC="<%$fsurl%>view/REAL_logo.cgi"><% $company_url ? '</A>' : '' |n %></td> + <td align=left BGCOLOR="#ffffff"> <!-- valign="top" --> + <font size=6><% $company_name || 'ExampleCo' %></font> + </td> + <td align="right" BGCOLOR="#ffffff"> + <& notify-tickets.html &> + </td> + <td align=right valign=top BGCOLOR="#ffffff"><FONT SIZE="-1">Logged in as <b><% $FS::CurrentUser::CurrentUser->username |h %> </b> <FONT SIZE="-2"><a href="<%$fsurl%>loginout/logout.html">logout</a></FONT><br></FONT><FONT SIZE="-2"><a href="<%$fsurl%>pref/pref.html" STYLE="color: #000000">Preferences</a> +% if ( $conf->config("ticket_system") +% && FS::TicketSystem->access_right(\%session, 'ModifySelf') ) { + | <a href="<%$fsurl%>rt/Prefs/Other.html" STYLE="color: #000000">Ticketing preferences</a> +% } + <BR></FONT> + </td> + </tr> + </table> + + <TABLE WIDTH="100%" CELLSPACING=0 CELLPADDING=0> + +<link href="<%$fsurl%>elements/freeside-menu.css" type="text/css" rel="stylesheet"> + +% if ( $menu_position eq 'top' ) { + + <TR CLASS="fsmenubar"> + +% if ( $mobile ) { + + <TD STYLE="padding:1px 0px 0px 0px;border-top: 1px solid #7e0079;width:auto" BGCOLOR="#dddddd"> + <SCRIPT TYPE="text/javascript"> + document.write(myBar.toString()); + </SCRIPT> + </TD> + <TD STYLE="padding:1px 0px 0px 0px;border-top: 1px solid #7e0079;width:auto" BGCOLOR="#dddddd"> + <% include('searchbar-combined.html') |n %> + </TD> + +% } else { + + <TD COLSPAN="7" WIDTH="100%" STYLE="padding:1px 0px 0px 0px;border-top: 1px solid #7e0079" BGCOLOR="#dddddd"> + <SCRIPT TYPE="text/javascript"> + document.write(myBar); + </SCRIPT> + </TD> + + </TR> + + <TR CLASS="fssearchbar"> + + <TD COLSPAN=1 BGCOLOR="#dddddd" ALIGN="right" STYLE="padding-left:2px"> + <% include('searchbar-prospect.html') |n %> + </TD> + + <TD COLSPAN=1 BGCOLOR="#dddddd" ALIGN="right" STYLE="padding-left:2px"> + <% include('searchbar-cust_main.html') |n %> + </TD> + + <TD COLSPAN=1 BGCOLOR="#dddddd" ALIGN="center"> + <% include('searchbar-address2.html') |n %> + </TD> + + <TD COLSPAN=1 BGCOLOR="#dddddd" ALIGN="right"> + <% include('searchbar-cust_bill.html') |n %> + </TD> + + <TD COLSPAN=1 BGCOLOR="#dddddd" ALIGN="right" STYLE="padding-left:2px"> + <% include('searchbar-cust_svc.html') |n %> + </TD> + + <TD COLSPAN=1 BGCOLOR="#dddddd" ALIGN="right" STYLE="padding-left:2px;padding-right:2px"> + <% include('searchbar-ticket.html') |n %> + </TD> +% } + + </TR> + </TABLE> + +% } else { #$menu_position eq 'left' + + <TR CLASS="fsmenubar"> + + <TD COLSPAN="7" WIDTH="100%" STYLE="padding:1px 0px 0px 0px;border-top: 1px solid #7e0079" BGCOLOR="#dddddd"> + </TD> + + </TR> + +% } + + + <TABLE WIDTH="100%" HEIGHT="100%" CELLSPACING=0 CELLPADDING=4> + + <TR HEIGHT="100%"> + +% if ( $menu_position eq 'left' ) { + + <TD BGCOLOR="#dddddd" ALIGN="left" HEIGHT="100%" WIDTH="154" VALIGN="top" ALIGN="right" CLASS="fsmenubar"> + <SCRIPT TYPE="text/javascript"> + document.write(myBar); + </SCRIPT> + + <BR> + <% include('searchbar-prospect.html') |n %> + <% include('searchbar-cust_main.html') |n %> + <% include('searchbar-address2.html') |n %> + <% include('searchbar-cust_bill.html') |n %> + <% include('searchbar-cust_svc.html') |n %> + <% include('searchbar-ticket.html') |n %> + + </TD> + +% } else { #$menu_position eq 'top' + <BR> +% } +%# page content starts here + <TD CLASS="background" HEIGHT="100%" VALIGN="top"> <!-- WIDTH="100%"> --> + + <H1> + <% $title_noescape || encode_entities($title) %> + </H1> + +% unless ( $nobr ) { + <BR> +% } + + <% $menubar !~ /^\s*$/ ? "$menubar<BR><BR>" : '' %> + +<%init> + +my( $title, $title_noescape, $menubar, $etc, $head ) = ( '', '', '', '', '' ); +my( $nobr, $nocss, $no_jquery ) = ( 0, 0, 0 ); + +my $mobile; + +if ( ref($_[0]) ) { + my $opt = shift; + $title = $opt->{title}; + $title_noescape = $opt->{title_noescape}; + $menubar = $opt->{menubar}; + $etc = $opt->{etc}; + $head = $opt->{head}; + $nobr = $opt->{nobr}; + $nocss = $opt->{nocss}; + $mobile = $opt->{mobile}; + $no_jquery = $opt->{no_jquery}; +} else { + ($title, $menubar) = ( shift, shift ); + $etc = @_ ? shift : ''; #$etc is for things like onLoad= etc. + $head = @_ ? shift : ''; #$head is for things that go in the <HEAD> section +} + +my $conf = new FS::Conf; + +my $curuser = $FS::CurrentUser::CurrentUser; + +my $menu_position = $curuser->option('menu_position') + || 'top'; #new default for 1.9 + +if ( !defined($mobile) ) { + $mobile = $curuser->option('mobile_menu',1) && FS::UI::Web::is_mobile(); +} +if ( $cgi->param('mobile') =~ /^(\d)$/ ) { # allow client to override + $mobile = $1; +} + +my($company_name, $company_url); +my @agentnums = $curuser->agentnums; +if ( scalar(@agentnums) == 1 ) { + $company_name = $conf->config('company_name', $agentnums[0] ); + $company_url = $conf->config('company_url', $agentnums[0] ); +} else { + $company_name = $conf->config('company_name'); + $company_url = $conf->config('company_url'); +} + +</%init> diff --git a/httemplate/elements/header-popup.html b/httemplate/elements/header-popup.html index 17593693e..839a63676 100644 --- a/httemplate/elements/header-popup.html +++ b/httemplate/elements/header-popup.html @@ -30,7 +30,11 @@ Example: <META HTTP-Equiv="Expires" Content="0"> % unless ( $no_jquery ) { <SCRIPT SRC="<% $fsurl %>elements/jquery.js"></SCRIPT> +% if ( $FS::CurrentUser::CurrentUser->option('printtofit') ) { + <SCRIPT SRC="<% $fsurl %>elements/printtofit.js"></SCRIPT> +% } % } + <SCRIPT SRC="<% $fsurl %>elements/topreload.js"></SCRIPT> <% $head |n %> </HEAD> <BODY <% $etc |n %>> diff --git a/httemplate/elements/header.html b/httemplate/elements/header.html index c8a83ca7e..c6b10e301 100644 --- a/httemplate/elements/header.html +++ b/httemplate/elements/header.html @@ -1,228 +1,6 @@ -<%doc> - -Example: - - <& /elements/header.html', - { - 'title' => 'Title', - 'menubar' => \@menubar, - 'etc' => '', #included in <BODY> tag, for things like onLoad= - 'head' => '', #included before closing </HEAD> tag - 'nobr' => 0, #1 for no <BR><BR> after the title - 'no_jquery' => #for use from RT, which loads its own - } - &> - - %#old-style - <& /elements/header.html, 'Title', $menubar, $etc, $head &> - -</%doc> -<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> -%#<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> -%# above is what RT declares, should we switch now? hopefully no glitches result -%# or just fuck it, XHTML died anyway, HTML 5 or bust? -<HTML> - <HEAD> - <TITLE> - <% encode_entities($title) || $title_noescape |n %> - </TITLE> - <!-- per RT, to prevent IE compatibility mode --> - <meta http-equiv="X-UA-Compatible" content="IE=edge" /> - <!-- The X-UA-Compatible <meta> tag above must be very early in <head> --> - <META HTTP-Equiv="Cache-Control" Content="no-cache"> - <META HTTP-Equiv="Pragma" Content="no-cache"> - <META HTTP-Equiv="Expires" Content="0"> -% if ( $mobile ) { - <META NAME="viewport" content="width=device-width height=device-height user-scalable=yes"> +% # for testing, disable the page menus/search boxes +% if ( $FS::CurrentUser::CurrentUser->option('header-minimal') ) { +<& header-minimal.html, @_ &> +% } else { +<& header-full.html, @_ &> % } - - <% include('menu.html', 'freeside_baseurl' => $fsurl, - 'position' => $menu_position, - 'nocss' => $nocss, - 'mobile' => $mobile, - ) |n - %> - -% unless ( $no_jquery ) { - <link rel="stylesheet" href="<% $fsurl %>elements/jquery-ui.min.css"> - <SCRIPT SRC="<% $fsurl %>elements/jquery.js"></SCRIPT> - <SCRIPT SRC="<% $fsurl %>elements/jquery-ui.min.js"></SCRIPT> -% } - <% include('init_overlib.html') |n %> - <% include('rs_init_object.html') |n %> - - <% $head |n %> - - </HEAD> - <BODY BGCOLOR="#f8f8f8" <% $etc |n %> STYLE="margin-top:0; margin-bottom:0; margin-left:0px; margin-right:0px"> - <table width="100%" CELLPADDING=0 CELLSPACING=0 STYLE="padding-left:0px; padding-right:4px" CLASS="fshead"> - <tr> - <td BGCOLOR="#ffffff"><% $company_url ? qq(<A HREF="$company_url">) : '' |n %><IMG BORDER=0 ALT="freeside" HEIGHT="36" SRC="<%$fsurl%>view/REAL_logo.cgi"><% $company_url ? '</A>' : '' |n %></td> - <td align=left BGCOLOR="#ffffff"> <!-- valign="top" --> - <font size=6><% $company_name || 'ExampleCo' %></font> - </td> - <td align=right valign=top BGCOLOR="#ffffff"><FONT SIZE="-1">Logged in as <b><% $FS::CurrentUser::CurrentUser->username |h %> </b> <FONT SIZE="-2"><a href="<%$fsurl%>loginout/logout.html">logout</a></FONT><br></FONT><FONT SIZE="-2"><a href="<%$fsurl%>pref/pref.html" STYLE="color: #000000">Preferences</a> -% if ( $conf->config("ticket_system") -% && FS::TicketSystem->access_right(\%session, 'ModifySelf') ) { - | <a href="<%$fsurl%>rt/Prefs/Other.html" STYLE="color: #000000">Ticketing preferences</a> -% } - <BR></FONT> - </td> - </tr> - </table> - - <TABLE WIDTH="100%" CELLSPACING=0 CELLPADDING=0> - -<link href="<%$fsurl%>elements/freeside-menu.css" type="text/css" rel="stylesheet"> - -% if ( $menu_position eq 'top' ) { - - <TR CLASS="fsmenubar"> - -% if ( $mobile ) { - - <TD STYLE="padding:1px 0px 0px 0px;border-top: 1px solid #7e0079;width:auto" BGCOLOR="#dddddd"> - <SCRIPT TYPE="text/javascript"> - document.write(myBar.toString()); - </SCRIPT> - </TD> - <TD STYLE="padding:1px 0px 0px 0px;border-top: 1px solid #7e0079;width:auto" BGCOLOR="#dddddd"> - <% include('searchbar-combined.html') |n %> - </TD> - -% } else { - - <TD COLSPAN="7" WIDTH="100%" STYLE="padding:1px 0px 0px 0px;border-top: 1px solid #7e0079" BGCOLOR="#dddddd"> - <SCRIPT TYPE="text/javascript"> - document.write(myBar); - </SCRIPT> - </TD> - - </TR> - - <TR CLASS="fssearchbar"> - - <TD COLSPAN=1 BGCOLOR="#dddddd" ALIGN="right" STYLE="padding-left:2px"> - <% include('searchbar-prospect.html') |n %> - </TD> - - <TD COLSPAN=1 BGCOLOR="#dddddd" ALIGN="right" STYLE="padding-left:2px"> - <% include('searchbar-cust_main.html') |n %> - </TD> - - <TD COLSPAN=1 BGCOLOR="#dddddd" ALIGN="center"> - <% include('searchbar-address2.html') |n %> - </TD> - - <TD COLSPAN=1 BGCOLOR="#dddddd" ALIGN="right"> - <% include('searchbar-cust_bill.html') |n %> - </TD> - - <TD COLSPAN=1 BGCOLOR="#dddddd" ALIGN="right" STYLE="padding-left:2px"> - <% include('searchbar-cust_svc.html') |n %> - </TD> - - <TD COLSPAN=1 BGCOLOR="#dddddd" ALIGN="right" STYLE="padding-left:2px;padding-right:2px"> - <% include('searchbar-ticket.html') |n %> - </TD> -% } - - </TR> - </TABLE> - -% } else { #$menu_position eq 'left' - - <TR CLASS="fsmenubar"> - - <TD COLSPAN="7" WIDTH="100%" STYLE="padding:1px 0px 0px 0px;border-top: 1px solid #7e0079" BGCOLOR="#dddddd"> - </TD> - - </TR> - -% } - - - <TABLE WIDTH="100%" HEIGHT="100%" CELLSPACING=0 CELLPADDING=4> - - <TR HEIGHT="100%"> - -% if ( $menu_position eq 'left' ) { - - <TD BGCOLOR="#dddddd" ALIGN="left" HEIGHT="100%" WIDTH="154" VALIGN="top" ALIGN="right" CLASS="fsmenubar"> - <SCRIPT TYPE="text/javascript"> - document.write(myBar); - </SCRIPT> - - <BR> - <% include('searchbar-prospect.html') |n %> - <% include('searchbar-cust_main.html') |n %> - <% include('searchbar-address2.html') |n %> - <% include('searchbar-cust_bill.html') |n %> - <% include('searchbar-cust_svc.html') |n %> - <% include('searchbar-ticket.html') |n %> - - </TD> - -% } else { #$menu_position eq 'top' - <BR> -% } -%# page content starts here - <TD CLASS="background" HEIGHT="100%" VALIGN="top"> <!-- WIDTH="100%"> --> - - <H1> - <% $title_noescape || encode_entities($title) %> - </H1> - -% unless ( $nobr ) { - <BR> -% } - - <% $menubar !~ /^\s*$/ ? "$menubar<BR><BR>" : '' %> -<%init> - -my( $title, $title_noescape, $menubar, $etc, $head ) = ( '', '', '', '', '' ); -my( $nobr, $nocss, $no_jquery ) = ( 0, 0, 0 ); - -my $mobile; - -if ( ref($_[0]) ) { - my $opt = shift; - $title = $opt->{title}; - $title_noescape = $opt->{title_noescape}; - $menubar = $opt->{menubar}; - $etc = $opt->{etc}; - $head = $opt->{head}; - $nobr = $opt->{nobr}; - $nocss = $opt->{nocss}; - $mobile = $opt->{mobile}; - $no_jquery = $opt->{no_jquery}; -} else { - ($title, $menubar) = ( shift, shift ); - $etc = @_ ? shift : ''; #$etc is for things like onLoad= etc. - $head = @_ ? shift : ''; #$head is for things that go in the <HEAD> section -} - -my $conf = new FS::Conf; - -my $curuser = $FS::CurrentUser::CurrentUser; - -my $menu_position = $curuser->option('menu_position') - || 'top'; #new default for 1.9 - -if ( !defined($mobile) ) { - $mobile = $curuser->option('mobile_menu',1) && FS::UI::Web::is_mobile(); -} -if ( $cgi->param('mobile') =~ /^(\d)$/ ) { # allow client to override - $mobile = $1; -} - -my($company_name, $company_url); -my @agentnums = $curuser->agentnums; -if ( scalar(@agentnums) == 1 ) { - $company_name = $conf->config('company_name', $agentnums[0] ); - $company_url = $conf->config('company_url', $agentnums[0] ); -} else { - $company_name = $conf->config('company_name'); - $company_url = $conf->config('company_url'); -} -</%init> diff --git a/httemplate/elements/location.html b/httemplate/elements/location.html index b5f0a963c..3c8e973ed 100644 --- a/httemplate/elements/location.html +++ b/httemplate/elements/location.html @@ -245,13 +245,13 @@ Example: % } % if ( $opt{enable_district} and $conf->config('tax_district_method') ) { <TR> - <TD ALIGN="right">Tax district</TD> + <TH ALIGN="right">Tax district</TH> <TD COLSPAN=8> <INPUT TYPE="text" SIZE=15 NAME="<%$pre%>district" ID="<%$pre%>district" VALUE="<% $object->district |h %>"> - <% '(automatic)' %> + <FONT SIZE="-1" COLOR="#333333"><% '(automatic)' %></FONT> </TD> </TR> % } else { diff --git a/httemplate/elements/menu.html b/httemplate/elements/menu.html index c385cb4c7..51ec26329 100644 --- a/httemplate/elements/menu.html +++ b/httemplate/elements/menu.html @@ -275,6 +275,7 @@ $report_packages{'Suspension summary'} = [ $fsurl.'search/cust_pkg_susp.html', ' $report_packages{'Customer packages with unconfigured services'} = [ $fsurl.'search/cust_pkg.cgi?APKG_pkgnum', 'List packages which have provisionable services' ]; $report_packages{'FCC Form 477'} = [ $fsurl.'search/report_477.html' ] if $conf->exists('part_pkg-show_fcc_options'); +$report_packages{'Contract end dates'} = [ $fsurl.'search/cust_pkg-date.html?date=contract_end', 'Show packages by contract end date' ]; $report_packages{'Advanced package reports'} = [ $fsurl.'search/report_cust_pkg.html', 'by agent, date range, status, package definition' ]; tie my %report_inventory, 'Tie::IxHash', @@ -320,7 +321,6 @@ tie my %report_ticketing, 'Tie::IxHash', ; tie my %report_bill_event, 'Tie::IxHash', - 'All billing events' => [ $fsurl.'search/report_cust_event.html', 'All billing events for a date range' ], 'Billing event errors' => [ $fsurl.'search/report_cust_event.html?failed=1', 'Failed credit cards, processor or printer problems, etc.' ], ; @@ -365,7 +365,8 @@ tie my %report_commissions, 'Tie::IxHash', 'Agent per package' => [ $fsurl.'search/report_agent_commission_pkg.html' ], 'Sales Person' => [ $fsurl.'search/report_sales_commission.html' ], 'Sales Person per package' => [ $fsurl.'search/report_sales_commission_pkg.html' ], - 'Employee' => [ $fsurl.'search/report_employee_commission.html', '' ] + 'Employee' => [ $fsurl.'search/report_employee_commission.html', '' ], + 'Agent Credits and Payments' => [ $fsurl.'search/report_agent_credit_payment.html' ], ; tie my %report_financial, 'Tie::IxHash'; @@ -404,7 +405,7 @@ tie my %report_payable, 'Tie::IxHash', ; tie my %report_logs, 'Tie::IxHash'; -$report_logs{'Billing events'} = [ \%report_bill_event, 'Billing events' ] +$report_logs{'Billing events'} = [ $fsurl.'search/report_cust_event.html', 'Search billing events by date and status' ] if $curuser->access_right('Billing event reports'); $report_logs{'Credit limit incidents'} = [ $fsurl.'search/report_cust_main_credit_limit.html', '' ] if $curuser->access_right('List rating data'); @@ -672,7 +673,10 @@ $config_cust{'Note classes'} = [ $fsurl.'browse/cust_note_class.html', 'Note cla tie my %config_agent, 'Tie::IxHash', 'Agent types' => [ $fsurl.'browse/agent_type.cgi', 'Agent types define groups of package definitions that you can then assign to particular agents' ], 'Agents' => [ $fsurl.'browse/agent.cgi', 'Agents are resellers of your service. Agents may be limited to a subset of your full offerings (via their type)' ], - 'Agent payment gateways' => [ $fsurl.'browse/payment_gateway.html', 'Credit card and electronic check processors for agent overrides' ]; + 'Agent payment gateways' => [ $fsurl.'browse/payment_gateway.html', 'Credit card and electronic check processors for agent overrides' ], + 'separator' => '', + 'Commission schedules' => [ $fsurl.'browse/commission_schedule.html', + 'Commission schedules for consecutive billing periods' ], ; tie my %config_sales, 'Tie::IxHash', diff --git a/httemplate/elements/notify-tickets.html b/httemplate/elements/notify-tickets.html new file mode 100644 index 000000000..e661737bc --- /dev/null +++ b/httemplate/elements/notify-tickets.html @@ -0,0 +1,27 @@ +% if ($enabled) { +<div style="font-weight: bold; vertical-align: bottom; text-align: left"> +% if ( $UnrepliedTickets->Count > 0 ) { + <a href="<% $fsurl %>rt/Search/UnrepliedTickets.html"> + <div class="dot" style="background-color: green"></div> + <% emt('New activity on [quant,_1,ticket]', $UnrepliedTickets->Count) %> + </a> +% } else { + <% emt('No new activity on tickets') %> +% } +</div> +% } +<%init> +use Class::Load 'load_class'; + +my $enabled = $FS::TicketSystem::system eq 'RT_Internal'; +my $UnrepliedTickets; +if ($enabled) { + my $class = 'RT::Search::UnrepliedTickets'; + load_class($class); + my $session = FS::TicketSystem->session; + my $CurrentUser = $session->{CurrentUser}; + $UnrepliedTickets = RT::Tickets->new($CurrentUser); + my $search = $class->new(TicketsObj => $UnrepliedTickets); + $search->Prepare; +} +</%init> diff --git a/httemplate/elements/page_pref.js b/httemplate/elements/page_pref.js new file mode 100644 index 000000000..253c6da86 --- /dev/null +++ b/httemplate/elements/page_pref.js @@ -0,0 +1,10 @@ +function set_page_pref(prefname, tablenum, prefvalue, success) { + jQuery.post( window.fsurl + 'misc/process/set_page_pref.html', + { path: window.request_comp_path, + name: prefname, + num: tablenum, + value: prefvalue + }, + success + ); +} diff --git a/httemplate/elements/printtofit.js b/httemplate/elements/printtofit.js new file mode 100644 index 000000000..66257fca8 --- /dev/null +++ b/httemplate/elements/printtofit.js @@ -0,0 +1,26 @@ +$().ready(function() { + var beforePrint = function() { + if ($('body').width() > 0) { + // 7.5 inches * 96 DPI; maybe make the width a user pref? + var maxwidth = 7.5 * 96; + $('body').css('zoom', maxwidth / $('body').width()); + } + }; + var afterPrint = function() { + $('body').css('zoom', 1); + } + + if (window.matchMedia) { // chrome, most importantly; also IE10? + window.matchMedia('print').addListener( + function(mq) { + mq.matches ? beforePrint() : afterPrint(); + } + ); + } else { // other IE + $(window).on('beforeprint', beforePrint); + $(window).on('afterprint', afterPrint); + } + // got nothing for firefox + // https://bugzilla.mozilla.org/show_bug.cgi?id=774398 + // but firefox already has "shrink to fit" +}); diff --git a/httemplate/elements/progress-init.html b/httemplate/elements/progress-init.html index e38dde65f..0c2b8165a 100644 --- a/httemplate/elements/progress-init.html +++ b/httemplate/elements/progress-init.html @@ -98,14 +98,14 @@ function <%$key%>process () { overlib( 'Submitting job to server...', WIDTH, 444, HEIGHT, 168, CAPTION, 'Please wait...', STICKY, AUTOSTATUSCAP, CLOSETEXT, '', CLOSECLICK, MIDX, 0, MIDY, 0 ); + // jQuery .serializeArray() maybe? + var copy_fields = <% encode_json(\%copy_fields) %>; var Hash = new Array(); var x = 0; var fieldName; for (var i = 0; i<document.<%$formname%>.elements.length; i++) { field = document.<%$formname%>.elements[i]; - if ( <% join(' || ', map { "(field.name.indexOf('$_') > -1)" } @$fields ) %> - ) - { + if ( <% $all_fields %> || copy_fields[ field.name ] ) { if ( field.type == 'select-multiple' ) { //alert('select-multiple ' + field.name); for (var j=0; j < field.options.length; j++) { @@ -168,6 +168,14 @@ $progress_url->query_form( %dest_info, ); +my $all_fields = 0; +my %copy_fields; +if (grep '/^ALL$/', @$fields) { + $all_fields = 1; +} else { + %copy_fields = map { $_ => 1 } @$fields; +} + #stupid safari is caching the "location" of popup iframs, and submitting them #instead of displaying them. this should prevent that. my $popup_name = 'popup-'.random_id(); diff --git a/httemplate/elements/select-areacode.html b/httemplate/elements/select-areacode.html index f0f56d56d..612f03a8b 100644 --- a/httemplate/elements/select-areacode.html +++ b/httemplate/elements/select-areacode.html @@ -28,6 +28,7 @@ function <% $opt{'prefix'} %>update_areacodes(areacodes) { + var reply = JSON.parse(areacodes); // blank the current areacode for ( var i = what.form.<% $opt{'prefix'} %>areacode.length; i >= 0; i-- ) what.form.<% $opt{'prefix'} %>areacode.options[i] = null; @@ -47,7 +48,7 @@ % } // add the new areacodes - var areacodeArray = eval('(' + areacodes + ')' ); + var areacodeArray = reply.areacodes; for ( var s = 0; s < areacodeArray.length; s++ ) { var areacodeLabel = areacodeArray[s]; if ( areacodeLabel == "" ) @@ -62,6 +63,11 @@ } else { var areacodeerror = document.getElementById('<% $opt{'prefix'} %>areacodeerror'); areacodeerror.style.display = 'inline'; + if ( reply.error ) { + areacodeerror.textContent = reply.error; + } else { + areacodeerror.textContent = 'Select a different state'; + } } //run the callback @@ -78,7 +84,7 @@ <DIV ID="areacodewait" STYLE="display:none"><IMG SRC="<%$fsurl%>images/wait-orange.gif"> <B>Finding area codes</B></DIV> -<DIV ID="areacodeerror" STYLE="display:none"><IMG SRC="<%$fsurl%>images/cross.png"> <B>Select a different state</B></DIV> +<DIV ID="areacodeerror" STYLE="display:none; font-weight: bold"><IMG SRC="<%$fsurl%>images/cross.png"></DIV> <SELECT NAME="<% $opt{'prefix'} %>areacode" onChange="<% $opt{'prefix'} %>areacode_changed(this); <% $opt{'onchange'} %>" <% $opt{'disabled'} %>> <OPTION VALUE="">Select area code</OPTION> diff --git a/httemplate/elements/select-exchange.html b/httemplate/elements/select-exchange.html index b9677094a..33def31b9 100644 --- a/httemplate/elements/select-exchange.html +++ b/httemplate/elements/select-exchange.html @@ -27,6 +27,7 @@ function <% $opt{'prefix'} %>update_exchanges(exchanges) { + var reply = JSON.parse(exchanges); // blank the current exchange for ( var i = what.form.<% $opt{'prefix'} %>exchange.length; i >= 0; i-- ) what.form.<% $opt{'prefix'} %>exchange.options[i] = null; @@ -42,7 +43,7 @@ % } // add the new exchanges - var exchangeArray = eval('(' + exchanges + ')' ); + var exchangeArray = reply.exchanges; for ( var s = 0; s < exchangeArray.length; s++ ) { var exchangeLabel = exchangeArray[s]; if ( exchangeLabel == "" ) @@ -57,6 +58,12 @@ } else { var exchangeerror = document.getElementById('<% $opt{'prefix'} %>exchangeerror'); exchangeerror.style.display = 'inline'; + if ( reply.error ) { + exchangeerror.textContent = reply.error; + } else { + exchangeerror.textContent = 'Select a different area code'; + } + } //run the callback @@ -73,7 +80,7 @@ <DIV ID="exchangewait" STYLE="display:none"><IMG SRC="<%$fsurl%>images/wait-orange.gif"> <B>Finding cities / exchanges</B></DIV> -<DIV ID="exchangeerror" STYLE="display:none"><IMG SRC="<%$fsurl%>images/cross.png"> <B>Select a different area code</B></DIV> +<DIV ID="exchangeerror" STYLE="display:none; font-weight: bold"><IMG SRC="<%$fsurl%>images/cross.png"></DIV> <SELECT NAME="<% $opt{'prefix'} %>exchange" onChange="<% $opt{'prefix'} %>exchange_changed(this); <% $opt{'onchange'} %>" <% $opt{'disabled'} %>> <OPTION VALUE="">Select city / exchange</OPTION> diff --git a/httemplate/elements/select-phonenum.html b/httemplate/elements/select-phonenum.html index 118fe4901..dd1b84736 100644 --- a/httemplate/elements/select-phonenum.html +++ b/httemplate/elements/select-phonenum.html @@ -56,6 +56,7 @@ passing the exchange (or region) and function <% $opt{'prefix'} %>update_phonenums(phonenums) { + var reply = JSON.parse(phonenums); // blank the current phonenum for ( var i = what.form.<% $opt{'prefix'} %>phonenum.length; i >= 0; i-- ) what.form.<% $opt{'prefix'} %>phonenum.options[i] = null; @@ -67,7 +68,7 @@ passing the exchange (or region) and % } // add the new phonenums - var phonenumArray = eval('(' + phonenums + ')' ); + var phonenumArray = reply.phonenums; for ( var s = 0; s < phonenumArray.length; s++ ) { var phonenumLabel = phonenumArray[s]; if ( phonenumLabel == "" ) @@ -86,6 +87,11 @@ passing the exchange (or region) and } else { var phonenumerror = document.getElementById('<% $opt{'prefix'} %>phonenumerror'); phonenumerror.style.display = 'inline'; + if ( reply.error ) { + phonenumerror.textContent = reply.error; + } else { + phonenumerror.textContent = 'Select a different <% $opt{'region'} ? 'region' : 'city/exchange' %>'; + } } //run the callback @@ -157,7 +163,7 @@ passing the exchange (or region) and % unless ( $opt{'tollfree'} ) { <DIV ID="phonenumwait" STYLE="display:none"><IMG SRC="<%$fsurl%>images/wait-orange.gif"> <B>Finding phone numbers</B></DIV> -<DIV ID="phonenumerror" STYLE="display:none"><IMG SRC="<%$fsurl%>images/cross.png"> <B>Select a different <% $opt{'region'} ? 'region' : 'city/exchange' %></B></DIV> +<DIV ID="phonenumerror" STYLE="display:none; font-weight: bold"><IMG SRC="<%$fsurl%>images/cross.png"></DIV> % } <SELECT <% $opt{multiple} ? 'MULTIPLE SIZE=25' : '' %> diff --git a/httemplate/elements/select-region.html b/httemplate/elements/select-region.html index 7ed959269..46c37c935 100644 --- a/httemplate/elements/select-region.html +++ b/httemplate/elements/select-region.html @@ -27,6 +27,7 @@ function <% $opt{'prefix'} %>update_regions(regions) { + var reply = JSON.parse(regions); // blank the current region for ( var i = what.form.<% $opt{'prefix'} %>region.length; i >= 0; i-- ) what.form.<% $opt{'prefix'} %>region.options[i] = null; @@ -42,7 +43,7 @@ % } // add the new regions - var regionArray = eval('(' + regions + ')' ); + var regionArray = reply.regions; for ( var s = 0; s < regionArray.length; s++ ) { var regionLabel = regionArray[s]; if ( regionLabel == "" ) @@ -57,6 +58,11 @@ } else { var regionerror = document.getElementById('<% $opt{'prefix'} %>regionerror'); regionerror.style.display = 'inline'; + if ( reply.error ) { + regionerror.textContent = reply.error; + } else { + regionerror.textContent = 'Select a different state'; + } } //run the callback @@ -73,7 +79,7 @@ <DIV ID="<% $opt{'prefix'} %>regionwait" STYLE="display:none"><IMG SRC="<%$fsurl%>images/wait-orange.gif"> <B>Finding regions</B></DIV> -<DIV ID="<% $opt{'prefix'} %>regionerror" STYLE="display:none"><IMG SRC="<%$fsurl%>images/cross.png"> <B>Select a different state</B></DIV> +<DIV ID="<% $opt{'prefix'} %>regionerror" STYLE="display:none; font-weight: bold"><IMG SRC="<%$fsurl%>images/cross.png"></DIV> <SELECT NAME="<% $opt{'prefix'} %>region" onChange="<% $opt{'prefix'} %>region_changed(this); <% $opt{'onchange'} %>" <% $opt{'disabled'} %>> <OPTION VALUE="">Select region</OPTION> diff --git a/httemplate/elements/select-rt-customfield.html b/httemplate/elements/select-rt-customfield.html index 85758d585..488accac3 100644 --- a/httemplate/elements/select-rt-customfield.html +++ b/httemplate/elements/select-rt-customfield.html @@ -1,31 +1,27 @@ -<SELECT NAME="<% $opt{name} %>"> +<SELECT NAME="<% $opt{'name'} %>"<% $opt{'multiple'} ? ' MULTIPLE' : '' %>> % while ( @fields ) { -<OPTION VALUE="<% shift @fields %>"><% shift @fields %></OPTION> +% my $value = shift @fields; +% my $label = shift @fields; +<OPTION VALUE="<% $value %>"<% $curr_value{$value} ? ' SELECTED' : '' %>><% $label %></OPTION> % } </SELECT> <%init> my %opt = @_; -my $lookuptype = $opt{lookuptype}; -my $valuetype = $opt{valuetype}; -# get a list of TimeValue-type custom fields -my $CurrentUser = RT::CurrentUser->new(); -$CurrentUser->LoadByName($FS::CurrentUser::CurrentUser->username); -die "RT not configured" unless $CurrentUser->id; -my $CFs = RT::CustomFields->new($CurrentUser); -$CFs->Limit(FIELD => 'LookupType', - OPERATOR => 'ENDSWITH', - VALUE => $lookuptype) - if $lookuptype; - -$CFs->Limit(FIELD => 'Type', - VALUE => $valuetype) - if $valuetype; +my %curr_value = map { $_ => 1 } split(', ',$opt{'curr_value'}); my @fields; push @fields, '', $opt{empty_label} if exists($opt{empty_label}); -while (my $CF = $CFs->Next) { - push @fields, $CF->Name, ($CF->Description || $CF->Name); +my $conf = new FS::Conf; + +if ($conf->config('ticket_system') eq 'RT_Internal') { + + push @fields, FS::TicketSystem->custom_fields( + lookuptype => $opt{lookuptype}, + valuetype => $opt{valuetype}, + ); + } + </%init> diff --git a/httemplate/elements/select-rt-queue.html b/httemplate/elements/select-rt-queue.html new file mode 100644 index 000000000..289336516 --- /dev/null +++ b/httemplate/elements/select-rt-queue.html @@ -0,0 +1,24 @@ +<SELECT NAME="<% $opt{'name'} || $opt{'field'} %>"<% $opt{'multiple'} ? ' MULTIPLE' : '' %>> +% while ( @fields ) { +% my $value = shift @fields; +% my $label = shift @fields; +<OPTION VALUE="<% $value %>"<% $curr_value{$value} ? ' SELECTED' : '' %>><% $label %></OPTION> +% } +</SELECT> +<%init> +my %opt = @_; + +my %curr_value = map { $_ => 1 } split(', ',$opt{'curr_value'}); + +my @fields; +push @fields, '', $opt{empty_label} if exists($opt{empty_label}); + +my $conf = new FS::Conf; + +if ($conf->config('ticket_system') eq 'RT_Internal') { + + push @fields, FS::TicketSystem->queues(); + +} + +</%init> diff --git a/httemplate/elements/select-svc.html b/httemplate/elements/select-svc.html new file mode 100644 index 000000000..b439a2852 --- /dev/null +++ b/httemplate/elements/select-svc.html @@ -0,0 +1,73 @@ +<%init> +my %opt = @_; +my $svcdb = $opt{table}; +my $field = $opt{field} || 'svcnum'; +my $id = $opt{id} || $opt{field}; +my $curr_value = [ split(',', $opt{curr_value} || '') ]; +my $label = $opt{name_col} || 'label'; + +# cut-down, jquerified version of select-tiered +# XXX do we need to agent-virt this? edit/part_svc is Configuration-access. +my @part_svc = qsearch('part_svc', { + disabled => '', + svcdb => $svcdb +}); +my %labels; # service labels, of some kind +my %values; # svcnums +my (@all_l, @all_v); +foreach my $part_svc (@part_svc) { + my (@l, @v); + foreach my $svc_x (qsearch({ + 'table' => 'cust_svc', + 'addl_from' => " JOIN $svcdb USING (svcnum)", + 'select' => "$svcdb.*, cust_svc.svcpart", + 'hashref' => { 'svcpart' => $part_svc->svcpart }, + })) + { + push @l, $svc_x->$label; + push @all_l, $svc_x->$label; + push @v, $svc_x->svcnum; + push @all_v, $svc_x->svcnum; + } + $labels{ $part_svc->svcpart } = \@l; + $values{ $part_svc->svcpart } = \@v; +} +$labels{ '' } = \@all_l; +$values{ '' } = \@all_v; + +</%init> +<& /elements/select-table.html, + 'table' => 'part_svc', + 'records' => \@part_svc, + 'id' => $id.'_svcpart', + 'name_col' => 'svc', + 'empty_label' => 'any', + 'curr_value' => '', + 'field' => $id.'_svcpart', # avoid confusion with any other field +&> +<BR> +<& /elements/select.html, + %opt, + 'field' => $field, + 'id' => $id, +&> +<script> +$().ready(function() { + var labels = <% encode_json(\%labels) %>; + var values = <% encode_json(\%values) %>; + var select_svcpart = $('#<% $id.'_svcpart' %>'); + var select_svcnum = $('#<% $id %>'); + var onchange_svcpart = function() { + var l = labels[ this.value ]; + var v = values[ this.value ]; + select_svcnum.empty(); + for (var i = 0; i < v.length; i++) { + var opt = $('<option />').val(v[i]).text(l[i]); + select_svcnum.append(opt); + } + }; + select_svcpart.on('change', onchange_svcpart); + select_svcpart.change(); + select_svcnum.val(<% encode_json($curr_value) %>); +}); +</script> diff --git a/httemplate/elements/select-terms.html b/httemplate/elements/select-terms.html index a330df17c..eda439a4c 100644 --- a/httemplate/elements/select-terms.html +++ b/httemplate/elements/select-terms.html @@ -34,10 +34,7 @@ my $empty_label = my $empty_value = $opt{'empty_value'} || ''; -my @terms = ( emt('Payable upon receipt'), - ( map "Net $_", - 0, 3, 5, 7, 9, 10, 14, 15, 18, 20, 21, 25, 30, 45, 60, 90 ), - ); +my @terms = map emt($_), @FS::Conf::invoice_terms; my @pre_options = $opt{pre_options} ? @{ $opt{pre_options} } : (); diff --git a/httemplate/elements/select.html b/httemplate/elements/select.html index 42cd89504..3a0dc5b68 100644 --- a/httemplate/elements/select.html +++ b/httemplate/elements/select.html @@ -21,6 +21,7 @@ disabled => 0, onchange => 'do_something()', js_only => 0, # disables the whole thing + element_etc => '', # anything else to put in the <select> tag &> </%doc> @@ -35,6 +36,7 @@ <% $style %> <% $opt{disabled} %> <% $onchange %> + <% $opt{'element_etc'} %> > % if ( $opt{options} ) { diff --git a/httemplate/elements/selectlayersx.html b/httemplate/elements/selectlayersx.html new file mode 100644 index 000000000..41f3cb0b7 --- /dev/null +++ b/httemplate/elements/selectlayersx.html @@ -0,0 +1,248 @@ +<%doc> + +Example: + + include( '/elements/selectlayers.html', + 'field' => $key, # SELECT element NAME (passed as form field) + # also used as ID and a unique key for layers and + # functions + 'curr_value' => $selected_layer, + 'options' => [ 'option1', 'option2' ], + 'labels' => { 'option1' => 'Option 1 Label', + 'option2' => 'Option 2 Label', + }, + + #XXX put this handling it its own selectlayers-fields.html element? + 'layer_prefix' => 'prefix_', #optional prefix for fieldnames + 'layer_fields' => { 'layer' => [ 'fieldname', + { label => 'fieldname2', + type => 'text', #implemented: + # text, money, fixed, + # hidden, checkbox, + # checkbox-multiple, + # select, select-agent, + # select-pkg_class, + # select-part_referral, + # select-taxclass, + # select-table, + #XXX tbd: + # more? + }, + ... + ], + 'layer2' => [ 'l2fieldname', + ... + ], + }, + + #current values for layer fields above + 'layer_values' => { 'layer' => { 'fieldname' => 'current_value', + 'fieldname2' => 'field2value', + ... + }, + 'layer2' => { 'l2fieldname' => 'l2value', + ... + }, + ... + }, + + #or manual control, instead of layer_fields and layer_values above + #called with args: my( $layer, $layer_fields, $layer_values, $layer_prefix ) + 'layer_callback' => + + 'html_between => '', #optional HTML displayed between the SELECT and the + #layers, scalar or coderef ('field' passed as a param) + 'onchange' => '', #javascript code run when the SELECT changes + # ("what" is the element) + 'js_only' => 0, #set true to return only the JS portions + 'html_only' => 0, #set true to return only the HTML portions + 'select_only' => 0, #set true to return only the <SELECT> HTML + 'layers_only' => 0, #set true to return only the layers <DIV> HTML + ) + +</%doc> +% unless ( grep $opt{$_}, qw(html_only js_only select_only layers_only) ) { +<SCRIPT TYPE="text/javascript"> +% } +% unless ( grep $opt{$_}, qw(html_only select_only layers_only) ) { + +% unless ($selectlayersx_init) { + +var selectlayerx_info = {}; + +function selectlayersx_changed (field) { + + var what = document.getElementById(field); + selectlayerx_info[field]['onchange'](what); + + var selectedlayer = what.options[what.selectedIndex].value; + for (i=0; i < selectlayerx_info[field]['layers'].length; i++) { + var iterlayer = selectlayerx_info[field]['layers'][i]; + var iterobj = document.getElementById(field+'d'+iterlayer); + if (selectedlayer == iterlayer) { + iterobj.style.display = ""; + iterobj.style.zIndex = 1; + } else { + iterobj.style.display = "none"; + iterobj.style.zIndex = 0; + } + } + +} + +% $selectlayersx_init = 1; +% } #selectlayersx_init + +selectlayerx_info['<% $key %>'] = {}; +selectlayerx_info['<% $key %>']['onchange'] = function (what) { <% $opt{'onchange'} %> }; +selectlayerx_info['<% $key %>']['layers'] = <% encode_json(\@layers) %>; + + +% } #unless html_only/select_only/layers_only +% unless ( grep $opt{$_}, qw(html_only js_only select_only layers_only) ) { +</SCRIPT> +% } +% +% unless ( grep $opt{$_}, qw(js_only layers_only) ) { + + <SELECT NAME = "<% $key %>" + ID = "<% $key %>" + previousValue = "<% $selected %>" + previousText = "<% $options{$selected} %>" + onChange="selectlayersx_changed('<% $key %>')" + > + +% foreach my $option ( keys %$options ) { + + <OPTION VALUE="<% $option %>" + <% $option eq $selected ? ' SELECTED' : '' %> + ><% $options->{$option} |h %></OPTION> + +% } + + </SELECT> + +% } +% unless ( grep $opt{$_}, qw(js_only select_only layers_only) ) { + +<% ref($between) ? &{$between}($key) : $between %> + +% } +% +% unless ( grep $opt{$_}, qw(js_only select_only) ) { + +% foreach my $layer ( @layers ) { +% my $selected_layer; +% $selected_layer = $selected; + + <DIV ID="<% $key %>d<% $layer %>" + STYLE="<% $selected_layer eq $layer + ? 'display: block; z-index: 1' + : 'display: none; z-index: 0' + %>" + > + + <% &{$layer_callback}($layer, $layer_fields, $layer_values, $layer_prefix) %> + + </DIV> + +% } + +% } +<%once> + +my $conf = new FS::Conf; +my $money_char = $conf->config('money_char') || '$'; +my $date_noinit = 0; + +</%once> +<%shared> + +my $selectlayersx_init = 0; + +</%shared> +<%init> + +my %opt = @_; + +#use Data::Dumper; +#warn Dumper(%opt); + +my $key = $opt{field}; # || 'generate_one' #? + +tie my %options, 'Tie::IxHash', + map { $_ => $opt{'labels'}->{$_} } + @{ $opt{'options'} }; #just arrayref for now + +my $between = exists($opt{html_between}) ? $opt{html_between} : ''; +my $options = \%options; + +my @layers = (); +@layers = keys %options; + +my $selected = exists($opt{curr_value}) ? $opt{curr_value} : ''; + +#XXX eek. also eek $layer_fields in the layer_callback() call... +my $layer_fields = $opt{layer_fields}; +my $layer_values = $opt{layer_values}; +my $layer_prefix = $opt{layer_prefix}; + +my $layer_callback = $opt{layer_callback} || \&layer_callback; + +sub layer_callback { + my( $layer, $layer_fields, $layer_values, $layer_prefix ) = @_; + + return '' unless $layer && exists $layer_fields->{$layer}; + tie my %fields, 'Tie::IxHash', @{ $layer_fields->{$layer} }; + + #XXX this should become an element itself... (false laziness w/edit.html) + # but at least all the elements inside are the shared mason elements now + + return '' unless keys %fields; + my $html = "<TABLE>"; + + foreach my $field ( keys %fields ) { + + my $lf = ref($fields{$field}) + ? $fields{$field} + : { 'label'=>$fields{$field} }; + + my $value = $layer_values->{$layer}{$field}; + + my $type = $lf->{type} || 'text'; + + my $include = $type; + + if ( $include eq 'date' ) { + # several important differences from other tr-* + $html .= include( '/elements/tr-input-date-field.html', + { + 'name' => "$layer_prefix$field", + 'value' => $value, + 'label' => $lf->{label}, + 'format'=> $lf->{format}, + 'noinit'=> $date_noinit, + } + ); + $date_noinit = 1; + } + else { + $include = "input-$include" if $include =~ /^(text|money|percentage)$/; + $include = "tr-$include" unless $include eq 'hidden'; + $html .= include( "/elements/$include.html", + %$lf, + 'field' => "$layer_prefix$field", + 'id' => "$layer_prefix$field", #separate? + #don't want field0_label0...? + 'label_id' => $layer_prefix.$field."_label", + + 'value' => ( $lf->{'value'} || $value ), #hmm. + 'curr_value' => $value, + ); + } + } #foreach $field + $html .= '</TABLE>'; + return $html; +} + +</%init> diff --git a/httemplate/elements/standardize_locations.js b/httemplate/elements/standardize_locations.js index 0c4fb029a..15d687cf3 100644 --- a/httemplate/elements/standardize_locations.js +++ b/httemplate/elements/standardize_locations.js @@ -115,7 +115,7 @@ function confirm_standardize(arg) { // then all entered address fields are correct // but we still need to set the lat/long fields and addr_clean - if ( returned['addr_clean'] ) { + if ( returned['all_clean'] ) { status_message('Verified'); } else { status_message('Unverified'); diff --git a/httemplate/elements/table-tickets.html b/httemplate/elements/table-tickets.html index d722c9d2b..b322a5f7c 100644 --- a/httemplate/elements/table-tickets.html +++ b/httemplate/elements/table-tickets.html @@ -14,6 +14,7 @@ View <THEAD> <TR> + <TH CLASS="grid" BGCOLOR="#cccccc"></TH> <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('#') |h %></TH> <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('Subject') |h %></TH> <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('Status') |h %></TH> @@ -42,6 +43,16 @@ View <TR> <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"> +% if ( $ticket->{is_unreplied} ) { + <A CLASS="dot" STYLE="background-color: green" HREF=<%$href%>> +% } else { +% # placeholder + <A CLASS="dot" STYLE="visibility: hidden" HREF=<%$href%>> +% } + </A> + </TD> + + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"> <A HREF=<%$href%>><% $ticket->{id} %></A> </TD> diff --git a/httemplate/elements/topreload.js b/httemplate/elements/topreload.js new file mode 100644 index 000000000..a66703b29 --- /dev/null +++ b/httemplate/elements/topreload.js @@ -0,0 +1,5 @@ +window.topreload = function() { + if (window != window.top) { + window.top.location.reload(); + } +} diff --git a/httemplate/elements/tower_sector.html b/httemplate/elements/tower_sector.html index 151d3ba65..987177582 100644 --- a/httemplate/elements/tower_sector.html +++ b/httemplate/elements/tower_sector.html @@ -56,8 +56,11 @@ tie my %label, 'Tie::IxHash', 'height' => 'Height', 'freq_mhz' => 'Freq. (MHz)', 'direction' => 'Direction', # or a button to set these to 0 for omni - 'width' => 'Width', # + 'downtilt' => 'Downtilt', + 'width' => 'Horiz. width', + 'v_width' => 'Vert. width', 'sector_range' => 'Range', + 'margin' => 'Signal margin (dB)', ; my @fields = keys %label; diff --git a/httemplate/elements/tr-cust_svc_cancel.html b/httemplate/elements/tr-cust_svc_cancel.html index 44276ec82..52dedd6c8 100644 --- a/httemplate/elements/tr-cust_svc_cancel.html +++ b/httemplate/elements/tr-cust_svc_cancel.html @@ -18,7 +18,14 @@ for use in view/cust_main. % } </B></TD> </TR> -%# no action links, the service is canceled +%# no action links except unprovision, the service is canceled +% if ( $curuser->access_right('Unprovision customer service') && ! $opt{no_links} ) { +<TR> + <TD COLSPAN="2" ALIGN="right" VALIGN="top" STYLE="padding-bottom:5px; padding-top:0px"> + <FONT SIZE="-2">( <% $svc_unprovision_link %> )</FONT> + </TD> +</TR> +% } <%init> my %opt = @_; @@ -29,4 +36,9 @@ my $part_svc = $opt{'part_svc'} || $cust_svc->part_svc; my $cust_pkg = $opt{'cust_pkg'} || $cust_svc->cust_pkg; my $svc_x = $cust_svc->svc_x; +my $svc_unprovision_link = + qq!<A HREF="javascript:areyousure('${p}misc/unprovision.cgi?! . + $cust_svc->svcnum . + qq!', '!.emt('Permanently unprovision and delete this service?').qq!')">!.emt('Unprovision').'</A>'; + </%init> diff --git a/httemplate/elements/tr-freq.html b/httemplate/elements/tr-freq.html index cb58bf6b5..795684cf7 100644 --- a/httemplate/elements/tr-freq.html +++ b/httemplate/elements/tr-freq.html @@ -15,7 +15,7 @@ <% $freq eq $units ? 'SELECTED' : '' %> ><% $freq{$freq} %> % } - </SELECT> + </SELECT><% $opt{'post_text'} || '' %> </TD> diff --git a/httemplate/elements/tr-input-locale-text.html b/httemplate/elements/tr-input-locale-text.html new file mode 100644 index 000000000..110a8aa9b --- /dev/null +++ b/httemplate/elements/tr-input-locale-text.html @@ -0,0 +1,120 @@ +<%doc> +Usage: + +In edit/foo.html: + +<& /elements/tr-input-locale-text.html, + cgi => $cgi, # needed to preserve values in error redirect + object => $record, + field => 'myfield', + label => 'My Field', +&> + +And in edit/process/foo.html: +<& elements/process.html, + ... + process_locale => 'myfield', +&> + +'object' needs to be an FS::Record subclass instance for a table that has +a '_msgcat' localization table. For a table "foo" where "foo.myfield" +contains some customer-visible label (in the default locale), +"foo_msgcat.myfield" contains the translation of that label for a customer +locale. The foreign key in foo_msgcat must have the same name as the primary +key of foo. + +Currently only a single field can be localized this way; including this +element more than once in the form will lead to conflicts. This is how +it should work; if at some point we need to localize several fields of the +same record, we should modify this element to show multiple inputs for each +locale. + +</%doc> +<%init> + +my %opt = @_; +my $object = delete $opt{object}; +my $field = delete $opt{field}; + +# identify our locales +my $conf = FS::Conf->new; +my $default_locale = $conf->config('locale') || 'en_'; +my @locales = grep { ! /^$default_locale/ } $conf->config('available-locales'); + +my $label = delete $opt{label}; +my %labels = map { $_ => "$label—".FS::Locales->description($_) } + @locales; +@locales = sort { $labels{$a} cmp $labels{$b} } @locales; +my %curr_values; + +# where are the msgcat records? +my $msgcat_table = $object->table . '_msgcat'; +my $msgcat_pkey = dbdef->table($msgcat_table)->primary_key; +my %msgcat_pkeyvals; + +# find existing msgcat records, if any, and record their message values +# and pkeys +my $pkey = $object->primary_key; +my $pkeyval = $object->get($pkey); +if ($pkeyval) { # of course if this is a new record there won't be any + my @linked = qsearch($msgcat_table, { $pkey => $pkeyval }); + foreach (@linked) { + $curr_values{ $_->locale } = $_->get( $field ); + $msgcat_pkeyvals{ $_->locale } = $_->get( $msgcat_pkey ); + } +} + +# sticky-on-error the locale inputs +if( my $cgi = $opt{cgi} ) { + my $i = 0; + # they're named 'foomsgnum0_locale' and 'foomsgnum0_myfield' + while ( my $locale = $cgi->param($msgcat_pkey . $i . '_locale') ) { + my $value = $cgi->param($msgcat_pkey . $i . '_' . $field); + $curr_values{ $locale } = $value; + $i++; + } +} + +# compat with tr-input-text for styling +my $cell_style = $opt{'cell_style'} ? 'STYLE="'. $opt{'cell_style'}. '"' : ''; + +my $colspan = $opt{'colspan'} ? 'COLSPAN="'.$opt{'colspan'}.'"' : ''; + + +</%init> +% # pass through %opt on all of these to retain formatting +% # one tr, td, and input for the default locale +<& tr-input-text.html, + %opt, + 'label' => $label, + 'field' => $field +&> +% # and one for each of the others +% my $i = 0; +% foreach my $locale (@locales) { +% my $basename = $msgcat_pkey . $i; +% my $lfield = $basename . '_' . $field; +<& tr-td-label.html, + %opt, + 'id' => $lfield, # uniqueness + 'label' => $labels{$locale} +&> + <TD <% $colspan %><% $cell_style %> ID="<% $lfield %>_input0"> + <& hidden.html, + 'field' => $basename, + 'curr_value' => $msgcat_pkeyvals{$locale}, + # will be empty if this is a new record and/or new locale, that's fine + &> + <& hidden.html, + 'field' => $basename . '_locale', + 'curr_value' => $locale, + &> + <& input-text.html, + %opt, + 'field' => $lfield, + 'curr_value' => $curr_values{$locale}, + &> + </TD> +</TR> +% $i++; +% } # foreach $locale diff --git a/httemplate/elements/tr-select-cust-part_pkg.html b/httemplate/elements/tr-select-cust-part_pkg.html index 6244b6cb7..f4af405fc 100644 --- a/httemplate/elements/tr-select-cust-part_pkg.html +++ b/httemplate/elements/tr-select-cust-part_pkg.html @@ -86,7 +86,7 @@ </TR> % } else { # so that the rest of the page works correctly - <INPUT TYPE="hidden" ID="classnum" NAME="classnum" VALUE="-1`"> + <INPUT TYPE="hidden" ID="classnum" NAME="classnum" VALUE="-1"> % } <TR> diff --git a/httemplate/elements/tr-select-reason.html b/httemplate/elements/tr-select-reason.html index 97466f175..9a430222c 100755 --- a/httemplate/elements/tr-select-reason.html +++ b/httemplate/elements/tr-select-reason.html @@ -188,9 +188,8 @@ my $class = $opt{'reason_class'}; my $init_reason; if ( $opt{'cgi'} ) { $init_reason = $opt{'cgi'}->param($name); -} else { - $init_reason = $opt{'curr_value'}; } +$init_reason ||= $opt{'curr_value'}; my $id = $opt{'id'} || $name; $id =~ s/\./_/g; # for edit/part_event diff --git a/httemplate/elements/tr-select-router_block_ip.html b/httemplate/elements/tr-select-router_block_ip.html index ee135686c..2aa715e29 100644 --- a/httemplate/elements/tr-select-router_block_ip.html +++ b/httemplate/elements/tr-select-router_block_ip.html @@ -56,7 +56,7 @@ function clearhint_ip_addr (what) { ] &> </td></tr> -<& /elements/tr-td-label.html, label => 'IP address', required => $opt{'ip_addr_required'} &> +<& /elements/tr-td-label.html, label => ($opt{'ip_addr_label'} || 'IP address'), required => $opt{'ip_addr_required'} &> <td> % #warn Dumper \%fixed; % if ( exists $fixed{$ip_field} ) { diff --git a/httemplate/elements/tr-select-rt-queue.html b/httemplate/elements/tr-select-rt-queue.html new file mode 100644 index 000000000..ac3689b1c --- /dev/null +++ b/httemplate/elements/tr-select-rt-queue.html @@ -0,0 +1,7 @@ + +<& 'tr-td-label.html', @_ &> +<TD> +<& 'select-rt-queue.html', @_ &> +</TD> +</TR> + diff --git a/httemplate/elements/tr-selectlayersx.html b/httemplate/elements/tr-selectlayersx.html new file mode 100644 index 000000000..ca7a36079 --- /dev/null +++ b/httemplate/elements/tr-selectlayersx.html @@ -0,0 +1,25 @@ +% unless ( $opt{js_only} ) { + + <% include('tr-td-label.html', @_ ) %> + + <TD <% $style %>> + +% } + + <% include('selectlayersx.html', @_ ) %> + +% unless ( $opt{js_only} ) { + + </TD> + + </TR> + +% } + +<%init> + +my %opt = @_; + +my $style = $opt{'cell_style'} ? 'STYLE="'. $opt{'cell_style'}. '"' : ''; + +</%init> diff --git a/httemplate/elements/xmlhttp.html b/httemplate/elements/xmlhttp.html index 2f4f0d555..e70871169 100644 --- a/httemplate/elements/xmlhttp.html +++ b/httemplate/elements/xmlhttp.html @@ -44,7 +44,7 @@ my %initialized = ();#won't work if component is "preloaded"... so don't do that len = args.length - 1; } for (var i = 0; i < len; i++) - content = content + "&arg=" + escape(args[i]); + content = content + "&arg=" + encodeURIComponent(args[i]); content = content.replace( /[+]/g, '%2B'); // fix unescaped plus signs if ( '<%$method%>' == 'GET' ) { diff --git a/httemplate/misc/areacodes.cgi b/httemplate/misc/areacodes.cgi index 4b31deb00..afbe93e91 100644 --- a/httemplate/misc/areacodes.cgi +++ b/httemplate/misc/areacodes.cgi @@ -1,4 +1,4 @@ -<% encode_json(\@areacodes) %>\ +<% encode_json({ error => $error, areacodes => \@areacodes}) %>\ <%init> my( $state, $svcpart ) = $cgi->param('arg'); @@ -7,6 +7,8 @@ my $part_svc = qsearchs('part_svc', { 'svcpart'=>$svcpart } ); die "unknown svcpart $svcpart" unless $part_svc; my @areacodes = (); +my $error; + if ( $state ) { my @exports = $part_svc->part_export_did; @@ -17,9 +19,12 @@ if ( $state ) { } my $export = $exports[0]; - my $something = $export->get_dids('state'=>$state); + local $@; + local $SIG{__DIE__}; + my $something = eval { $export->get_dids('state'=>$state) }; + $error = $@; - @areacodes = @{ $something }; + @areacodes = @{ $something } if $something; } diff --git a/httemplate/misc/cancel_pkg.html b/httemplate/misc/cancel_pkg.html index c80b2b278..d3ca9964f 100755 --- a/httemplate/misc/cancel_pkg.html +++ b/httemplate/misc/cancel_pkg.html @@ -45,12 +45,41 @@ } &> <& /elements/tr-checkbox.html, - 'label' => mt("Uncancel even if a service can't be re-provisioned"), + 'label' => mt("Uncancel even if service reprovisioning fails"), 'field' => 'svc_not_fatal', 'value' => 'Y', &> % $date_init = 1; +% my @uncancel_svcs = sort { $b->{'reprovisionable'} <=> $a->{'reprovisionable'} } +% sort { $a->{'svcpart'} <=> $b->{'svcpart'} } +% $cust_pkg->uncancel_svc_summary(); +% if (@uncancel_svcs) { +<TR><TD COLSPAN="2"> </TD></TR> +<TR><TH ALIGN="right"><% emt("Re-provision the following services") %></TH><TD></TD> +% foreach my $uncancel_svc (@uncancel_svcs) { +% my $uncancel_curr_value = $uncancel_svc->{'uncancel_svcnum'}; +% my $uncancel_disabled = ''; +% my $uncancel_postfix = ''; +% if ($cgi->param('error')) { +% $uncancel_curr_value = '' unless grep { $_ == $uncancel_svc->{'uncancel_svcnum'} } $cgi->param('only_svcnum'); +% } +% unless ($uncancel_svc->{'reprovisionable'}) { +% $uncancel_curr_value = ''; +% $uncancel_disabled = 1; +% $uncancel_postfix = '<I>(' . emt('Cannot be reprovisioned') . ')</I>'; +% } + <& /elements/tr-checkbox.html, + 'label' => $uncancel_svc->{'svc'} . ': ' . $uncancel_svc->{'label'}, + 'field' => 'only_svcnum', + 'value' => $uncancel_svc->{'uncancel_svcnum'}, + 'curr_value' => $uncancel_curr_value, + 'disabled' => $uncancel_disabled, + 'cell_style' => 'font-weight: normal', + 'postfix' => $uncancel_postfix, + &> +% } +% } % } % unless ( $method eq 'resume' || $method eq 'uncancel' ) { @@ -62,7 +91,7 @@ &> % } -% if ( $method eq 'adjourn' || $method eq 'suspend' ) { +% if (( $method eq 'adjourn' || $method eq 'suspend' ) && $curuser->access_right('Customize billing during suspension')) { <TR><TD COLSPAN=2> % if ( $part_pkg->option('suspend_bill', 1) ) { <& /elements/checkbox.html, name=>'no_suspend_bill', value=>'Y' &> diff --git a/httemplate/misc/change_pkg_start.html b/httemplate/misc/change_pkg_date.html index 5a890c86e..642a5b89e 100755 --- a/httemplate/misc/change_pkg_start.html +++ b/httemplate/misc/change_pkg_date.html @@ -3,11 +3,12 @@ <& /elements/error.html &> % # only slightly different from unhold_pkg. -<FORM NAME="MyForm" ACTION="process/change_pkg_start.html" METHOD=POST> +<FORM NAME="MyForm" ACTION="process/change_pkg_date.html" METHOD=POST> <INPUT TYPE="hidden" NAME="pkgnum" VALUE="<% $pkgnum %>"> +<INPUT TYPE="hidden" NAME="field" VALUE="<% $field %>"> <BR> -<% emt('Start billing [_1]', $part_pkg->pkg_comment(cust_pkg => $cust_pkg)) %> +<% emt(($isstart ? 'Start billing' : 'Set contract end for').' [_1]', $part_pkg->pkg_comment(cust_pkg => $cust_pkg)) %> <UL STYLE="padding-left: 3ex; list-style: none; background-color: #cccccc"> <LI> <& /elements/radio.html, @@ -16,7 +17,7 @@ value => 'now', curr_value => $when, &> - <label for="when_now"><% emt('Immediately') %></label> + <label for="when_now"><% emt($isstart ? 'Now' : 'Never') %></label> </LI> % if ( $next_bill_date ) { <LI> @@ -41,13 +42,13 @@ &> <label for="when_date"> <% emt('On this date:') %> </label> <& /elements/input-date-field.html, - { name => 'start_date', - value => $cgi->param('start_date') || $cust_pkg->start_date, + { name => 'date_value', + value => $cgi->param('date_value') || $cust_pkg->get($field), } &> </LI> </UL> -<INPUT TYPE="submit" NAME="submit" VALUE="<% emt('Set start date') %>"> +<INPUT TYPE="submit" NAME="submit" VALUE="<% emt('Set '.($isstart ? 'start date' : 'contract end')) %>"> </FORM> </BODY> @@ -55,9 +56,21 @@ <%init> +my $field = $cgi->param('field'); + +my ($acl, $isstart); +if ($field eq 'start_date') { + $acl = 'Change package start date'; + $isstart = 1; +} elsif ($field eq 'contract_end') { + $acl = 'Change package contract end date'; +} else { + die "Unknown date field"; +} + my $curuser = $FS::CurrentUser::CurrentUser; die "access denied" - unless $curuser->access_right('Change package start date'); + unless $curuser->access_right($acl); my $pkgnum; if ( $cgi->param('pkgnum') =~ /^(\d+)$/ ) { @@ -69,7 +82,7 @@ if ( $cgi->param('pkgnum') =~ /^(\d+)$/ ) { my $conf = new FS::Conf; my $date_format = $conf->config('date_format') || '%m/%d/%Y'; -my $title = 'Start billing package'; +my $title = $isstart ? 'Start billing package' : 'Change contract end'; my $cust_pkg = qsearchs({ table => 'cust_pkg', @@ -83,12 +96,12 @@ my $next_bill_date = $cust_pkg->cust_main->next_bill_date; my $part_pkg = $cust_pkg->part_pkg; # defaults: -# sticky on error, then the existing start date if any, then the customer's +# sticky on error, then the existing date if any, then the customer's # next bill date, and if none of those, default to now my $when = $cgi->param('when'); if (!$when) { - if ($cust_pkg->start_date) { + if ($cust_pkg->get($field)) { $when = 'date'; } elsif ($next_bill_date) { $when = 'next_bill_date'; diff --git a/httemplate/misc/confirm-address_standardize.html b/httemplate/misc/confirm-address_standardize.html index 9d1a5c135..8bd43ca54 100644 --- a/httemplate/misc/confirm-address_standardize.html +++ b/httemplate/misc/confirm-address_standardize.html @@ -7,6 +7,9 @@ th { line-height: 150%; vertical-align: middle; text-align: center; } +button { + width: 215px; +} </STYLE> <CENTER><BR><B> % if ( $is_error ) { @@ -23,9 +26,13 @@ Confirm address standardization % my $name = $pre eq 'bill_' ? 'billing' : 'service'; % my $rows = 5; % if ( $new{$pre.'error'} ) { +% # Standardization returned an error, so the user can either "continue +% # without replacing" (replace = "") or "abort". <TR> <TD ROWSPAN=<% $rows %> CLASS="td_radio"> +% if ( $show_radio ) { <INPUT TYPE="radio" NAME="<% $pre %>replace" VALUE="" CHECKED="Y"> +% } # else confirm_manual_address will be called </TD> <TH>Entered <%$name%> address </TH> @@ -51,15 +58,19 @@ Confirm address standardization % $rows++ if !$new{$pre.'addr_clean'}; <TR> <TD ROWSPAN=<% $rows %> CLASS="td_radio"> +% if ( $show_radio ) { <INPUT TYPE="radio" NAME="<% $pre %>replace" VALUE=""> +% } </TD> <TH>Entered <%$name%> address</TH> <TH>Standardized <%$name%> address</TH> <TD ROWSPAN=<% $rows %> CLASS="td_radio"> +% if ( $show_radio ) { <INPUT TYPE="radio" NAME="<% $pre %>replace" VALUE="Y" CHECKED="Y"> +% } </TD> </TR> -% if ( !$new{$pre.'addr_clean'} ) { +% if ( !$new{$pre.'addr_clean'} ) { # we incremented $rows to fit this in <TR> <TD></TD> <TH STYLE="font-size:smaller;color:#ff0000">(unverified)</TH> @@ -88,18 +99,24 @@ Confirm address standardization % } # if error % } # for $pre -%# only do this part if address standardization provided a censustract -% my $pre = $old{same} ? 'bill_' : 'ship_'; +% # the prefix for the censustract +% my $pre = $old{billship} ? +% ( $old{same} ? 'bill_' : 'ship_' ) : +% ''; % my $censustract = $new{$pre.'censustract'}; % if ( $censustract ) { <TR> <TD ROWSPAN=2 CLASS="td_radio"> +% if ( $show_radio ) { <INPUT TYPE="radio" NAME="census_replace" VALUE="" <% $census_error ? 'CHECKED="Y"' : '' %>> +% } </TD> <TH>Entered census tract</TH> <TH>Calculated census tract</TH> <TD ROWSPAN=2 CLASS="td_radio"> +% if ( $show_radio ) { <INPUT TYPE="radio" NAME="census_replace" VALUE="Y" <% $census_error ? '' : 'CHECKED="Y"' %>> +% } </TD> </TR> <TR> @@ -115,17 +132,40 @@ Confirm address standardization % } #if censustract <TR> +% if ( $show_radio ) { +% # One button: "use selected address(es)". + <TD ALIGN="center" COLSPAN=4> + <BUTTON TYPE="button" onclick="replace_address();"> + <IMG SRC="<%$p%>images/<% $is_error ? 'error.png' : 'tick.png' %>" + ALT=""> + Use selected <%$addresses%> + </BUTTON> + </TD> +% } else { +% # Two buttons: "use entered address", and "use selected address" +% # and empty columns before and after <TD> </TD> <TD ALIGN="center"> - <BUTTON TYPE="button" STYLE="width:205px" onclick="replace_address();"> - <IMG SRC="<%$p%>images/<% $is_error ? 'error.png' : 'tick.png' %>" - ALT=""> Use selected <%$addresses%> - </BUTTON></TD> + <BUTTON TYPE="button" onclick="confirm_manual_address();"> + <IMG SRC="<%$p%>images/error.png" ALT=""> Use entered <%$addresses%> + </BUTTON> + </TD> +% # disable the button if standardization completely failed <TD ALIGN="center"> - <BUTTON TYPE="button" STYLE="width:205px" onclick="submit_abort();"> - <IMG SRC="<%$p%>images/cross.png" ALT=""> Cancel submission - </BUTTON></TD> + <BUTTON TYPE="button" onclick="replace_address();" <% $failed ? 'DISABLED' : '' %>> + <IMG SRC="<%$p%>images/tick.png" ALT=""> Use standardized <%$addresses%> + </BUTTON> + </TD> <TD> </TD> +% } + </TR> +% # always provide a cancel button + <TR> + <TD ALIGN="center" COLSPAN=4> + <BUTTON TYPE="button" onclick="submit_abort();"> + <IMG SRC="<%$p%>images/cross.png" ALT=""> Cancel submission + </BUTTON> + </TD> </TR> </TABLE> </FORM> @@ -138,15 +178,43 @@ my %old = %{ $q->{old} }; my %new = %{ $q->{new} }; my $addresses = $old{billship} ? 'addresses' : 'address'; +my $show_radio = 0; +my $failed = 0; # true if standardization returned no addresses my @prefixes = (''); + if ( $old{same} ) { + + # Then there are bill and ship addresses and they're the same. Treat + # bill_error as a failure, and let the user accept or reject the whole + # standardization. + @prefixes = ('bill_'); + $failed = 1 if $new{'bill_error'}; + } elsif ( $old{billship} ) { + + # There are separate bill and ship addresses. Treat error in both as a + # failure. Otherwise, at least one of them has a choice between entered + # and standardized address, so let the user choose. + @prefixes = ('bill_', 'ship_'); + if ( $new{'bill_error'} and $new{'ship_error'} ) { + $failed = 1; + } else { + $show_radio = 1; + } + +} else { + + # There are no bill/ship addresses (this is used for package locations). + # Treat like the first case but without the bill_ prefix. + @prefixes = (''); + $failed = 1 if $new{'error'}; + } -my $census_error = $new{'census_error'}; +my $census_error = $new{'census_error'}; # seems to be unused my $is_error = $census_error || grep { $new{$_.'error'} } @prefixes; </%init> diff --git a/httemplate/misc/cust_credit-import.html b/httemplate/misc/cust_credit-import.html index 937010dd6..a124c9591 100644 --- a/httemplate/misc/cust_credit-import.html +++ b/httemplate/misc/cust_credit-import.html @@ -62,15 +62,16 @@ Import a file containing credits. Uploaded files can be CSV (comma-separated value) files or Excel spreadsheets. The file should have a .CSV or .XLS extension. <BR><BR> - <b>Default</b> format has the following field order: <i>custnum, amount, reasonnum, invnum</i><br> + <b>Default</b> format has the following field order: <i>custnum, amount, reasonnum, invnum, agent_custid</i><br> <BR><BR> Field information: <ul> - <li><i>custnum</i>: Customer number - <li><i>amount</i>: - <li><i>reasonnum</i>: <A HREF="<%$p%>browse/reason_type.html?class=R">Credit reason</A> - <li><i>invnum</i>: Invoice number + <li><i>custnum</i>: This is the freeside customer number. It may be left blank. If specified, agent_custid must be blank.</li> + <li><i>amount</i>:</li> + <li><i>reasonnum</i>: <A HREF="<%$p%>browse/reason_type.html?class=R">Credit reason</A></li> + <li><i>invnum</i>: Invoice number</li> + <li><i>agent_custid</i>: This is the reseller's idea of the customer number or identifier. It may be left blank. If specified, custnum must be blank.</li> </ul> <BR><BR> diff --git a/httemplate/misc/cust_main-cancel.cgi b/httemplate/misc/cust_main-cancel.cgi index f6fd1e915..73c4deb7a 100755 --- a/httemplate/misc/cust_main-cancel.cgi +++ b/httemplate/misc/cust_main-cancel.cgi @@ -1,6 +1,6 @@ <& /elements/header-popup.html, mt("Customer cancelled") &> <SCRIPT TYPE="text/javascript"> - window.top.location.reload(); + topreload(); </SCRIPT> </BODY> </HTML> diff --git a/httemplate/misc/cust_main-suspend.cgi b/httemplate/misc/cust_main-suspend.cgi index 7a501d61a..e81e2b490 100755 --- a/httemplate/misc/cust_main-suspend.cgi +++ b/httemplate/misc/cust_main-suspend.cgi @@ -1,6 +1,6 @@ <& /elements/header-popup.html, mt("Customer suspended") &> <SCRIPT TYPE="text/javascript"> - window.top.location.reload(); + topreload(); </SCRIPT> </BODY> </HTML> diff --git a/httemplate/misc/cust_main-unsuspend.cgi b/httemplate/misc/cust_main-unsuspend.cgi index e8ac8d31e..99ec70a23 100755 --- a/httemplate/misc/cust_main-unsuspend.cgi +++ b/httemplate/misc/cust_main-unsuspend.cgi @@ -1,6 +1,6 @@ <& /elements/header-popup.html, mt("Customer unsuspended") &> <SCRIPT TYPE="text/javascript"> - window.top.location.reload(); + topreload(); </SCRIPT> </BODY> </HTML> diff --git a/httemplate/misc/delete-addr_range.html b/httemplate/misc/delete-addr_range.html index c6310e9b1..239332d23 100644 --- a/httemplate/misc/delete-addr_range.html +++ b/httemplate/misc/delete-addr_range.html @@ -3,7 +3,7 @@ % } else { <& /elements/header-popup.html, "Address range deleted" &> <SCRIPT TYPE="text/javascript"> - window.top.location.reload(); + topreload(); </SCRIPT> </BODY> </HTML> diff --git a/httemplate/misc/delete-rate_detail.html b/httemplate/misc/delete-rate_detail.html index 30856a73a..b4d31b379 100755 --- a/httemplate/misc/delete-rate_detail.html +++ b/httemplate/misc/delete-rate_detail.html @@ -3,7 +3,7 @@ % } else { <% header('Rate deleted') %> <SCRIPT TYPE="text/javascript"> - window.top.location.reload(); + topreload(); </SCRIPT> </BODY></HTML> % } diff --git a/httemplate/misc/did_order_confirmed.html b/httemplate/misc/did_order_confirmed.html index 53dbb2f3a..3cc121c6f 100644 --- a/httemplate/misc/did_order_confirmed.html +++ b/httemplate/misc/did_order_confirmed.html @@ -14,7 +14,7 @@ die "access denied" my $action = $1; my $header = ''; my $popup = ''; -my $js = 'window.top.location.reload();'; +my $js = 'topreload();'; $cgi->param('ordernum') =~ /^(\d+)$/ or die 'illegal ordernum'; my $ordernum = $1; diff --git a/httemplate/misc/disable-cust_location.cgi b/httemplate/misc/disable-cust_location.cgi index ee7ba1dbc..677f0b891 100755 --- a/httemplate/misc/disable-cust_location.cgi +++ b/httemplate/misc/disable-cust_location.cgi @@ -1,6 +1,6 @@ <% header("Location disabled") %> <SCRIPT TYPE="text/javascript"> - window.top.location.reload(); + topreload(); </SCRIPT> </BODY> </HTML> diff --git a/httemplate/misc/disable-msg_template.cgi b/httemplate/misc/disable-msg_template.cgi index 1eb4d25e5..565eb2cee 100644 --- a/httemplate/misc/disable-msg_template.cgi +++ b/httemplate/misc/disable-msg_template.cgi @@ -3,7 +3,7 @@ % } else { <& /elements/header-popup.html, "Template ${actioned}" &> <SCRIPT TYPE="text/javascript"> - window.top.location.reload(); + topreload(); </SCRIPT> </BODY> </HTML> diff --git a/httemplate/misc/email-customers.html b/httemplate/misc/email-customers.html index 8e2863455..d086c676d 100644 --- a/httemplate/misc/email-customers.html +++ b/httemplate/misc/email-customers.html @@ -7,8 +7,8 @@ selecting an existing msg_template, or creating a custom message, and shows a preview of the message before sending. If linked to as a popup, include the cgi parameter 'popup' for proper header handling. -This may also be used as an element in other pages, enabling you to provide an -alternate initial form while using this for search freezing/thawing and +This may also be used as an element in other pages, enabling you to provide +an alternate initial form while using this for search freezing/thawing and preview/send actions, with the following options: acl - the access right to use (defaults to 'Bulk send customer notices') @@ -48,6 +48,7 @@ from/subject/body cgi params <INPUT TYPE="hidden" NAME="search" VALUE="<% encode_base64(nfreeze(\%search)) %>"> <INPUT TYPE="hidden" NAME="popup" VALUE="<% $popup %>"> <INPUT TYPE="hidden" NAME="url" VALUE="<% $url | h %>"> +<INPUT TYPE="hidden" NAME="to_contact_classnum" VALUE="<% join(',', @contact_classnum) %>"> % if ( $cgi->param('preview') ) { % # preview mode: at this point we have a msg_template (either "real" or @@ -57,7 +58,7 @@ from/subject/body cgi params <FONT SIZE="+2">Preview notice</FONT> <& /elements/progress-init.html, 'OneTrueForm', - [ qw( search table msgnum ) ], + [ qw( search table msgnum to_contact_classnum ) ], $process_url, $pdest, &> @@ -79,6 +80,10 @@ from/subject/body cgi params <td><% $from |h %></td> </tr> + <& /elements/tr-td-label.html, 'label' => 'To contacts:' &> + <td><% join('<BR>', @contact_classname) %></td> + </tr> + <& /elements/tr-td-label.html, 'label' => 'Subject:' &> <td><% $subject |h %></td> </tr> @@ -158,6 +163,20 @@ Template: &> <BR> % } +% # select destination contact classes +Send to contacts: + <& /elements/checkboxes.html, + 'style' => 'display: inline; vertical-align: top', + 'disable_links' => 1, + 'names_list' => \@contact_checkboxes, + 'element_name_prefix' => 'contact_class_', + 'checked_callback' => sub { + my($cgi, $name) = @_; + $name eq 'invoice' #others default to unchecked + }, + &> +<BR> +% # if sending a one-off message, show a form to edit it <TABLE BGCOLOR="#cccccc" CELLSPACING=0 WIDTH="100%" id="table_no_template"> <& /elements/tr-td-label.html, 'label' => 'From:' &> <TD><& /elements/input-text.html, @@ -262,6 +281,9 @@ if ( $cgi->param('msgnum') =~ /^(\d+)$/ ) { or die "template not found: ".$cgi->param('msgnum'); } +my @contact_classnum; +my @contact_classname; + my $subject = $cgi->param('subject'); my $body = $cgi->param('body'); my ($html_body, $text_body); @@ -337,6 +359,28 @@ if ( !$cgi->param('preview') ) { $subject = $1; } } + + # contact_class_X params + foreach my $param ( $cgi->multi_param ) { + if ( $param =~ /^contact_class_(\w+)$/ ) { + push @contact_classnum, $1; + if ( $1 eq 'invoice' ) { + push @contact_classname, 'Invoice recipients'; + } else { + my $contact_class = FS::contact_class->by_key($1); + push @contact_classname, encode_entities($contact_class->classname); + } + } + } } +my @contact_checkboxes = ( + [ 'invoice' => { label => 'Invoice recipients' } ] +); +foreach my $class (qsearch('contact_class', { disabled => '' })) { + push @contact_checkboxes, [ + $class->classnum, + { label => $class->classname } + ]; +} </%init> diff --git a/httemplate/misc/exchanges.cgi b/httemplate/misc/exchanges.cgi index 0de4ace25..d62679191 100644 --- a/httemplate/misc/exchanges.cgi +++ b/httemplate/misc/exchanges.cgi @@ -1,4 +1,4 @@ -<% encode_json(\@exchanges) %>\ +<% encode_json({ error => $error, exchanges => \@exchanges}) %>\ <%init> my( $areacode, $svcpart ) = $cgi->param('arg'); @@ -7,6 +7,8 @@ my $part_svc = qsearchs('part_svc', { 'svcpart'=>$svcpart } ); die "unknown svcpart $svcpart" unless $part_svc; my @exchanges = (); +my $error; + if ( $areacode ) { my @exports = $part_svc->part_export_did; @@ -17,9 +19,12 @@ if ( $areacode ) { } my $export = $exports[0]; - my $something = $export->get_dids('areacode'=>$areacode); + local $@; + local $SIG{__DIE__}; + my $something = eval { $export->get_dids('areacode'=>$areacode) }; + $error = $@; - @exchanges = @{ $something }; + @exchanges = @{ $something } if $something; } diff --git a/httemplate/misc/make_appointment.html b/httemplate/misc/make_appointment.html index 6f308e0a8..79c3c2c89 100644 --- a/httemplate/misc/make_appointment.html +++ b/httemplate/misc/make_appointment.html @@ -6,13 +6,10 @@ <INPUT TYPE="hidden" NAME="custnum" VALUE="<% $cgi->param('custnum') |h %>"> -% my @sched_item = qsearch('sched_item', { 'disabled' => '', }); -% my @username = map $_->access_user->username, @sched_item; -% foreach my $username (@username) { - <INPUT TYPE="hidden" NAME="username" VALUE="<% $username |h %>"> -% } - -Length: +<TABLE> +<TR> +<TD STYLE="text-align: right">Length:</TD> +<TD> <SELECT NAME="LengthMin"> % for ( my $hours = .5; $hours < 10.5; $hours += .5 ) { % my $min = $hours * 60; @@ -21,9 +18,26 @@ Length: ><% $hours %> hour<% $hours > 1 ? 's' : '' %> % } </SELECT> -<BR> -<BR> +</TD> +</TR> + +% my @sched_item = qsearch('sched_item', { 'disabled' => '', }); +% my @username = map $_->access_user->username, @sched_item; + +<TR> +<TD STYLE="text-align: right">Installer:</TD> +<TD> +<SELECT NAME="username" ID="username_select" MULTIPLE> +% foreach my $username (@username) { + <OPTION SELECTED><% $username |h %></OPTION> +% } +</SELECT> +</TD> +</TR> +</TABLE> + +<BR> <INPUT TYPE="submit" VALUE="Schedule appointment"> </FORM> diff --git a/httemplate/misc/phonenums.cgi b/httemplate/misc/phonenums.cgi index 62923ac62..aae04f5d1 100644 --- a/httemplate/misc/phonenums.cgi +++ b/httemplate/misc/phonenums.cgi @@ -1,4 +1,4 @@ -<% encode_json(\@phonenums) %>\ +<% encode_json({ error => $error, phonenums => \@phonenums}) %>\ <%init> my( $exchangestring, $svcpart ) = $cgi->param('arg'); @@ -7,6 +7,7 @@ my $part_svc = qsearchs('part_svc', { 'svcpart'=>$svcpart } ); die "unknown svcpart $svcpart" unless $part_svc; my @phonenums = (); +my $error; if ( $exchangestring ) { @@ -35,8 +36,12 @@ if ( $exchangestring ) { $opts{'exchange'} = $exchange; } - my $something = $export->get_dids(%opts); - @phonenums = @{ $something }; + local $@; + local $SIG{__DIE__}; + my $something = eval { $export->get_dids(%opts) }; + $error = $@; + + @phonenums = @{ $something } if $something; } diff --git a/httemplate/misc/post_fsinc-invoice.cgi b/httemplate/misc/post_fsinc-invoice.cgi new file mode 100644 index 000000000..94eaf667e --- /dev/null +++ b/httemplate/misc/post_fsinc-invoice.cgi @@ -0,0 +1,43 @@ +% my $title = $error ? 'Error printing and mailing invoice' : 'Invoice printed and mailed'; +<% include('/elements/header-popup.html', $title ) %> +<DIV STYLE="text-align: center;"> +<SPAN STYLE="color: red; font-weight: bold;"><% $error %></SPAN><BR> +<BUTTON TYPE="button" onClick="parent.cClick();">Close</BUTTON> +</DIV> +<% include('/elements/footer-popup.html') %> + +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Print and mail invoices'); + +my $invnum = $cgi->param('invnum'); + +my $template = $cgi->param('template'); +my $notice_name = $cgi->param('notice_name') if $cgi->param('notice_name'); +my $no_coupon = $cgi->param('no_coupon'); + +#XXX agent-virt +my $cust_bill = qsearchs('cust_bill',{'invnum'=>$invnum}) + or die "Unknown invnum\n"; + +my $mode; +if ( $cgi->param('mode') =~ /^(\d+)$/ ) { + $mode = $1; +} +$cust_bill->set('mode' => $mode) if $mode; + +#these methods die instead of return errors, so, handle that without a backtrace +local $@; +my $letter_id = + eval { $cust_bill->postal_mail_fsinc( 'template' => $template, + 'notice_name' => $notice_name, + 'no_coupon' => $no_coupon, + ); + }; +my $error = "$@"; + +$error ||= 'Unknown print and mail error: no letter ID returned' + unless $letter_id; + +</%init> diff --git a/httemplate/misc/process/bulk_pkg_increment_bill.cgi b/httemplate/misc/process/bulk_pkg_increment_bill.cgi index 0d8417b26..8da849508 100755 --- a/httemplate/misc/process/bulk_pkg_increment_bill.cgi +++ b/httemplate/misc/process/bulk_pkg_increment_bill.cgi @@ -4,7 +4,7 @@ %} else { <% header('Packages Adjusted') %> <SCRIPT TYPE="text/javascript"> - window.top.location.reload(); + topreload(); </SCRIPT> </BODY></HTML> % } diff --git a/httemplate/misc/process/cancel_pkg.html b/httemplate/misc/process/cancel_pkg.html index c7a43979d..46ba06a6d 100755 --- a/httemplate/misc/process/cancel_pkg.html +++ b/httemplate/misc/process/cancel_pkg.html @@ -1,6 +1,6 @@ <% header(emt("Package $past_method")) %> <SCRIPT TYPE="text/javascript"> - window.top.location.reload(); + topreload(); </SCRIPT> </BODY> </HTML> @@ -33,8 +33,10 @@ $method =~ /^(cancel|expire|suspend|adjourn|resume|uncancel)$/ $method = $1; my $past_method = $past{$method}; +my $curuser = $FS::CurrentUser::CurrentUser; + die "access denied" - unless $FS::CurrentUser::CurrentUser->access_right($right{$method}); + unless $curuser->access_right($right{$method}); #untaint pkgnum my $pkgnum = $cgi->param('pkgnum'); @@ -60,7 +62,8 @@ if ( $method eq 'suspend' ) { #or 'adjourn' $options = { map { $_ => scalar($cgi->param($_)) } qw( suspend_bill no_suspend_bill ) - }; + } + if $curuser->access_right('Customize billing during suspension'); } my $cust_pkg = qsearchs( 'cust_pkg', {'pkgnum'=>$pkgnum} ); @@ -82,6 +85,8 @@ my $bill = my $svc_fatal = ( $cgi->param('svc_not_fatal') ne 'Y' ); +my $only_svcnum = ($method eq 'uncancel') ? [ $cgi->param('only_svcnum') ] : undef; + $error ||= $cust_pkg->$method( 'reason' => $reasonnum, 'date' => $date, 'resume_date' => $resume_date, @@ -89,6 +94,7 @@ $error ||= $cust_pkg->$method( 'reason' => $reasonnum, 'bill' => $bill, 'svc_fatal' => $svc_fatal, 'options' => $options, + 'only_svcnum' => $only_svcnum, ); if ($error) { diff --git a/httemplate/misc/process/change_pkg_contact.html b/httemplate/misc/process/change_pkg_contact.html index 2795c1197..5bf896200 100644 --- a/httemplate/misc/process/change_pkg_contact.html +++ b/httemplate/misc/process/change_pkg_contact.html @@ -1,6 +1,6 @@ <% header(emt("Package contact $past_method")) %> <SCRIPT TYPE="text/javascript"> - window.top.location.reload(); + topreload(); </SCRIPT> </BODY> </HTML> diff --git a/httemplate/misc/process/change_pkg_start.html b/httemplate/misc/process/change_pkg_date.html index 17a8518f9..3084ec538 100755 --- a/httemplate/misc/process/change_pkg_start.html +++ b/httemplate/misc/process/change_pkg_date.html @@ -6,9 +6,21 @@ </HTML> <%init> +my $field = $cgi->param('field'); + +my ($acl, $isstart); +if ($field eq 'start_date') { + $acl = 'Change package start date'; + $isstart = 1; +} elsif ($field eq 'contract_end') { + $acl = 'Change package contract end date'; +} else { + die "Unknown date field"; +} + my $curuser = $FS::CurrentUser::CurrentUser; die "access denied" - unless $curuser->access_right('Change package start date'); + unless $curuser->access_right($acl); $cgi->param('pkgnum') =~ /^(\d+)$/ or die "illegal pkgnum"; @@ -24,23 +36,25 @@ my $cust_pkg = qsearchs({ my $cust_main = $cust_pkg->cust_main; my $error; -my $start_date; +my $date_value; if ( $cgi->param('when') eq 'now' ) { - # start it the next time billing runs - $start_date = ''; + # blank start means start it the next time billing runs ("Now") + # blank contract end means it never ends ("Never") + $date_value = ''; } elsif ( $cgi->param('when') eq 'next_bill_date' ) { - $start_date = $cust_main->next_bill_date; + $date_value = $cust_main->next_bill_date; } elsif ( $cgi->param('when') eq 'date' ) { - $start_date = parse_datetime($cgi->param('start_date')); + $date_value = parse_datetime($cgi->param('date_value')); } -if ( $cust_pkg->setup ) { +if ( $isstart && $cust_pkg->setup ) { # shouldn't happen $error = 'This package has already started billing.'; } else { local $FS::UID::AutoCommit = 0; foreach my $pkg ($cust_pkg, $cust_pkg->supplemental_pkgs) { - $pkg->set('start_date', $start_date); + last if $error; + $pkg->set($field, $date_value); $error ||= $pkg->replace; } $error ? dbh->rollback : dbh->commit; @@ -48,6 +62,6 @@ if ( $cust_pkg->setup ) { if ( $error ) { $cgi->param('error', $error); - print $cgi->redirect($fsurl.'misc/change_pkg_start.html?', $cgi->query_string); + print $cgi->redirect($fsurl.'misc/change_pkg_date.html?', $cgi->query_string); } </%init> diff --git a/httemplate/misc/process/cust_bill-promised_date.html b/httemplate/misc/process/cust_bill-promised_date.html index 721a763eb..f390609c7 100644 --- a/httemplate/misc/process/cust_bill-promised_date.html +++ b/httemplate/misc/process/cust_bill-promised_date.html @@ -1,4 +1,4 @@ -<SCRIPT TYPE="text/javascript">window.top.location.reload()</SCRIPT> +<SCRIPT TYPE="text/javascript">topreload()</SCRIPT> <%init> # XXX ACL? die "access denied" diff --git a/httemplate/misc/process/delay_susp_pkg.html b/httemplate/misc/process/delay_susp_pkg.html index 675da0496..15a3c963c 100755 --- a/httemplate/misc/process/delay_susp_pkg.html +++ b/httemplate/misc/process/delay_susp_pkg.html @@ -1,6 +1,6 @@ <% header($msg) %> <SCRIPT TYPE="text/javascript"> - window.top.location.reload(); + topreload(); </SCRIPT> </BODY> </HTML> diff --git a/httemplate/misc/process/enable_or_disable_tax.html b/httemplate/misc/process/enable_or_disable_tax.html index 9b7324b0d..8a7a559a0 100755 --- a/httemplate/misc/process/enable_or_disable_tax.html +++ b/httemplate/misc/process/enable_or_disable_tax.html @@ -4,7 +4,7 @@ <% include('/elements/header-popup.html', $title) %> <SCRIPT TYPE="text/javascript"> - window.top.location.reload(); + topreload(); </SCRIPT> </BODY> diff --git a/httemplate/misc/process/nms-add_iface.html b/httemplate/misc/process/nms-add_iface.html index 79e685686..cbd0fc048 100644 --- a/httemplate/misc/process/nms-add_iface.html +++ b/httemplate/misc/process/nms-add_iface.html @@ -1,6 +1,6 @@ <& /elements/header-popup.html, 'Interface added' &> <SCRIPT TYPE="text/javascript"> - window.top.location.reload(); + topreload(); </SCRIPT> </BODY></HTML> <%init> diff --git a/httemplate/misc/process/nms-add_router.html b/httemplate/misc/process/nms-add_router.html index c3b42a8d3..a4c3423da 100644 --- a/httemplate/misc/process/nms-add_router.html +++ b/httemplate/misc/process/nms-add_router.html @@ -1,6 +1,6 @@ <& /elements/header-popup.html, 'Router added' &> <SCRIPT TYPE="text/javascript"> - window.top.location.reload(); + topreload(); </SCRIPT> </BODY></HTML> <%init> diff --git a/httemplate/misc/process/payment.cgi b/httemplate/misc/process/payment.cgi index d232fe729..7768f921f 100644 --- a/httemplate/misc/process/payment.cgi +++ b/httemplate/misc/process/payment.cgi @@ -86,7 +86,7 @@ if ( (my $custpaybynum = scalar($cgi->param('custpaybynum'))) > 0 ) { $payinfo = $cust_payby->payinfo; $paymask = $cust_payby->paymask; - $paycvv = ''; + $paycvv = $cust_payby->paycvv; # pass it if we got it, running a transaction will clear it ( $month, $year ) = $cust_payby->paydate_mon_year; $payname = $cust_payby->payname; diff --git a/httemplate/misc/process/recharge_svc.html b/httemplate/misc/process/recharge_svc.html index 2d49f6b06..88a1f7f36 100755 --- a/httemplate/misc/process/recharge_svc.html +++ b/httemplate/misc/process/recharge_svc.html @@ -4,7 +4,7 @@ %} else { <% header("Package recharged") %> <SCRIPT TYPE="text/javascript"> - window.top.location.reload(); + topreload(); </SCRIPT> </BODY></HTML> %} diff --git a/httemplate/misc/process/set_page_pref.html b/httemplate/misc/process/set_page_pref.html new file mode 100644 index 000000000..a7f123116 --- /dev/null +++ b/httemplate/misc/process/set_page_pref.html @@ -0,0 +1,12 @@ +<%init> +my $path = $cgi->param('path'); +my $name = $cgi->param('name'); +my $tablenum = $cgi->param('num') || ''; +my $value = $cgi->param('value'); + +my $error = $FS::CurrentUser::CurrentUser->set_page_pref($path, $name, $tablenum, $value); +my $result = { 'error' => $error }; # in case someone cares + +http_header('Content-Type', 'application/json'); +</%init> +<% encode_json($result) %> diff --git a/httemplate/misc/process/unhold_pkg.html b/httemplate/misc/process/unhold_pkg.html index 694048023..7e54262c6 100755 --- a/httemplate/misc/process/unhold_pkg.html +++ b/httemplate/misc/process/unhold_pkg.html @@ -1,6 +1,6 @@ <& /elements/header-popup.html &> <SCRIPT TYPE="text/javascript"> - window.top.location.reload(); + topreload(); </SCRIPT> </BODY> </HTML> diff --git a/httemplate/misc/process/void-cust_bill.html b/httemplate/misc/process/void-cust_bill.html index 7773b0ba9..32a2fc591 100755 --- a/httemplate/misc/process/void-cust_bill.html +++ b/httemplate/misc/process/void-cust_bill.html @@ -4,7 +4,7 @@ %} else { <& /elements/header-popup.html, 'Invoice voided' &> <SCRIPT TYPE="text/javascript"> - window.top.location.reload(); + topreload(); </SCRIPT> </BODY></HTML> %} diff --git a/httemplate/misc/reason-merge.html b/httemplate/misc/reason-merge.html index 14f5ebb84..b7e4df454 100644 --- a/httemplate/misc/reason-merge.html +++ b/httemplate/misc/reason-merge.html @@ -1,7 +1,7 @@ % if ($success) { <% include('/elements/header-popup.html', 'Reason Merge Success') %> <SCRIPT> -window.top.location.reload() +topreload() </SCRIPT> % } else { <% include('/elements/header-popup.html', 'Merge Reasons') %> diff --git a/httemplate/misc/regions.cgi b/httemplate/misc/regions.cgi index 31538b08e..882dd48df 100644 --- a/httemplate/misc/regions.cgi +++ b/httemplate/misc/regions.cgi @@ -1,4 +1,4 @@ -<% encode_json(\@regions) %>\ +<% encode_json({ error => $error, regions => \@regions}) %>\ <%init> my( $state, $svcpart ) = $cgi->param('arg'); @@ -7,6 +7,8 @@ my $part_svc = qsearchs('part_svc', { 'svcpart'=>$svcpart } ); die "unknown svcpart $svcpart" unless $part_svc; my @regions = (); +my $error; + if ( $state ) { my @exports = $part_svc->part_export_did; @@ -17,9 +19,12 @@ if ( $state ) { } my $export = $exports[0]; - my $something = $export->get_dids('state'=>$state); + local $@; + local $SIG{__DIE__}; + my $something = eval { $export->get_dids('state'=>$state) }; + $error = $@; - @regions = @{ $something }; + @regions = @{ $something } if $something; } diff --git a/httemplate/misc/sector-create_map.html b/httemplate/misc/sector-create_map.html new file mode 100644 index 000000000..6af5fddbe --- /dev/null +++ b/httemplate/misc/sector-create_map.html @@ -0,0 +1,10 @@ +<% $server->process %> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); # ??? + +my $server = + new FS::UI::Web::JSRPC 'FS::tower_sector::process_generate_coverage', $cgi; + +</%init> diff --git a/httemplate/misc/tax-fetch_and_import.cgi b/httemplate/misc/tax-fetch_and_import.cgi index 33a6c9b01..970d47c32 100644 --- a/httemplate/misc/tax-fetch_and_import.cgi +++ b/httemplate/misc/tax-fetch_and_import.cgi @@ -29,8 +29,9 @@ Import a tax data update. <TR> <TD COLSPAN=2 ALIGN="center" STYLE="padding-top:6px"> <INPUT TYPE = "submit" + NAME = "submitButton" VALUE = "Download and Import" - onClick = "document.TaxRateImport.submit.disabled=true; process();" + onClick = "document.TaxRateImport.submitButton.disabled=true; process();" > </TD> </TR> diff --git a/httemplate/misc/tax-fetch_and_replace.cgi b/httemplate/misc/tax-fetch_and_replace.cgi index 3290a3c44..ff64e6320 100644 --- a/httemplate/misc/tax-fetch_and_replace.cgi +++ b/httemplate/misc/tax-fetch_and_replace.cgi @@ -29,8 +29,9 @@ Replace tax data. <TR> <TD COLSPAN=2 ALIGN="center" STYLE="padding-top:6px"> <INPUT TYPE = "submit" + NAME = "submitButton" VALUE = "Download and Import" - onClick = "document.TaxRateImport.submit.disabled=true; process();" + onClick = "document.TaxRateImport.submitButton.disabled=true; process();" > </TD> </TR> diff --git a/httemplate/misc/tower-export.html b/httemplate/misc/tower-export.html new file mode 100644 index 000000000..9d63640f0 --- /dev/null +++ b/httemplate/misc/tower-export.html @@ -0,0 +1,36 @@ +<%init> +# currently, browse/tower just shows all towers, so do the same here +my @towers = qsearch({ table => 'tower' }); +http_header('Content-Type' => 'text/csv'); +http_header('Content-Disposition' => 'attachment;filename=towers.csv'); +if ( $cgi->param('format') eq 'tc' ) { + # towercoverage.com format: not a true CSV, no quoting (so no way to include + # commas in any field, so we strip them) + + # lat/long are signed decimals, northeast positive + # height is in meters + # Description/Group are not necessary + # sector/antenna information (orientation, beamwidth, gain, frequency, + # etc.) is in what TC calls a "Coverage", which can't be edited this way. + my $text = "SiteName,Latitude,Longitude,Description,Group,Height\n"; + + foreach my $tower (@towers) { + next if ( !$tower->latitude or !$tower->longitude ); + + my $name = $tower->towername; + my $height = ($tower->height || 0) / 3.28; + $name =~ s(,)( )g; + $text .= join(',', + $name, + $tower->latitude, + $tower->longitude, + '', + '', + $height, + ) . "\n"; + } + $m->print($text); +} else { + die('unknown format '.$cgi->param('format')); +} +</%init> diff --git a/httemplate/misc/void-cust_credit.html b/httemplate/misc/void-cust_credit.html index 1e71f0030..81ba31d54 100755 --- a/httemplate/misc/void-cust_credit.html +++ b/httemplate/misc/void-cust_credit.html @@ -1,7 +1,7 @@ %if ( $success ) { <& /elements/header-popup.html, mt("Credit voided") &> <SCRIPT TYPE="text/javascript"> - window.top.location.reload(); + topreload(); </SCRIPT> </BODY> </HTML> diff --git a/httemplate/misc/xmlhttp-address_standardize.html b/httemplate/misc/xmlhttp-address_standardize.html index d0255a02a..be58618d0 100644 --- a/httemplate/misc/xmlhttp-address_standardize.html +++ b/httemplate/misc/xmlhttp-address_standardize.html @@ -23,6 +23,7 @@ if ( $old{same} ) { @prefixes = ('bill_', 'ship_'); } my $all_same = 1; +my $all_clean = 1; foreach my $pre ( @prefixes ) { my $location = { @@ -48,8 +49,13 @@ foreach my $pre ( @prefixes ) { $old{$pre.'censustract'} ne $new{$pre.'censustract'} ); $all_same = 0 if $new{$pre.'error'}; + + $all_clean = 0 if !$new{$pre.'addr_clean'}; } -my $return = { old => \%old, new => \%new, all_same => $all_same }; +my $return = { old => \%old, + new => \%new, + all_same => $all_same, + all_clean => $all_clean }; warn "result:\n".encode_json($return) if $DEBUG; </%init> diff --git a/httemplate/misc/xmlhttp-ticket-update.html b/httemplate/misc/xmlhttp-ticket-update.html index bd58b95c6..01fb1b44d 100644 --- a/httemplate/misc/xmlhttp-ticket-update.html +++ b/httemplate/misc/xmlhttp-ticket-update.html @@ -8,14 +8,18 @@ my $username = $cgi->param('username'); my $ticket = FS::TicketSystem->get_ticket_object( \%session, ticket_id=>$id ); -#hmm, this should happen in a single transaction and either commit or rollback, -# but in reality failures "Don't Happen" so its not like a ticket gets -# half changed +#hmm, this should happen in a single transaction and either commit or rollback my $return; if ( $ticket ) { - my($orv, $omsg) = $ticket->SetOwner( $username, 'Steal' ); + my $curowner = $ticket->OwnerObj->Name; + my($orv, $omsg); + if (( $curowner eq $FS::CurrentUser::CurrentUser->username ) or ( $curowner eq 'nobody' )) { + ($orv, $omsg) = $ticket->SetOwner( $username ); + } else { + ($orv, $omsg) = $ticket->SetOwner( $username, 'Steal' ); + } $orv = 1 if ! $orv && $omsg =~ /already own/i; if ( $orv ) { @@ -42,15 +46,18 @@ if ( $ticket ) { my %hash = $m->comp('/rt/Ticket/Elements/Customers', Ticket => $ticket); my @cust_main = values( %{$hash{cust_main}} ); + my $timelabel = FS::sched_avail::pretty_time($sh*60+$sm). '-'. + FS::sched_avail::pretty_time($eh*60+$em); + my $titlelabel = encode_entities($cust_main[0]->_FreesideURILabel); + $return = { 'error' => '', #'starts' => $starts, #'due' => $due, #'username' => $username, #false laziness w/CalendarSlotSchedule - 'sched_label' => - FS::sched_avail::pretty_time($sh*60+$sm). '-'. - FS::sched_avail::pretty_time($eh*60+$em). ': '. - encode_entities($cust_main[0]->_FreesideURILabel), + 'sched_label' => $timelabel . ': ' . $titlelabel, + 'sched_label_time' => $timelabel, + 'sched_label_title' => $titlelabel, }; } else { $return = { 'error' => $smsg }; diff --git a/httemplate/pref/pref-process.html b/httemplate/pref/pref-process.html index f03a8dfa3..b622efc15 100644 --- a/httemplate/pref/pref-process.html +++ b/httemplate/pref/pref-process.html @@ -55,6 +55,7 @@ unless ( $error ) { # if ($access_user) { disable_html_editor disable_enter_submit_onetimecharge enable_mask_clipboard_hack dashboard_customers customer_view_emails + printtofit email_address snom-ip snom-username snom-password vonage-fromnumber vonage-username vonage-password diff --git a/httemplate/pref/pref.html b/httemplate/pref/pref.html index 50d6e8d23..93c71996c 100644 --- a/httemplate/pref/pref.html +++ b/httemplate/pref/pref.html @@ -36,7 +36,7 @@ Interface <TH ALIGN="right">Locale: </TH> <TD COLSPAN=2> <SELECT NAME="locale"> -% foreach my $locale ( FS::Locales->locales ) { +% foreach my $locale ( @locales ) { % my %info = FS::Locales->locale_info($locale); % my $selected = ($locale eq $curuser->option('locale')) % ? 'SELECTED' : ''; @@ -130,6 +130,13 @@ Interface </TR> <TR> + <TH ALIGN="right">Scale documents to fit on a letter-size page</TH> + <TD ALIGN="left"> + <INPUT TYPE="checkbox" NAME="printtofit" VALUE="Y" <% $curuser->option('printtofit') ? 'CHECKED' : '' %>> + </TD> + </TR> + + <TR> <TH ALIGN="right">How many recently-modified customers displayed on dashboard</TH> <TD ALIGN="left" COLSPAN=2> <INPUT TYPE="text" NAME="dashboard_customers" VALUE="<% $curuser->option('dashboard_customers') %>"></TD> @@ -143,8 +150,6 @@ Interface </TD> </TR> - - </TABLE> <BR> @@ -282,4 +287,14 @@ my $menu_position = $1; =~ /^([,\w\@.\-]*)$/ or die "illegal email_address"; #too late my $email_address = $1; +my $conf = new FS::Conf; + +my @locales = $conf->config('available-locales'); + +if ( ! @locales ) { + + @locales = FS::Locales->locales ; + +} + </%init> diff --git a/httemplate/search/agent_credit_payment.html b/httemplate/search/agent_credit_payment.html new file mode 100644 index 000000000..0dda83bbd --- /dev/null +++ b/httemplate/search/agent_credit_payment.html @@ -0,0 +1,155 @@ +<& elements/grid-report.html, + title => $title.'Package Agent Credits and Payments', + rows => $rows, + cells => $cells, + head => <<END, +<P>Shows agent commission credits, and payments applied to invoices for packages that triggered those credits.</P> +<STYLE SCOPED> +td.creditcell { background-color: #ffff99; } +td.paycell { background-color: #66ff66; } +</STYLE> +END +&> + +<%init> + +my $curuser = $FS::CurrentUser::CurrentUser; +die "access denied" + unless $curuser->access_right('Financial reports'); + +my $extra_sql = ''; + +# search for agent +my ($agentnum,$sel_agent); +if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) { + $agentnum = $1; + $sel_agent = qsearchs('agent', { 'agentnum' => $agentnum } ); + die "agentnum $agentnum not found!" unless $sel_agent; + $extra_sql .= " AND cust_credit.commission_agentnum = $agentnum\n"; +} +my $title = $sel_agent ? $sel_agent->agent.' ' : ''; + +# search for credits in time period (applied to payments in $query) +my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi); +$extra_sql .= " AND cust_credit._date >= $beginning\n"; +$extra_sql .= " AND cust_credit._date <= $ending\n"; + +# agent virtualization +my $agentnums_sql = $curuser->agentnums_sql( table => 'agent' ); + +my $join = <<END; + LEFT JOIN agent ON ( cust_credit.commission_agentnum = agent.agentnum ) + LEFT JOIN cust_pkg ON ( cust_credit.commission_pkgnum = cust_pkg.pkgnum ) + LEFT JOIN part_pkg ON ( cust_pkg.pkgpart = part_pkg.pkgpart ) +END + +my $query = <<END; +SELECT DISTINCT + agent, + cust_pkg.custnum AS xcustnum, + cust_credit.commission_pkgnum AS xpkgnum, + pkg, + 'cust_pay' AS xtable, + cust_pay.paynum AS xnum, + to_timestamp(cust_pay._date) AS xdate, + cust_pay.paid AS xamount, + cust_pay.order_number AS order_number +FROM cust_pay + INNER JOIN cust_bill_pay ON ( cust_pay.paynum = cust_bill_pay.paynum ) + INNER JOIN cust_bill_pkg ON ( cust_bill_pay.invnum = cust_bill_pkg.invnum ) + INNER JOIN cust_credit ON ( cust_bill_pkg.pkgnum = cust_credit.commission_pkgnum ) +$join +WHERE cust_credit.commission_pkgnum IS NOT NULL + AND cust_pay._date >= $beginning + AND cust_pay._date <= $ending + AND $agentnums_sql +$extra_sql +UNION +SELECT DISTINCT + agent, + cust_pkg.custnum AS xcustnum, + cust_credit.commission_pkgnum AS xpkgnum, + pkg, + 'cust_credit' AS xtable, + cust_credit.crednum AS xnum, + to_timestamp(cust_credit._date) AS xdate, + cust_credit.amount AS xamount, + '' AS order_number +FROM cust_credit +$join +WHERE cust_credit.commission_pkgnum is not null + AND $agentnums_sql +$extra_sql +ORDER BY agent, xcustnum, xpkgnum, xdate +END + +my $sth = dbh->prepare($query) or die dbh->errstr; +$sth->execute() or die $sth->errstr; + +my $cells = []; +my $rows = []; +my $agentstack = []; +my $custstack = []; +my $pkgstack = []; +my ($prev_agent,$count_agent,$prev_cust,$count_cust,$prev_pkg,$count_pkg); +while (my $row = $sth->fetchrow_arrayref) { + my @row = @$row; + my $curr_agent = shift @row; + my $curr_cust = shift @row; + my $curr_pkg = (shift @row) . ': ' . (shift @row); + + if ($curr_pkg eq $prev_pkg) { + $count_pkg += 1; + } else { + unshift @{$$pkgstack[0]}, { value => $prev_pkg, rowspan => $count_pkg } if @$pkgstack;; + push @$custstack, @$pkgstack; + $pkgstack = []; + $count_pkg = 1; + } + $prev_pkg = $curr_pkg; + + if ($curr_cust eq $prev_cust) { + $count_cust += 1; + } else { + if (@$custstack) { + my $cust_main = qsearchs('cust_main',{ custnum => $prev_cust }); + unshift @{$$custstack[0]}, { value => $cust_main->name, rowspan => $count_cust } if @$custstack;; + } + push @$agentstack, @$custstack; + $custstack = []; + $count_cust = 1; + } + $prev_cust = $curr_cust; + + if ($curr_agent eq $prev_agent) { + $count_agent += 1; + } else { + unshift @{$$agentstack[0]}, { value => $prev_agent, rowspan => $count_agent } if @$agentstack;; + push @$cells, @$agentstack; + $agentstack = []; + $count_agent = 1; + } + $prev_agent = $curr_agent; + + my %coloropts = ($row[0] eq 'cust_credit') ? ( 'class' => 'creditcell' ) : ( 'class' => 'paycell' ); + push @$pkgstack, [ map { { value => $_, %coloropts } } @row ]; +} + +unshift @{$$pkgstack[0]}, { value => $prev_pkg, rowspan => $count_pkg } if @$pkgstack;; +push @$custstack, @$pkgstack; +if (@$custstack) { + my $cust_main = qsearchs('cust_main',{ custnum => $prev_cust }); + unshift @{$$custstack[0]}, { value => $cust_main->name, rowspan => $count_cust } if @$custstack;; +} +push @$agentstack, @$custstack; +unshift @{$$agentstack[0]}, { value => $prev_agent, rowspan => $count_agent } if @$agentstack;; +push @$cells, @$agentstack; + +$sth->finish; + +my $rows = [ map { {} } @$cells ]; + +unshift @$cells, [ map { { value => $_, header => 1 } } ('Agent','Customer','Package','Table','#','Date','Amount','Order Number') ]; +unshift @$rows, { header => 1 }; + +</%init> diff --git a/httemplate/search/contact.html b/httemplate/search/contact.html index c3667df98..44c864c16 100644 --- a/httemplate/search/contact.html +++ b/httemplate/search/contact.html @@ -22,6 +22,12 @@ push @select, map "contact.$_", qw( first last title ); my %hash = (); my $addl_from = ''; +my $email_sub = sub { + my $contact = shift; + my @contact_email = $contact->contact_email; + join(', ', map $_->emailaddress, @contact_email); +}; + my $link; #for closure in this sub, we'll define it later my $contact_classname_sub = sub { my $contact = shift; @@ -37,9 +43,9 @@ my $contact_classname_sub = sub { $X_contact->contact_classname; }; -my @header = ( 'First', 'Last', 'Title', 'Type' ); -my @fields = ( 'first', 'last', 'title', $contact_classname_sub ); -my @links = ( '', '', '', '', ); +my @header = ( 'First', 'Last', 'Title', 'Email', 'Type' ); +my @fields = ( 'first', 'last', 'title', $email_sub, $contact_classname_sub ); +my @links = ( '', '', '', '', '', ); my $company_link = ''; diff --git a/httemplate/search/cust_bill_pay_pkg.html b/httemplate/search/cust_bill_pay_pkg.html index 5a3be7551..e2ffd1258 100644 --- a/httemplate/search/cust_bill_pay_pkg.html +++ b/httemplate/search/cust_bill_pay_pkg.html @@ -14,6 +14,7 @@ #payment 'Date', + @on_header, 'By', #application @@ -43,6 +44,7 @@ ? cardtype($cust_pay->paymask) : ''; }, sub { time2str('%b %d %Y', shift->get('cust_pay_date') ) }, + @on_field, sub { shift->cust_bill_pay->cust_pay->otaker }, sub { sprintf($money_char.'%.2f', shift->amount ) }, @@ -64,6 +66,7 @@ '', #payinfo/paymask '', #cardtype 'cust_pay_date', + @on_null, #order_number '', #'otaker', '', #amount '', #line item description @@ -80,6 +83,7 @@ '', '', '', + @on_null, '', '', '', @@ -92,10 +96,9 @@ FS::UI::Web::cust_header() ), ], - 'align' => 'rcrlrlrlll', -#original value before cardtype & package were added -#why are there 13 cols? -#'rcrrlrlllrrcl'. + 'align' => 'rcrlr'. + $on_align. + 'lrlll'. $post_desc_align. 'rr'. FS::UI::Web::cust_aligns(), @@ -105,6 +108,7 @@ '', '', '', + @on_null, '', '', '', @@ -121,6 +125,7 @@ '', '', '', + @on_null, '', '', '', @@ -142,6 +147,17 @@ die "access denied" unless $FS::CurrentUser::CurrentUser->access_right('Financial reports'); +my @on_header = (); +my @on_field = (); +my @on_null = (); +my $on_align = ''; +if ($cgi->param('show_order_number')) { + @on_header = ('Order Number'); + @on_field = (sub { shift->cust_bill_pay->cust_pay->order_number }); + @on_null = (''); + $on_align = 'r'; +} + my $conf = new FS::Conf; my %payby = FS::payby->payby2shortname; diff --git a/httemplate/search/cust_credit.html b/httemplate/search/cust_credit.html index 2241f02e3..dbf0ff333 100755 --- a/httemplate/search/cust_credit.html +++ b/httemplate/search/cust_credit.html @@ -56,24 +56,29 @@ if ($unapplied) { push @header, emt('Date'), emt('By'), emt('Reason'), + emt('Info'), ; push @fields, sub { time2str('%b %d %Y', shift->_date ) }, 'otaker', - 'reason', + 'reason_only', + 'addlinfo', ; -push @sort_fields, '_date', 'otaker', 'reason'; -$align .= 'rll'; +push @sort_fields, '_date', 'otaker', 'reasonnum', 'addlinfo'; +$align .= 'rlll'; push @links, '', '', '', + '', ; push @color, '', '', '', + '', ; push @style, '', '', '', + '', ; # insert customer email after 'Reason' if this is a commission report diff --git a/httemplate/search/cust_credit_bill_pkg.html b/httemplate/search/cust_credit_bill_pkg.html index b9bbc4dbb..0cdd8defd 100644 --- a/httemplate/search/cust_credit_bill_pkg.html +++ b/httemplate/search/cust_credit_bill_pkg.html @@ -14,6 +14,7 @@ 'Date', 'By', 'Reason', + 'Info', # line item 'Description', @@ -33,7 +34,8 @@ sub { time2str('%b %d %Y', shift->get('cust_credit_date') ) }, sub { shift->cust_credit_bill->cust_credit->otaker }, - sub { shift->cust_credit_bill->cust_credit->reason }, + sub { shift->cust_credit_bill->cust_credit->reason_only }, + sub { shift->cust_credit_bill->cust_credit->addlinfo }, sub { $_[0]->pkgnum > 0 ? $_[0]->get('pkg') # possibly use override.pkg @@ -51,6 +53,7 @@ 'cust_credit_date', '', #'otaker', '', #reason + '', #addlinfo '', #line item description '', #location @post_desc_null, @@ -66,6 +69,7 @@ '', '', '', + '', @post_desc_null, $ilink, $ilink, @@ -73,7 +77,7 @@ FS::UI::Web::cust_header() ), ], - 'align' => 'rrrllll'. + 'align' => 'rrrlllll'. $post_desc_align. 'rr'. FS::UI::Web::cust_aligns(), @@ -85,6 +89,7 @@ '', '', '', + '', @post_desc_null, '', '', @@ -98,6 +103,7 @@ '', '', '', + '', @post_desc_null, '', '', diff --git a/httemplate/search/cust_credit_source_bill_pkg.html b/httemplate/search/cust_credit_source_bill_pkg.html index 3ef88bdf9..1d5f8d2a0 100644 --- a/httemplate/search/cust_credit_source_bill_pkg.html +++ b/httemplate/search/cust_credit_source_bill_pkg.html @@ -28,7 +28,7 @@ sub { time2str('%b %d %Y', shift->get('cust_credit_date') ) }, sub { shift->cust_credit->otaker }, - sub { shift->cust_credit->reason }, + sub { shift->cust_credit->reason }, # split into reason_only/addlinfo if addlinfo ever gets used here sub { $_[0]->pkgnum > 0 ? $_[0]->get('pkg') # possibly use override.pkg diff --git a/httemplate/search/cust_credit_void.html b/httemplate/search/cust_credit_void.html index 18731d144..8a8b4133e 100755 --- a/httemplate/search/cust_credit_void.html +++ b/httemplate/search/cust_credit_void.html @@ -47,6 +47,7 @@ push @header, emt('Void Date'), emt('Date'), emt('By'), emt('Reason'), + emt('Info'), FS::UI::Web::cust_header(), ; push @fields, sub { time2str('%b %d %Y', shift->void_date ) }, @@ -54,7 +55,8 @@ push @fields, sub { time2str('%b %d %Y', shift->void_date ) }, 'void_reason', sub { time2str('%b %d %Y', shift->_date ) }, 'otaker', - 'reason', + 'reason_only', + 'addlinfo', \&FS::UI::Web::cust_fields, ; push @sort_fields, 'void_date', @@ -63,14 +65,16 @@ push @sort_fields, 'void_date', '_date', 'usernum', #ditto 'reasonnum, reason', #ditto + 'addlinfo', FS::UI::Web::cust_sort_fields(); -$align .= 'rllrll'.FS::UI::Web::cust_aligns(); +$align .= 'rllrlll'.FS::UI::Web::cust_aligns(); push @links, '', '', '', '', '', '', + '', ( map { $_ ne 'Cust. Status' ? $clink : '' } FS::UI::Web::cust_header() ), @@ -81,6 +85,7 @@ push @color, '', '', '', '', + '', FS::UI::Web::cust_colors(), ; push @style, '', @@ -89,6 +94,7 @@ push @style, '', '', '', '', + '', FS::UI::Web::cust_styles(), ; diff --git a/httemplate/search/cust_event.html b/httemplate/search/cust_event.html index 757982b95..b1ba9090e 100644 --- a/httemplate/search/cust_event.html +++ b/httemplate/search/cust_event.html @@ -1,6 +1,6 @@ <& elements/search.html, 'title' => $title, - 'html_init' => $html_init, + 'html_init' => include('.init'), 'menubar' => $menubar, 'name' => 'billing events', 'query' => $sql_query, @@ -140,6 +140,11 @@ my $trigger_link = sub { }; </%once> +<%shared> +my @scalars = qw( agentnum status custnum invnum pkgnum failed ); +my @lists = qw( eventpart event_status ); +my %search; +</%shared> <%init> my $curuser = $FS::CurrentUser::CurrentUser; @@ -152,18 +157,19 @@ die "access denied" || $cgi->param('pkgnum') =~ /^(\d+)$/ ); -my $title = $cgi->param('failed') ? 'Failed billing events' : 'Billing events'; - -my %search = (); +my @statuses = $cgi->param('event_status'); +my $title = 'Billing events'; +if ( $statuses[0] eq 'failed' and !defined($statuses[1]) ) { + # tweak the title if we're showing only failed events + $title = 'Failed billing events'; +} -my @scalars = qw( agentnum status custnum invnum pkgnum failed ); for my $param (@scalars) { $search{$param} = scalar( $cgi->param($param) ) if $cgi->param($param); } #lists -my @lists = qw( eventpart ); foreach my $param (@lists) { $search{$param} = [ $cgi->param($param) ]; } @@ -175,12 +181,16 @@ $search{'ending'} = $ending; my $where = ' WHERE '. FS::cust_event->search_sql_where( \%search ); my $join = FS::cust_event->join_sql() . + # warning: does not show the true service address for package events. + # the query to do that would be painfully slow. 'LEFT JOIN cust_location bill_location '. 'ON (cust_main.bill_locationnum = bill_location.locationnum) '. 'LEFT JOIN cust_location ship_location '. - 'ON (cust_main.ship_locationnum = ship_location.locationnum)'; - # warning: does not show the true service address for package events. - # the query to do that would be painfully slow. + 'ON (cust_main.ship_locationnum = ship_location.locationnum)'. + # include link to referral in case it's in cust-fields + # (maybe we should be using FS::UI::Web::join_cust_main instead?) + 'LEFT JOIN (select refnum, referral from part_referral) AS part_referral_x '. + 'ON (cust_main.refnum = part_referral_x.refnum) '; my $sql_query = { 'table' => 'cust_event', @@ -203,55 +213,6 @@ my $count_sql = "SELECT COUNT(*) FROM cust_event $join $where"; my $conf = new FS::Conf; -my @params = ( @scalars, qw( beginning ending ) ); - -my $html_init = join("\n", map { - ( my $action = $_ ) =~ s/_$//; - include('/elements/progress-init.html', - $_.'form', - [ 'action', @params ], - "../misc/${_}events.cgi", - { 'message' => "Invoices re-${action}ed" }, #would be nice to show the number of them, but... - $_, #key - ), - qq!<FORM NAME="${_}form">!, - qq!<INPUT TYPE="hidden" NAME="action" VALUE="$_">!, #not used though - ( map { my $value = encode_entities( $search{$_} ); - qq(<INPUT TYPE="hidden" NAME="$_" VALUE="$value">); - } - @params #keys %search - ), - ( map { my $value = encode_entities( join(',', @{ $search{$_} } ) ); - qq(<INPUT TYPE="hidden" NAME="$_" VALUE="$value">); - } - @lists - ), - qq!</FORM>! -} qw( print_ email_ fax_ ) ). - -'<SCRIPT TYPE="text/javascript"> - -function confirm_print_process() { - if ( ! confirm("Are you sure you want to reprint these invoices?") ) { - return; - } - print_process(); -} -function confirm_email_process() { - if ( ! confirm("Are you sure you want to re-email these invoices?") ) { - return; - } - email_process(); -} -function confirm_fax_process() { - if ( ! confirm("Are you sure you want to re-fax these invoices?") ) { - return; - } - fax_process(); -} - -</SCRIPT>'; - my $menubar = []; if ( $curuser->access_right('Resend invoices') ) { @@ -276,3 +237,46 @@ my $link_cust = sub { }; </%init> +<%def .init> +% # action is part of the target URL, don't need to pass it as a param +% foreach my $action (qw(print email fax)) { +<& /elements/progress-init.html, + $action.'_form', + [ @scalars, @lists, 'beginning', 'ending' ], + "../misc/${action}_events.cgi", + { 'message' => "Invoices re-${action}ed" }, #would be nice to show the number of them, but... + $action.'_', #key +&> +<FORM NAME="<% $action %>_form"> +% foreach my $param (@scalars, 'beginning', 'ending') { + <INPUT TYPE="hidden" NAME="<% $param %>" VALUE="<% $search{$param} |h %>"> +% } +% foreach my $param (@lists) { +% foreach my $value (@{ $search{$param} }) { + <INPUT TYPE="hidden" NAME="<% $param %>" VALUE="<% $value |h %>"> +% } +% } +</FORM> +% } # foreach $action +<SCRIPT TYPE="text/javascript"> + +function confirm_print_process() { + if ( ! confirm("Are you sure you want to reprint these invoices?") ) { + return; + } + print_process(); +} +function confirm_email_process() { + if ( ! confirm("Are you sure you want to re-email these invoices?") ) { + return; + } + email_process(); +} +function confirm_fax_process() { + if ( ! confirm("Are you sure you want to re-fax these invoices?") ) { + return; + } + fax_process(); +} +</SCRIPT> +</%def> diff --git a/httemplate/search/cust_main.cgi b/httemplate/search/cust_main.cgi index 96f5af913..da6c89d44 100755 --- a/httemplate/search/cust_main.cgi +++ b/httemplate/search/cust_main.cgi @@ -120,9 +120,21 @@ % foreach my $cust_pkg ( @{$all_pkgs{$custnum}} ) { % my %cust_svc_by_svcpart; % my $rows = 0; -% local($FS::part_pkg::cache_enabled) = 1; #for $cust_pkg->part_svc +% #local($FS::part_pkg::cache_enabled) = 1; #for $cust_pkg->part_svc +% local($FS::cust_svc::cache_enabled) = 1; #for $cust_svc->part_svc +% #local($FS::pkg_svc::cache_enabled) = 1; #for $pkg_svc->part_svc % foreach my $part_svc ( -% $cust_pkg->part_svc( summarize_size=>$large_pkg_size ) +% #$cust_pkg->part_svc( summarize_size=>$large_pkg_size ) +% qsearch({ +% 'select' => 'part_svc.*, COUNT(*) AS num_cust_svc', +% 'table' => 'part_svc', +% 'addl_from' => 'LEFT JOIN cust_svc USING ( svcpart )', +% 'extra_sql' => 'WHERE pkgnum = ? '. +% ' GROUP BY '. join(', ', +% map "part_svc.$_", fields('part_svc') +% ), +% 'extra_param' => [ [$cust_pkg->pkgnum,'int'] ], +% }) % ) { % my $svcpart = $part_svc->svcpart; % my $num_cust_svc = $part_svc->num_cust_svc; @@ -134,7 +146,9 @@ % $rows += 2; % } % elsif ( $num_cust_svc ) { -% $cust_svc_by_svcpart{$svcpart} = $part_svc->cust_pkg_svc; +% #$cust_svc_by_svcpart{$svcpart} = $part_svc->cust_pkg_svc; +% #further optimization opportunities: don't need to re-pull in another $part_svc object, sorting this is expensive, etc. +% $cust_svc_by_svcpart{$svcpart} = [ $cust_pkg->cust_svc($part_svc->svcpart) ]; % $rows += $num_cust_svc; % } #if summarize % } #foreach $part_svc @@ -234,6 +248,7 @@ % my $n1 = ''; % foreach ( @{$all_pkgs{$custnum}} ) { +% local($FS::cust_svc::cache_enabled) = 1; #for $cust_svc->part_svc % my $pkgnum = $_->pkgnum; % my $part_pkg = $_->part_pkg; % @@ -281,12 +296,13 @@ % } % elsif ( scalar @$these ) { # do not summarize % foreach my $cust_svc ( @$these ) { +% my $part_svc = $cust_svc->part_svc; <% $n2 %> <% $td %> - <% FS::UI::Web::svc_link($m, $cust_svc->part_svc, $cust_svc) %> + <% FS::UI::Web::svc_link($m, $part_svc, $cust_svc) %> </TD> <% $td %> - <% FS::UI::Web::svc_label_link($m, $cust_svc->part_svc, $cust_svc) %> + <% FS::UI::Web::svc_label_link($m, $part_svc, $cust_svc) %> </TD> % $n2="</TR><TR>"; % } #foreach $cust_svc @@ -485,7 +501,7 @@ if ( $cgi->param('browse') ); } - @cust_main = grep { $_->num_ncancelled_pkgs || ! $_->num_pkgs } @cust_main + @cust_main = grep { $_->status ne 'cancelled' } @cust_main if ! $cgi->param('cancelled') && ( $cgi->param('showcancelledcustomers') eq '0' #see if it was set by me @@ -517,8 +533,9 @@ if ( scalar(@cust_main) > 1 || $cgi->param('referral_custnum') ) { local($FS::cust_pkg::cache_enabled) = 1; #for $cust_pkg->part_pkg %all_pkgs = map { $_->custnum => - [ $_->$pkgs_method({ select => $select, - addl_from => $addl_from, + [ $_->$pkgs_method({ select => $select, + addl_from => $addl_from, + skip_label_sort => 1, }) ]; } @@ -686,12 +703,20 @@ sub address2search { or errorpage(emt("Illegal address2")); my $address2 = $1; - push @cust_main, qsearch( 'cust_main', - { 'address2' => { 'op' => 'ILIKE', - 'value' => $address2 } } ); - push @cust_main, qsearch( 'cust_main', - { 'ship_address2' => { 'op' => 'ILIKE', - 'value' => $address2 } } ); + # matching at the start or end of an address, but not in the middle + my @where; + foreach my $toggle (0,1) { + push @where, 'LOWER(cust_location.address2) LIKE LOWER(' + . dbh->quote($toggle ? $address2 . '%' : '%' . $address2) + . ')'; + } + + push @cust_main, qsearch({ + 'debug' => 1, + 'table' => 'cust_main', + 'addl_from' => 'JOIN cust_location ON (cust_location.locationnum IN (cust_main.bill_locationnum, cust_main.ship_locationnum))', + 'extra_sql' => 'WHERE ' . join(' OR ',@where), + }); \@cust_main; } diff --git a/httemplate/search/cust_pay_pending.html b/httemplate/search/cust_pay_pending.html index 8662d1989..697bdbbf0 100755 --- a/httemplate/search/cust_pay_pending.html +++ b/httemplate/search/cust_pay_pending.html @@ -17,7 +17,7 @@ my %statusaction = ( 'new' => 'delete', 'pending' => 'complete', - #'authorized' => '', + 'authorized' => 'complete', 'captured' => 'capture', #'declined' => '', #wouldn't need to take action on a done state#'done' diff --git a/httemplate/search/cust_pkg-date.html b/httemplate/search/cust_pkg-date.html new file mode 100644 index 000000000..1b9377546 --- /dev/null +++ b/httemplate/search/cust_pkg-date.html @@ -0,0 +1,90 @@ +<%init> +my $curuser = $FS::CurrentUser::CurrentUser; +die 'access denied' unless $curuser->access_right('List packages'); + +my %cols = ( + 'contract_end' => 'Contract end', + # We could put any of the date fields in cust_pkg in here, but keep in + # mind: + # - for start_date, setup, and bill, make sure to include rows where + # the field is null, as that's effectively "right now". + # - for cancel and susp, and maybe expire, adjourn, and resume, add a + # column for the cancel or suspend reason. + # - for expire, also figure out if there's a future change scheduled. + # - for change_date, should probably show what it was changed from. +); + +my $col = $cgi->param('date'); +die "invalid date column" unless $cols{$col}; + +my $title = 'Packages by ' . lc($cols{$col}) . ' date'; +# second option on the cust_fields_avail list, plus email +my $cust_fields = 'Cust# | Customer | Day phone | Night phone | Mobile phone | Invoicing email(s)'; +my @header = ( $cols{$col}, + emt('#'), + emt('Quan.'), + emt('Package'), + # anything else? package status, maybe? + ); +my @fields = ( sub { time2str('%b %d %Y', $_[0]->$col) }, + 'pkgnum', + 'quantity', + 'pkg_label', + ); +my @sort_fields = ( map '', @fields ); # should only ever sort by $col + +push @header, FS::UI::Web::cust_header($cust_fields); +push @fields, \&FS::UI::Web::cust_fields; + +my $query = { + 'table' => 'cust_pkg', + 'addl_from' => FS::UI::Web::join_cust_main('cust_pkg', 'cust_pkg'), + 'hashref' => { + $col => { op => '!=', value => '' }, + 'cancel' => '', + }, + 'order_by' => "ORDER BY $col", +}; + +my $count_query = + "SELECT COUNT(*) FROM cust_pkg WHERE $col IS NOT NULL AND cancel IS NULL"; + +my $pkg_link = sub { + my $self = shift; + my $frag = 'cust_pkg'. $self->pkgnum; + [ "${p}view/cust_main.cgi?custnum=".$self->custnum. + ";show=packages;fragment=$frag#cust_pkg", + 'pkgnum' + ]; +}; + +my @links = ( '', ($pkg_link) x 3, + FS::UI::Web::cust_links() ); + +my $date_color_sub = sub { + my $self = shift; + my $color; + my $interval = ($self->$col - time) / 86400; + if ( $interval > 30 ) { + $color = 'palegreen'; + } elsif ( $interval > 0 ) { + $color = 'yellow'; + } else { + $color = 'tomato'; + } + "background-color: $color"; +}; + +</%init> +<& elements/search.html, + 'title' => $title, + 'name' => 'packages', + 'query' => $query, + 'count_query' => $count_query, + 'header' => \@header, + 'fields' => \@fields, + 'align' => 'rrrl'. FS::UI::Web::cust_aligns(), + 'links' => \@links, + 'cell_style' => [ $date_color_sub ], +&> + diff --git a/httemplate/search/cust_pkg_churn.html b/httemplate/search/cust_pkg_churn.html index 30962c996..4c7e7e8b2 100644 --- a/httemplate/search/cust_pkg_churn.html +++ b/httemplate/search/cust_pkg_churn.html @@ -18,7 +18,7 @@ emt('Susp.'), emt('Changed'), emt('Cancel'), - #emt('Reason'), # hard to do this right + @reason_header, FS::UI::Web::cust_header( $cgi->param('cust_fields') ), @@ -45,6 +45,7 @@ ( map { time_or_blank($_) } qw( setup last_bill bill susp change_date cancel ) ), + @reason_fields, \&FS::UI::Web::cust_fields, ], 'sort_fields' => [ @@ -53,21 +54,25 @@ ('') x 3, # can't use at all # use the plain SQL column names qw( setup last_bill bill susp change_date cancel ), + @reason_blank, # cust_fields can take care of themselves ], 'color' => [ ('') x 15, + @reason_blank, FS::UI::Web::cust_colors(), ], 'style' => [ ('') x 15, + @reason_blank, FS::UI::Web::cust_styles() ], 'size' => [ '', '', '', '', '-1' ], - 'align' => 'rrlcccrrlrrrrrr'. FS::UI::Web::cust_aligns(). 'r', + 'align' => 'rrlcccrrlrrrrrr'.$reason_align. FS::UI::Web::cust_aligns(). 'r', 'links' => [ $link, $link, $link, ('') x 12, + @reason_blank, ( map { $_ ne 'Cust. Status' ? $clink : '' } FS::UI::Web::cust_header( $cgi->param('cust_fields') @@ -184,4 +189,16 @@ sub time_or_blank { }; } +my (@reason_header,@reason_fields,@reason_blank); +my $reason_align = ''; +if ($status eq 'cancel') { + push @reason_header, emt('Cancel Reason'); + push @reason_fields, sub { + my $c = shift; + my $cust_pkg_reason = $c->last_cust_pkg_reason('cancel'); + $cust_pkg_reason ? $cust_pkg_reason->reason->reason : ''; + }; + push @reason_blank, ''; + $reason_align = 'l'; +} </%init> diff --git a/httemplate/search/cust_svc.html b/httemplate/search/cust_svc.html index 3b770432e..b2826309a 100644 --- a/httemplate/search/cust_svc.html +++ b/httemplate/search/cust_svc.html @@ -1,50 +1,66 @@ <& elements/search.html, 'title' => emt('Service search results'), - 'name' => emt('services'), - 'query' => $sql_query, - 'count_query' => $count_query, - 'redirect' => $link, - 'header' => [ emt('#'), - emt('Service'), - # package? - FS::UI::Web::cust_header(), - ], - 'fields' => [ 'svcnum', - sub { - #$_[0]->svc. ': '. $_[0]->label; - my($label, $value, $svcdb) = $_[0]->label; - my $id = $_[0]->agent_svcid - ? $_[0]->agent_svcid.': ' - : ''; - "$label: $id$value"; - }, - # package? - \&FS::UI::Web::cust_fields, - ], - 'links' => [ $link, - $link, - # package? - ( map { $_ ne 'Cust. Status' ? $link_cust : '' } - FS::UI::Web::cust_header() - ), - ], - 'align' => 'rl'. FS::UI::Web::cust_aligns(), - 'color' => [ - '', - '', - FS::UI::Web::cust_colors(), - ], - 'style' => [ - '', - '', - FS::UI::Web::cust_styles(), - ], + 'name' => emt('services'), + 'query' => $sql_query, + 'count_query' => $count_query, + 'redirect' => $link, + 'header' => [ emt('#'), + emt('Service'), + emt('Pkg. Status'), + # package? + FS::UI::Web::cust_header(), + ], + 'fields' => [ 'svcnum', + sub { + #$_[0]->svc. ': '. $_[0]->label; + my($label, $value, $svcdb) = $_[0]->label; + my $id = $_[0]->agent_svcid + ? $_[0]->agent_svcid.': ' + : ''; + "$label: $id$value"; + }, + sub { + $cust_pkg_cache{$_[0]->svcnum} ||= $_[0]->cust_pkg; + return '' unless $cust_pkg_cache{$_[0]->svcnum}; + $cust_pkg_cache{$_[0]->svcnum}->ucfirst_status + }, + # package? + \&FS::UI::Web::cust_fields, + ], + 'links' => [ $link, + $link, + '', # pkg status + # package? + ( map { $_ ne 'Cust. Status' ? $link_cust : '' } + FS::UI::Web::cust_header() + ), + ], + 'align' => 'rlr'. FS::UI::Web::cust_aligns(), + 'color' => [ + '', + '', + sub { + $cust_pkg_cache{$_[0]->svcnum} ||= $_[0]->cust_pkg; + return '' unless $cust_pkg_cache{$_[0]->svcnum}; + my $c = FS::cust_pkg::statuscolors; + $c->{$cust_pkg_cache{$_[0]->svcnum}->status }; + }, # pkg status + FS::UI::Web::cust_colors(), + ], + 'style' => [ + '', + '', + 'b', # pkg status + FS::UI::Web::cust_styles(), + ], &> <%init> die "access denied" unless $FS::CurrentUser::CurrentUser->access_right('List services'); +my %cust_pkg_cache; + my $sql_query; my $orderby = 'ORDER BY cust_svc.svcnum'; #has to be ordered by something @@ -83,6 +99,13 @@ if ( length( $cgi->param('search_svc') ) ) { } elsif ( $cgi->param('svcpart') =~ /^(\d+)$/ ) { push @extra_sql, "svcpart = $1"; + if (defined($cgi->param('cancelled'))) { + if ($cgi->param('cancelled')) { + push @extra_sql, "cust_pkg.cancel IS NOT NULL"; + } else { + push @extra_sql, "cust_pkg.cancel IS NULL"; + } + } } else { errorpage("No search term specified"); diff --git a/httemplate/search/elements/cust_pay_or_refund.html b/httemplate/search/elements/cust_pay_or_refund.html index 7d7fb73e9..1b1be5f36 100755 --- a/httemplate/search/elements/cust_pay_or_refund.html +++ b/httemplate/search/elements/cust_pay_or_refund.html @@ -63,10 +63,20 @@ Examples: sub { sprintf($money, $_[0]->$amount_field) }, ], 'total_row' => [ '<B>Total</B>', - sub { warn Dumper @_; sprintf("<B>$money</B>", $_[0]->$amount_field) }, + sub { sprintf("<B>$money</B>", $_[0]->$amount_field) }, ], 'show_combined' => 1, &> +<%shared> +# canonicalize the payby subtype string to an SQL-quoted list +my %cardtype_of = ( + 'VisaMC' => q['VISA card', 'MasterCard'], + 'Amex' => q['American Express card'], + 'Discover' => q['Discover card'], + 'Maestro' => q['Switch', 'Solo', 'Laser'], + 'Tokenized' => q['Tokenized'], +); +</%shared> <%init> my %opt = @_; @@ -91,29 +101,30 @@ my $title = ''; $title = 'Unapplied ' if $unapplied; $title .= "\u$name_singular Search Results"; -my $link = ''; -if ( ( $curuser->access_right('View invoices') #remove in 2.5 (2.7?) - || ($curuser->access_right('View payments') && $table =~ /^cust_pay/) - || ($curuser->access_right('View refunds') && $table eq 'cust_refund') - ) - && ! $opt{'disable_link'} - ) -{ - - my $key; - my $q = ''; - if ( $table eq 'cust_pay_void' ) { - $key = 'paynum'; - $q .= 'void=1;'; - } elsif ( $table eq /^cust_(\w+)$/ ) { - $key = $1.'num'; - } - - if ( $key ) { - $q .= "$key="; - $link = [ "${p}view/$table.html?$q", $key ] - } -} +###NOT USED??? +#my $link = ''; +#if ( ( $curuser->access_right('View invoices') #remove in 2.5 (2.7?) +# || ($curuser->access_right('View payments') && $table =~ /^cust_pay/) +# || ($curuser->access_right('View refunds') && $table eq 'cust_refund') +# ) +# && ! $opt{'disable_link'} +# ) +#{ +# +# my $key; +# my $q = ''; +# if ( $table eq 'cust_pay_void' ) { +# $key = 'paynum'; +# $q .= 'void=1;'; +# } elsif ( $table eq /^cust_(\w+)$/ ) { +# $key = $1.'num'; +# } +# +# if ( $key ) { +# $q .= "$key="; +# $link = [ "${p}view/$table.html?$q", $key ] +# } +#} my $cust_link = sub { my $cust_thing = shift; @@ -166,12 +177,18 @@ if ( $opt{'pre_header'} ) { push @sort_fields, @{ $opt{'pre_fields'} }; } -my $sub_receipt = sub { +my $sub_receipt = $opt{'disable_link'} ? '' : sub { my $obj = shift; my $objnum = $obj->primary_key . '=' . $obj->get($obj->primary_key); + my $table = $obj->table; + my $void = ''; + if ($table eq 'cust_pay_void') { + $table = 'cust_pay'; + $void = ';void=1'; + } include('/elements/popup_link_onclick.html', - 'action' => $p.'view/cust_pay.html?link=popup;'.$objnum, + 'action' => $p.'view/'.$table.'.html?link=popup;'.$objnum.$void, 'actionlabel' => emt('Payment Receipt'), ); }; @@ -191,10 +208,8 @@ if ($opt{'show_card_type'}) { push @header, emt('Card Type'); $align .= 'r'; push @links, ''; - push @fields, sub { - (($_[0]->payby eq 'CARD') && ($_[0]->paymask !~ /N\/A/)) ? cardtype($_[0]->paymask) : '' - }; - push @sort_fields, ''; + push @fields, 'paycardtype'; + push @sort_fields, 'paycardtype'; } if ( $unapplied ) { @@ -211,6 +226,14 @@ push @links, ''; push @fields, sub { time2str('%b %d %Y', shift->_date ) }; push @sort_fields, '_date'; +if ($cgi->param('show_order_number')) { + push @header, emt('Order Number'); + $align .= 'r'; + push @links, ''; + push @fields, 'order_number'; + push @sort_fields, 'order_number'; +} + unless ( $opt{'disable_by'} ) { push @header, emt('By'); $align .= 'c'; @@ -297,125 +320,23 @@ if ( $cgi->param('magic') ) { if ( $cgi->param('payby') ) { my @all_payby_search = (); - foreach my $payby ( $cgi->param('payby') ) { - - $payby =~ - /^(CARD|CHEK|BILL|CASH|PPAL|APPL|ANRD|PREP|WIRE|WEST|IDTP|EDI|MCRD|MCHK)(-(VisaMC|Amex|Discover|Maestro|Tokenized))?$/ - or die "illegal payby $payby"; - - my $payby_search = "$table.payby = '$1'"; - - if ( $3 ) { - - my $cardtype = $3; - - my $similar_to = dbh->{Driver}->{Name} =~ /^mysql/i - ? 'REGEXP' #doesn't behave exactly the same, but - #should work for our patterns - : 'SIMILAR TO'; - - my $search; - if ( $cardtype eq 'VisaMC' ) { - - #avoid posix regexes for portability - $search = - " ( ( substring($table.payinfo from 1 for 1) = '4' ". - " AND substring($table.payinfo from 1 for 4) != '4936' ". - " AND substring($table.payinfo from 1 for 6) ". - " NOT $similar_to '49030[2-9]' ". - " AND substring($table.payinfo from 1 for 6) ". - " NOT $similar_to '49033[5-9]' ". - " AND substring($table.payinfo from 1 for 6) ". - " NOT $similar_to '49110[1-2]' ". - " AND substring($table.payinfo from 1 for 6) ". - " NOT $similar_to '49117[4-9]' ". - " AND substring($table.payinfo from 1 for 6) ". - " NOT $similar_to '49118[1-2]' ". - " )". - " OR substring($table.payinfo from 1 for 2) = '51' ". - " OR substring($table.payinfo from 1 for 2) = '52' ". - " OR substring($table.payinfo from 1 for 2) = '53' ". - " OR substring($table.payinfo from 1 for 2) = '54' ". - " OR substring($table.payinfo from 1 for 2) = '54' ". - " OR substring($table.payinfo from 1 for 2) = '55' ". -# " OR substring($table.payinfo from 1 for 2) = '36' ". #Diner's int'l was processed as Visa/MC inside US, now Discover - " ) "; - - } elsif ( $cardtype eq 'Amex' ) { - - $search = - " ( substring($table.payinfo from 1 for 2 ) = '34' ". - " OR substring($table.payinfo from 1 for 2 ) = '37' ". - " ) "; - - } elsif ( $cardtype eq 'Discover' ) { - - my $country = $conf->config('countrydefault') || 'US'; - - $search = - " ( substring($table.payinfo from 1 for 4 ) = '6011' ". - " OR substring($table.payinfo from 1 for 2 ) = '65' ". - " OR substring($table.payinfo from 1 for 3 ) = '300' ". - " OR substring($table.payinfo from 1 for 3 ) = '301' ". - " OR substring($table.payinfo from 1 for 3 ) = '302' ". - " OR substring($table.payinfo from 1 for 3 ) = '303' ". - " OR substring($table.payinfo from 1 for 3 ) = '304' ". - " OR substring($table.payinfo from 1 for 3 ) = '305' ". - " OR substring($table.payinfo from 1 for 4 ) = '3095' ". - " OR substring($table.payinfo from 1 for 2 ) = '36' ". - " OR substring($table.payinfo from 1 for 2 ) = '38' ". - " OR substring($table.payinfo from 1 for 2 ) = '39' ". - " OR substring($table.payinfo from 1 for 3 ) = '644' ". - " OR substring($table.payinfo from 1 for 3 ) = '645' ". - " OR substring($table.payinfo from 1 for 3 ) = '646' ". - " OR substring($table.payinfo from 1 for 3 ) = '647' ". - " OR substring($table.payinfo from 1 for 3 ) = '648' ". - " OR substring($table.payinfo from 1 for 3 ) = '649' ". - ( $country =~ /^(US|CA)$/ - ?" OR substring($table.payinfo from 1 for 4 ) = '3528' ". # JCB cards in the 3528-3589 range identified as Discover inside US/CA - " OR substring($table.payinfo from 1 for 4 ) = '3529' ". - " OR substring($table.payinfo from 1 for 3 ) = '353' ". - " OR substring($table.payinfo from 1 for 3 ) = '354' ". - " OR substring($table.payinfo from 1 for 3 ) = '355' ". - " OR substring($table.payinfo from 1 for 3 ) = '356' ". - " OR substring($table.payinfo from 1 for 3 ) = '357' ". - " OR substring($table.payinfo from 1 for 3 ) = '358' " - :"" - ). - " OR substring($table.payinfo from 1 for 3 ) = '622' ". #China Union Pay processed as Discover outside CN - " ) "; - - } elsif ( $cardtype eq 'Maestro' ) { - - $search = - " ( substring($table.payinfo from 1 for 2 ) = '63' ". - " OR substring($table.payinfo from 1 for 2 ) = '67' ". - " OR substring($table.payinfo from 1 for 6 ) = '564182' ". - " OR substring($table.payinfo from 1 for 4 ) = '4936' ". - " OR substring($table.payinfo from 1 for 6 ) ". - " $similar_to '49030[2-9]' ". - " OR substring($table.payinfo from 1 for 6 ) ". - " $similar_to '49033[5-9]' ". - " OR substring($table.payinfo from 1 for 6 ) ". - " $similar_to '49110[1-2]' ". - " OR substring($table.payinfo from 1 for 6 ) ". - " $similar_to '49117[4-9]' ". - " OR substring($table.payinfo from 1 for 6 ) ". - " $similar_to '49118[1-2]' ". - " ) "; - - } elsif ( $cardtype eq 'Tokenized' ) { - - $search = " substring($table.payinfo from 1 for 2 ) = '99' "; - - } else { - die "unknown card type $cardtype"; - } - - my $masksearch = $search; - $masksearch =~ s/$table\.payinfo/$table.paymask/gi; - - $payby_search = "( $payby_search AND ( $search OR ( $table.paymask IS NOT NULL AND $masksearch ) ) )"; + foreach my $payby_string ( $cgi->param('payby') ) { + + my $payby_search; + + my ($payby, $subtype) = split('-', $payby_string); + # make sure it exists and is a transaction type + if ( FS::payby->payment_payby2longname($payby) ) { + $payby_search = "$table.payby = " . dbh->quote($payby); + } else { + die "illegal payby $payby_string"; + } + + if ( $subtype ) { + + my $in_cardtype = $cardtype_of{$subtype} + or die "unknown card type $subtype"; + $payby_search .= " AND $table.paycardtype IN($in_cardtype)"; } diff --git a/httemplate/search/elements/gmap.html b/httemplate/search/elements/gmap.html index 8b070ebf9..b7d135dd6 100644 --- a/httemplate/search/elements/gmap.html +++ b/httemplate/search/elements/gmap.html @@ -1,5 +1,6 @@ <%args> @features +@overlays </%args> <%doc> Generic Google Maps front end. @@ -24,6 +25,14 @@ Generic Google Maps front end. } }, # end of feature ], + overlays => [ + { url => 'https://localhost/freeside/view/sector_map-png.html?102', + west => -130.0, + east => -128.0, + south => 10.0, + north => 12.0, + }, # make a ground overlay + ], &> </%doc> @@ -54,6 +63,7 @@ body { height: 100%; margin: 0px; padding: 0px } <script type="text/javascript"> var data_geojson = <% encode_json($tree) %>; +var data_overlays = <% encode_json(\@overlays) %>; var baseStyle = { clickable: true, @@ -75,6 +85,7 @@ var featureStyle = function(feature) { }; var map; +var overlays = []; function initMap() { var canvas = $('#map_canvas'); map = new google.maps.Map(canvas[0], { zoom: 6 }); @@ -116,6 +127,15 @@ function initMap() { } }); // addListener() + + data_overlays.forEach(function(x) { + var url = x.url; + delete x.url; + var overlay = new google.maps.GroundOverlay( url, x ); + overlay.setMap(map); + overlay.setOpacity(0.4); + overlays.push(overlay); + }); } $().ready( initMap ); diff --git a/httemplate/search/elements/grouped-search/core b/httemplate/search/elements/grouped-search/core index ffa8cee39..3d38a8c7e 100644 --- a/httemplate/search/elements/grouped-search/core +++ b/httemplate/search/elements/grouped-search/core @@ -131,12 +131,14 @@ for my $i (0 .. scalar(@groups) - 1) { } if ( $opt{show_combined} ) { - # set up group 0 as a combined view - unshift @groups, $totals; - unshift @group_labels, 'All ' . PL($opt{name_singular}) . - ' (' . $totals->num_rows . ')'; - unshift @group_footers, []; # the total footer will suffice - unshift @queries, $base_query->clone; + if ( @groups > 1 ) { + # set up group 0 as a combined view + unshift @groups, $totals; + unshift @group_labels, 'All ' . PL($opt{name_singular}) . + ' (' . $totals->num_rows . ')'; + unshift @group_footers, []; # the total footer will suffice + unshift @queries, $base_query->clone; + } } my @total_footer; diff --git a/httemplate/search/elements/grouped-search/html b/httemplate/search/elements/grouped-search/html index df1471a52..293da338f 100644 --- a/httemplate/search/elements/grouped-search/html +++ b/httemplate/search/elements/grouped-search/html @@ -67,8 +67,10 @@ if ( $group->num_rows > scalar(@rows) ) { # set up tab bar my @menubar; -for (my $i = 0; $i < $group_info->{num}; $i++) { - push @menubar, $group_info->{group_labels}[$i], ";group=$i"; +if ($group_info->{num} > 1) { + for (my $i = 0; $i < $group_info->{num}; $i++) { + push @menubar, $group_info->{group_labels}[$i], ";group=$i"; + } } # not enabled yet; if we need this at some point, enable it on a per-report diff --git a/httemplate/search/elements/report_cust_pay_or_refund.html b/httemplate/search/elements/report_cust_pay_or_refund.html index 730db68e8..806746a23 100644 --- a/httemplate/search/elements/report_cust_pay_or_refund.html +++ b/httemplate/search/elements/report_cust_pay_or_refund.html @@ -151,6 +151,12 @@ Examples: 'value' => 1, &> + <& /elements/tr-checkbox.html, + 'label' => emt('Include order number'), + 'field' => 'show_order_number', + 'value' => 1, + &> + </TABLE> % } diff --git a/httemplate/search/elements/search.html b/httemplate/search/elements/search.html index a279f5327..b6ee7b373 100644 --- a/httemplate/search/elements/search.html +++ b/httemplate/search/elements/search.html @@ -135,8 +135,11 @@ Example: # sort, link & display properties for fields - 'sort_fields' => [], #optional list of field names or SQL expressions for - # sorts + 'sort_fields' => [], #optional list of field names or SQL expressions for sorts + + 'order_by_sql' => { #to keep complex SQL expressions out of cgi order_by value, + 'fieldname' => 'sql snippet', # maps fields/sort_fields values to sql snippets + } #listref - each item is the empty string, # or a listref of link and method name to append, @@ -406,6 +409,12 @@ $order_by = $cgi->param('order_by') if $cgi->param('order_by'); my $header = [ map { ref($_) ? $_->{'label'} : $_ } @{$opt{header}} ]; my $rows; +my ($order_by_key,$order_by_desc) = ($order_by =~ /^\s*(.*?)(\s+DESC)?\s*$/i); +$opt{'order_by_sql'} ||= {}; +$order_by_desc ||= ''; +$order_by = $opt{'order_by_sql'}{$order_by_key} . $order_by_desc + if $opt{'order_by_sql'}{$order_by_key}; + if ( ref $query ) { my @query; if (ref($query) eq 'HASH') { diff --git a/httemplate/search/log.html b/httemplate/search/log.html index b607f505d..5b330f899 100644 --- a/httemplate/search/log.html +++ b/httemplate/search/log.html @@ -81,15 +81,15 @@ a:visited {text-decoration: none} <TD>Level <& /elements/select.html, field => 'min_level', - options => [ 0..7 ], - labels => { map {$_ => $FS::Log::LEVELS[$_]} 0..7 }, + options => [ &FS::Log::levelnums ], + labels => { &FS::Log::levelmap }, curr_value => $cgi->param('min_level'), &> to <& /elements/select.html, field => 'max_level', - options => [ 0..7 ], - labels => { map {$_ => $FS::Log::LEVELS[$_]} 0..7 }, + options => [ &FS::Log::levelnums ], + labels => { &FS::Log::levelmap }, curr_value => $cgi->param('max_level'), &> </TD> @@ -101,6 +101,12 @@ a:visited {text-decoration: none} labels => { map {$_, $_} @contexts }, curr_value => ($cgi->param('context') || ''), &> + <BR><& /elements/checkbox.html, + 'field' => 'context_height', + 'postfix' => 'Only match most specific context', + 'value' => 1, + 'curr_value' => scalar($cgi->param('context_height')), + &> </TD> </TR> <TR> @@ -122,7 +128,7 @@ a:visited {text-decoration: none} <%once> my $date_sub = sub { time2str('%Y-%m-%d %T', $_[0]->_date) }; -my $level_sub = sub { $FS::Log::LEVELS[$_[0]->level] }; +my $level_sub = sub { $FS::Log::LEVELS{$_[0]->level} }; my $context_sub = sub { my $log = shift; @@ -185,18 +191,15 @@ my $object_link_sub = sub { } }; -my @colors = ( - '404040', #debug - '0000aa', #info - '00aa00', #notice - 'aa0066', #warning - '000000', #error - 'aa0000', #critical - 'ff0000', #alert - 'ff0000', #emergency +my %colors = ( + 0 => '404040', #debug, gray + 1 => '000000', #info, black + 3 => '0000aa', #warning, blue + 4 => 'aa0066', #error, purple + 5 => 'ff0000', #critical, red ); -my $color_sub = sub { $colors[ $_[0]->level ]; }; +my $color_sub = sub { $colors{ $_[0]->level }; }; my @contexts = ('', sort FS::log_context->contexts); </%once> @@ -206,15 +209,15 @@ die "access denied" unless $curuser->access_right([ 'View system logs', 'Configuration' ]); my @menubar = (); -push @menubar, qq(<A HREF="${fsurl}browse/log_email.html" STYLE="text-decoration: underline;">Configure conditions for sending email when logging</A>), +push @menubar, qq(<A HREF="${fsurl}browse/log_email.html" STYLE="text-decoration: underline;">Configure conditions for sending email when logging</A>); $cgi->param('min_level', 0) unless defined($cgi->param('min_level')); -$cgi->param('max_level', 7) unless defined($cgi->param('max_level')); +$cgi->param('max_level', 5) unless defined($cgi->param('max_level')); my %search = (); $search{'date'} = [ FS::UI::Web::parse_beginning_ending($cgi) ]; $search{'level'} = [ $cgi->param('min_level'), $cgi->param('max_level') ]; -foreach my $param (qw(agentnum context tablename tablenum custnum message)) { +foreach my $param (qw(agentnum context context_height tablename tablenum custnum message)) { if ( $cgi->param($param) ) { $search{$param} = $cgi->param($param); } diff --git a/httemplate/search/report_agent_credit_payment.html b/httemplate/search/report_agent_credit_payment.html new file mode 100755 index 000000000..57fc6a79c --- /dev/null +++ b/httemplate/search/report_agent_credit_payment.html @@ -0,0 +1,30 @@ +<& /elements/header.html, 'Package Agent Credits and Payments' &> + +<P>Shows agent commission credits, and payments applied to invoices for packages that triggered those credits.</P> + +<FORM ACTION="agent_credit_payment.html" METHOD="GET"> + + <TABLE BGCOLOR="#cccccc" CELLSPACING=0> + + <& /elements/tr-select-agent.html, + 'curr_value' => scalar( $cgi->param('agentnum') ), + 'label' => 'Agent ', + 'disable_empty' => 0, + &> + + <& /elements/tr-input-beginning_ending.html &> + + </TABLE> + +<BR> +<INPUT TYPE="submit" VALUE="Get Report"> + +</FORM> + +<% include('/elements/footer.html') %> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Financial reports'); + +</%init> diff --git a/httemplate/search/report_cust_bill_pay_pkg.html b/httemplate/search/report_cust_bill_pay_pkg.html index 2347bab6d..bdcd1549e 100644 --- a/httemplate/search/report_cust_bill_pay_pkg.html +++ b/httemplate/search/report_cust_bill_pay_pkg.html @@ -41,6 +41,13 @@ field => 'paid', &> + <& /elements/tr-checkbox.html, + 'label' => emt('Display order number'), + 'field' => 'show_order_number', + 'value' => 1, + 'cell_style' => 'font-weight: normal', #for consistency + &> + <!-- <TR> <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="nottax" VALUE="Y" onClick="nottax_changed(this)" onChange="nottax_change(thid)"></TD> diff --git a/httemplate/search/report_cust_bill_void.html b/httemplate/search/report_cust_bill_void.html index 91209aeba..65cce5fff 100644 --- a/httemplate/search/report_cust_bill_void.html +++ b/httemplate/search/report_cust_bill_void.html @@ -24,7 +24,7 @@ label => mt('Customer Class'), field => 'cust_classnum', multiple => 1, - 'pre_options' => [ '' => emt('(none)') ], + 'pre_options' => [ '0' => emt('(none)') ], 'all_selected' => 1, &> diff --git a/httemplate/search/report_cust_event.html b/httemplate/search/report_cust_event.html index 0dd98d479..7aa4ff9d7 100644 --- a/httemplate/search/report_cust_event.html +++ b/httemplate/search/report_cust_event.html @@ -5,7 +5,6 @@ %> <FORM ACTION="cust_event.html" METHOD="GET"> - <INPUT TYPE="hidden" NAME="failed" VALUE="<% $cgi->param('failed') ? 1 : 0 %>"> <TABLE BGCOLOR="#cccccc" CELLSPACING=0> <TR> @@ -15,7 +14,8 @@ <% include( '/elements/tr-select-agent.html', 'disable_empty'=>0 ) %> <% include( '/elements/tr-select-cust_main-status.html', - 'label' => 'Status' + 'label' => 'Customer status', + # this field is just called 'status' ) %> @@ -26,6 +26,36 @@ ) %> +% if ( $cgi->param('failed') ) { + <& /elements/tr-fixed.html, + 'label' => 'Event status', + 'field' => 'event_status', + 'curr_value' => 'failed', + 'formatted_value' => 'Failed', + &> +% } else { + +% # 'initial' is not on here, since nobody needs to see it. also, +% # 'done_Y' = "done, and no_action is null, and statustext is null" +% # 'done_S' = "done, and no_action is null, and statustext is not null" +% # 'done_N' = "done, and no_action = 'Y'". + <& /elements/tr-select.html, + 'label' => 'Event status', + 'field' => 'event_status', + 'multiple' => 1, + 'all_selected' => 1, + 'size' => 5, + 'options' => [ qw( done_Y done_S done_N failed new locked ) ], + 'option_labels' => { done_Y => 'Completed normally', + done_S => 'Completed, with an error', + done_N => 'Completed, no action taken', + failed => 'Failed', + new => 'Not yet processed', + locked => 'Running', + }, + &> +% } + <% include( '/elements/tr-input-beginning_ending.html' ) %> </TABLE> diff --git a/httemplate/search/report_sqlradius_usage-custnum.html b/httemplate/search/report_sqlradius_usage-custnum.html new file mode 100644 index 000000000..a71012dd4 --- /dev/null +++ b/httemplate/search/report_sqlradius_usage-custnum.html @@ -0,0 +1,71 @@ +<& /elements/header-popup.html, mt($title) &> + +<FORM ACTION="sqlradius_usage.html" METHOD="GET" TARGET="_top"> + +<& /elements/hidden.html, + 'field' => 'custnum', + 'value' => $custnum, +&> +<TABLE BGCOLOR="#cccccc" CELLSPACING=0> + +% if ( scalar(@exports) == 1 ) { +<tr><td> +<& /elements/hidden.html, + 'field' => 'exportnum', + 'value' => $exports[0]->exportnum, +&> +</td></tr> +% } else { +<& /elements/tr-select-table.html, + 'label' => 'Export', # kind of non-indicative... + 'table' => 'part_export', + 'name_col' => 'label', + 'value_col' => 'exportnum', + 'records' => \@exports, + 'disable_empty' => 1, +&> +% } +<& /elements/tr-input-beginning_ending.html &> + +</TABLE> + +<BR> +<INPUT TYPE="submit" VALUE="<% mt('Get Report') |h %>"> + +</FORM> + +<& /elements/footer.html &> +<%init> + +my $curuser = $FS::CurrentUser::CurrentUser; +die "access denied" + unless $curuser->access_right('Usage: RADIUS sessions'); + # yes? + +my $title = 'Data Usage Report'; +my $custnum; +if ($cgi->keywords) { + ($custnum) = $cgi->keywords; +} else { + $custnum = $cgi->param('custnum'); +} +$custnum =~ /^(\d+)$/ + or die "illegal custnum $custnum"; +my $cust_main = qsearchs( { + 'table' => 'cust_main', + 'hashref' => { 'custnum' => $custnum }, + 'extra_sql' => ' AND '. $curuser->agentnums_sql, +}); +# get all exports that apply to this customer's services--should be fast, as +# everything here is indexed +my @exports = qsearch({ + 'table' => 'part_export', + 'select' => 'DISTINCT part_export.*', + 'addl_from' => ' JOIN export_svc USING (exportnum) + JOIN cust_svc USING (svcpart) + JOIN cust_pkg USING (pkgnum) ', + 'extra_sql' => ' WHERE cust_pkg.custnum = '.$custnum, +}); +@exports = grep { $_->can('usage_sessions') } @exports; + +</%init> diff --git a/httemplate/search/report_sqlradius_usage.html b/httemplate/search/report_sqlradius_usage.html index e818fb57d..89b60847b 100644 --- a/httemplate/search/report_sqlradius_usage.html +++ b/httemplate/search/report_sqlradius_usage.html @@ -1,3 +1,4 @@ +%# some overlap with report_sqlradius_usage_custnum.html <& /elements/header.html, mt($title) &> <FORM ACTION="sqlradius_usage.html" METHOD="GET"> diff --git a/httemplate/search/report_tax-xls.cgi b/httemplate/search/report_tax-xls.cgi index c914d5adc..30b32e8d8 100755 --- a/httemplate/search/report_tax-xls.cgi +++ b/httemplate/search/report_tax-xls.cgi @@ -13,9 +13,7 @@ my %params = ( beginning => $beginning, ending => $ending, ); -$params{country} = $cgi->param('country'); $params{debug} = $DEBUG; -$params{breakdown} = { map { $_ => 1 } $cgi->param('breakdown') }; my $agentname; if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) { @@ -24,15 +22,38 @@ if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) { $agentname = $agent->agentname; } -# allow anything in here; FS::Report::Tax will treat it as unsafe -if ( length($cgi->param('taxname')) ) { - $params{taxname} = $cgi->param('taxname'); +# credit date behavior: limit by the date of the credit application, or +# the invoice? +if ( $cgi->param('credit_date') eq 'cust_credit_bill' ) { + $params{credit_date} = 'cust_credit_bill'; } else { - die "taxname required"; + $params{credit_date} = 'cust_bill'; +} + +my $all = $cgi->param('all'); +my $report_class; + +if ( $all ) { + $report_class = 'FS::Report::Tax::All'; +} else { + $report_class = 'FS::Report::Tax::ByName'; + $params{country} = $cgi->param('country'); + $params{breakdown} = { map { $_ => 1 } $cgi->param('breakdown') }; + + # allow anything in here; FS::Report::Tax will treat it as unsafe + if ( length($cgi->param('taxname')) ) { + $params{taxname} = $cgi->param('taxname'); + } else { + die "taxname required"; + } +} + +if ($DEBUG) { + warn "REPORT: $report_class\nPARAMS:\n".Dumper(\%params)."\n\n"; } # generate the report -my $report = FS::Report::Tax->report_internal(%params); +my $report = $report_class->report(%params); my @rows = $report->table; # array of hashrefs my %pkgclass_name = map { $_->classnum, $_->classname } qsearch('pkg_class'); diff --git a/httemplate/search/report_tax.cgi b/httemplate/search/report_tax.cgi index bbb3bc199..410fe4603 100644 --- a/httemplate/search/report_tax.cgi +++ b/httemplate/search/report_tax.cgi @@ -78,14 +78,6 @@ TD.rowhead { font-weight: bold; text-align: left; padding: 0px 3px } % # cust_bill_pkg.cgi wants a list of specific taxnums (and package class) % # cust_credit_bill_pkg.html wants a geographic scope (and package class) % my $rowlink = ';taxnum=' . $row->{taxnums}; -% # DON'T EVER USE THIS -% # my $rowregion = ';country=' . $cgi->param('country'); -% # foreach my $loc (qw(state county city district)) { -% # if ( $row->{$loc} ) { -% # $rowregion .= ";$loc=" . uri_escape($row->{$loc}); -% # } -% # } -% # and also the package class, if we're limiting package class % if ( $params{breakdown}->{pkgclass} ) { % $rowlink .= ';classnum=' . ($row->{pkgclass} || 0); % # $rowregion .= ';classnum=' . ($row->{pkgclass} || 0); @@ -96,7 +88,26 @@ TD.rowhead { font-weight: bold; text-align: left; padding: 0px 3px } % } <TR CLASS="row<% $rownum % 2 %>"> % # Row label - <TD CLASS="rowhead"><% $row->{label} |h %></TD> +% # Special: If this report is showing all taxes, link the row label to +% # the detailed tax report for that taxname/country. + <TD CLASS="rowhead"> +% if ( $all ) { +% my $newcgi = CGI->new($cgi); +% $newcgi->delete('all'); +% $newcgi->param('country', $row->{country}); +% $newcgi->param('taxname', $row->{taxname}); +% $newcgi->param('breakdown', qw(city district)); + + <A HREF="<% encode_entities( $newcgi->self_url ) %>"> + <% $row->{label} |h %> + </A> + +% } else { # on the per-taxname report, just show the label with no link + + <% $row->{label} |h %> + +% } + </TD> <TD> % # Total sales <A HREF="<% $saleslink . $rowlink %>"> @@ -167,7 +178,8 @@ TD.rowhead { font-weight: bold; text-align: left; padding: 0px 3px } % } # foreach my $row % # at the end of everything </TBODY> -% if ( $report->{out_sales} > 0 ) { +% # the all-taxes report doesn't have "out of region" +% if ( !$all and $report->{out_sales} > 0 ) { <TBODY CLASS="total" STYLE="background-color: #cccccc; line-height: 3"> <TR> <TD CLASS="rowhead"> @@ -175,7 +187,7 @@ TD.rowhead { font-weight: bold; text-align: left; padding: 0px 3px } </TD> <TD STYLE="text-align: right"> <A HREF="<% $saleslink %>;out=1;taxname=<% encode_entities($params{'taxname'}) %>"> - <% $money_sprintf->( $report->{out_sales } ) %> + <% $money_sprintf->( $report->{out_sales} ) %> </A> </TD> <TD COLSPAN=0></TD> @@ -254,7 +266,7 @@ TD.rowhead { font-weight: bold; text-align: left; padding: 0px 3px } % $prev_row = $row; % } # foreach my $row % # "out of taxable region" for credits (there is a need for it) -% if ( $report->{out_credit} > 0 ) { +% if ( !$all and $report->{out_credit} > 0 ) { % my $creditlink = "cust_credit_bill_pkg.html?out=1;$dateagentlink"; % if ( $params{'credit_date'} eq 'cust_credit_bill' ) { % $creditlink =~ s/begin/credit_begin/; @@ -268,7 +280,7 @@ TD.rowhead { font-weight: bold; text-align: left; padding: 0px 3px } </TD> <TD STYLE="text-align: right"> <A HREF="<% $creditlink %>"> - <% $money_sprintf->( $report->{out_credit } ) %> + <% $money_sprintf->( $report->{out_credit} ) %> </A> </TD> <TD COLSPAN=0></TD> @@ -295,33 +307,48 @@ my %params = ( beginning => $beginning, ending => $ending, ); -$params{country} = $cgi->param('country'); $params{debug} = $DEBUG; -$params{breakdown} = { map { $_ => 1 } $cgi->param('breakdown') }; - my $agentname; + +# filter by agentnum if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) { my $agent = FS::agent->by_key($1) or die "unknown agentnum $1"; $params{agentnum} = $1; $agentname = $agent->agentname; } -# allow anything in here; FS::Report::Tax will treat it as unsafe -if ( length($cgi->param('taxname')) ) { - $params{taxname} = $cgi->param('taxname'); -} else { - die "taxname required"; -} - +# credit date behavior: limit by the date of the credit application, or +# the invoice? if ( $cgi->param('credit_date') eq 'cust_credit_bill' ) { $params{credit_date} = 'cust_credit_bill'; } else { $params{credit_date} = 'cust_bill'; } -warn "PARAMS:\n".Dumper(\%params)."\n\n" if $DEBUG; +my $all = $cgi->param('all'); +my $report_class; + +if ( $all ) { + # then show the master report, no country, no taxname, no breakdown + $report_class = 'FS::Report::Tax::All'; +} else { + $report_class = 'FS::Report::Tax::ByName'; + $params{country} = $cgi->param('country'); + $params{breakdown} = { map { $_ => 1 } $cgi->param('breakdown') }; + + # allow anything in here; FS::Report::Tax will treat it as unsafe + if ( length($cgi->param('taxname')) ) { + $params{taxname} = $cgi->param('taxname'); + } else { + die "taxname required"; + } +} + +if ($DEBUG) { + warn "REPORT: $report_class\nPARAMS:\n".Dumper(\%params)."\n\n"; +} -my $report = FS::Report::Tax->report_internal(%params); +my $report = $report_class->report(%params); my @rows = $report->table; # array of hashrefs my $money_char = $conf->config('money_char') || '$'; diff --git a/httemplate/search/report_tax.html b/httemplate/search/report_tax.html index 8d8d1084c..f920adbac 100755 --- a/httemplate/search/report_tax.html +++ b/httemplate/search/report_tax.html @@ -8,6 +8,20 @@ <& /elements/tr-input-beginning_ending.html &> + <tr> + <td></td> + <td colspan=2 style="font-weight: bold"> + <& /elements/radio.html, + 'field' => 'all', + 'value' => 1, + 'curr_value' => 1, + &> All taxes + <& /elements/radio.html, + 'field' => 'all', + 'value' => 0, + &> A specific tax + </td> + </tr> <& /elements/tr-select.html, 'label' => 'Country', 'field' => 'country', @@ -49,6 +63,21 @@ </FORM> +<script> +$(document).ready(function() { + $('[name=all]').on('change', function(ev) { + // disable country/taxname/breakdown if 'all' = 1 + if (this.checked) { + var disabled = (this.value == 1); + $('[name=country').prop('disabled', disabled); + $('[name=taxname').prop('disabled', disabled); + $('[name=breakdown').prop('disabled', disabled); + } + }); + $('[name=all]').change(); +}); +</script> + <% include('/elements/footer.html') %> <%init> diff --git a/httemplate/search/report_unprovisioned_services.html b/httemplate/search/report_unprovisioned_services.html index fe4d46bf7..54181bb20 100755 --- a/httemplate/search/report_unprovisioned_services.html +++ b/httemplate/search/report_unprovisioned_services.html @@ -13,6 +13,7 @@ 'field' => 'svcpart', 'label' => 'Services', 'multiple' => 1, + 'size' => 20, ) %> diff --git a/httemplate/search/sector.html b/httemplate/search/sector.html new file mode 100644 index 000000000..037df10ea --- /dev/null +++ b/httemplate/search/sector.html @@ -0,0 +1,104 @@ +<& /elements/header.html, { + 'title' => 'Sector coverage maps', + } +&> +<style> + a.createmap { + font-weight: bold; + color: blue; + } + a.viewmap { + font-weight: bold; + color: green; + } + .grid th { + padding-left: 3px; + padding-right: 3px; + padding-bottom: 2px; + border: none; + empty-cells: show; + font-size:90%; + border-bottom: 1px solid #999999; + } + .grid td { + padding-left: 3px; + padding-right: 3px; + padding-bottom: 2px; + border: none; + empty-cells: show; + } +</style> +<table class="grid" style="border-spacing: 0px"> + <thead> + <tr> + <th>Tower / sector</th> + <th colspan=3> + </tr> + </thead> + <tbody> +% my $row = 0; +% foreach my $sector (@sectors) { +% my $sectornum = $sector->sectornum; + <tr class="row<% $row % 2 %>"> + <td> + <a href="<% $fsurl %>edit/tower.html?<% $sector->towernum |h %>"> + <% $sector->description |h %> + </a> + </td> + +% my @need_fields = $sector->need_fields_for_coverage; +% if ( @need_fields ) { + <td>Need fields:</td> + <td> + <% join('<br>', @need_fields) %> + </td> +% } else { + <td colspan="2" style="text-align: center"> +% my $text = 'Create map'; +% if ( length($sector->image) > 0 ) { +% $text = 'Reprocess'; +% } + <form name="create_<% $sectornum |h %>"> + <input type="hidden" name="sectornum" value="<% $sectornum |h %>"> + <& /elements/progress-init.html, + 'create_'.$sectornum, + [ 'sectornum' ], + $fsurl.'misc/sector-create_map.html', + { 'message' => 'Map generated', + 'url' => $cgi->self_url }, + "sector$sectornum" + &> + <a class="createmap" href="#" onclick="sector<% $sectornum %>process()"> + <% $text %> + </a> +% } + </td> + <td> +% if ( length($sector->image) > 0 ) { + <a class="viewmap" href="<% $fsurl %>search/svc_broadband-map.html?sectornum=<% $sectornum %>"> + View map—<% $sector->margin %>dB margin + </a> +% } + </td> + </tr> +% $row++; +% } # foreach $sector + </tbody> +</table> +<& /elements/footer.html &> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); + +my $query = { + table => 'tower_sector', + select => 'tower_sector.*, + tower.latitude, tower.longitude, tower.color, tower.towername', + extra_sql => ' WHERE tower.disabled is null', + addl_from => ' JOIN tower USING (towernum)', + order_by => ' ORDER BY towername, sectorname', +}; + +my @sectors = qsearch($query); +</%init> diff --git a/httemplate/search/sqlradius_usage.html b/httemplate/search/sqlradius_usage.html index 29ef4c0e8..08f9b6ba1 100644 --- a/httemplate/search/sqlradius_usage.html +++ b/httemplate/search/sqlradius_usage.html @@ -39,6 +39,7 @@ @svc_fields, @svc_usage, ], + 'order_by_sql' => $order_by_sql, 'links' => [ #( map { $_ ne 'Cust. Status' ? $link_cust : '' } # FS::UI::Web::cust_header() ), $link_cust, @@ -59,8 +60,8 @@ my %opt = @_; -die "access denied" unless - $FS::CurrentUser::CurrentUser->access_right('List services'); +my $curuser = $FS::CurrentUser::CurrentUser; +die "access denied" unless $curuser->access_right('List services'); my $title = 'Data Usage Report - '; my $agentnum; @@ -92,6 +93,40 @@ if ( $ending == 4294967295 ) { $title .= time2str('%h %o %Y', $ending); } +# can also show a specific customer / service. the main query will handle +# agent restrictions, but we need a list of the services to ask the export +# for usage data. +my ($cust_main, @svc_x); +if ( $cgi->param('custnum') =~ /^(\d+)$/ ) { + $cust_main = qsearchs( { + 'table' => 'cust_main', + 'hashref' => { 'custnum' => $1 }, + 'extra_sql' => ' AND '. $curuser->agentnums_sql, + }); + die "Customer not found!" unless $cust_main; + # then only report on this agent + $agentnum = $cust_main->agentnum; + @include_agents = (); + # and announce that we're doing it + $title .= ' - ' . $cust_main->name_short; + + # yes, we'll query the database once for each service the customer has, + # even non-radacct'd services. probably less bad than a single query that + # pulls records for every service for every customer. + foreach my $cust_pkg ($cust_main->all_pkgs) { + foreach my $cust_svc ($cust_pkg->cust_svc) { + push @svc_x, $cust_svc->svc_x; + } + } +} +foreach ($cgi->param('svcnum')) { + if (/^(\d+)$/) { + my $cust_svc = FS::cust_svc->by_key($1) + or die "service #$1 not found."; # or continue? + push @svc_x, $cust_svc->svc_x; + } +} + my $export; my %usage_by_username; if ( exists($opt{usage_by_username}) ) { @@ -109,16 +144,28 @@ if ( exists($opt{usage_by_username}) ) { or die "exportnum ".$export->exportnum." is type ".$export->exporttype. ", not sqlradius"; - my $usage = $export->usage_sessions( { + my %usage_param = ( stoptime_start => $beginning, stoptime_end => $ending, summarize => 1 - } ); - # arrayref of hashrefs of + ); + # usage_sessions() returns an arrayref of hashrefs of # (username, acctsessiontime, acctinputoctets, acctoutputoctets) # (XXX needs to include 'realm' for sqlradius_withdomain) - # rearrange to be indexed by username. + my $usage; + if ( @svc_x ) { + # then query once per service + $usage = []; + foreach my $svc ( @svc_x ) { + $usage_param{'svc'} = $svc; + push @$usage, @{ $export->usage_sessions(\%usage_param) }; + } + } else { + # one query, get everyone's data + my $usage = $export->usage_sessions(\%usage_param); + } + # rearrange to be indexed by username. foreach (@$usage) { my $username = $_->{'username'}; my @row = ( @@ -171,10 +218,22 @@ my @svc_fields = @{ $svc_fields{$svcdb} }; my %search_hash = ( 'agentnum' => $agentnum, 'exportnum' => $export->exportnum ); +if ($cust_main) { + $search_hash{'custnum'} = $cust_main->custnum; +} +if (@svc_x) { + $search_hash{'svcnum'} = [ map { $_->get('svcnum') } @svc_x ]; +} + my $sql_query = $class->search(\%search_hash); $sql_query->{'select'} .= ', part_pkg.pkg'; $sql_query->{'addl_from'} .= ' LEFT JOIN part_pkg USING (pkgpart)'; +if ( @svc_x ) { + my $svcnums = join(',', map { $_->get('svcnum') } @svc_x); + $sql_query->{'extra_sql'} .= ' AND svcnum IN('.$svcnums.')'; +} + my $link_svc = [ $p.'view/cust_svc.cgi?', 'svcnum' ]; my $link_cust = [ $p.'view/cust_main.cgi?', 'custnum' ]; @@ -182,9 +241,10 @@ my $link_cust = [ $p.'view/cust_main.cgi?', 'custnum' ]; # columns between the customer name and the usage fields my $skip_cols = 1 + scalar(@svc_header); +my $num_rows = FS::Record->scalar_sql($sql_query->{count_query}); my @footer = ( '', - FS::Record->scalar_sql($sql_query->{count_query}) . ' services', + emt('[quant,_1,service]', $num_rows), ('') x $skip_cols, map { my $i = $_; @@ -198,4 +258,23 @@ sub bytes_to_gb { $_[0] ? sprintf('%.3f', $_[0] / (1024*1024*1024.0)) : ''; } +my $conf = new FS::Conf; +my $order_by_sql = { + 'name' => "CASE WHEN cust_main.company IS NOT NULL + AND cust_main.company != '' + THEN CONCAT(cust_main.company,' (',cust_main.last,', ',cust_main.first,')') + ELSE CONCAT(cust_main.last,', ',cust_main.first) + END", + 'display_custnum' => $conf->exists('cust_main-default_agent_custid') + ? "CASE WHEN cust_main.agent_custid IS NOT NULL + AND cust_main.agent_custid != '' + AND cust_main.agent_custid ". regexp_sql. " '^[0-9]+\$' + THEN CAST(cust_main.agent_custid AS BIGINT) + ELSE cust_main.custnum + END" + : "custnum", +}; + +#warn Dumper \%usage_by_username; + </%init> diff --git a/httemplate/search/svc_acct.cgi b/httemplate/search/svc_acct.cgi index 58764f881..ecf37b42e 100755 --- a/httemplate/search/svc_acct.cgi +++ b/httemplate/search/svc_acct.cgi @@ -59,6 +59,8 @@ my $curuser = $FS::CurrentUser::CurrentUser; die "access denied" unless $curuser->access_right('List services'); +my %cust_pkg_cache; + my $link = [ "${p}view/svc_acct.cgi?", 'svcnum' ]; my $link_cust = sub { my $svc_acct = shift; @@ -130,6 +132,7 @@ for (qw( towernum sectornum )) { my $timepermonth = ''; my $orderby = 'ORDER BY svcnum'; +my $addl_from = ''; if ( $cgi->param('magic') =~ /^(all|unlinked)$/ ) { $search_hash{'unlinked'} = 1 @@ -281,6 +284,9 @@ if ( $cgi->param('magic') =~ /^(all|unlinked)$/ ) { } elsif ( $cgi->param('svcpart') ) { $orderby = "ORDER BY uid"; #$orderby = "ORDER BY svcnum"; + if ( defined($cgi->param('cancelled')) ) { + $search_hash{'cancelled'} = $cgi->param('cancelled') ? 1 : 0; + } } else { $orderby = "ORDER BY uid"; @@ -347,6 +353,22 @@ foreach my $pkg_field ( @pkg_fields ) { } +push @header, emt('Pkg. Status'); +push @fields, sub { + $cust_pkg_cache{$_[0]->svcnum} ||= $_[0]->cust_svc->cust_pkg; + return '' unless $cust_pkg_cache{$_[0]->svcnum}; + $cust_pkg_cache{$_[0]->svcnum}->ucfirst_status; +}; +push @links, ''; +$align .= 'r'; +push @color, sub { + $cust_pkg_cache{$_[0]->svcnum} ||= $_[0]->cust_svc->cust_pkg; + return '' unless $cust_pkg_cache{$_[0]->svcnum}; + my $c = FS::cust_pkg::statuscolors; + $c->{$cust_pkg_cache{$_[0]->svcnum}->status }; +}; +push @style, 'b'; + push @header, FS::UI::Web::cust_header($cgi->param('cust_fields')); push @fields, \&FS::UI::Web::cust_fields, push @links, map { $_ ne 'Cust. Status' ? $link_cust : '' } @@ -357,6 +379,7 @@ push @style, FS::UI::Web::cust_styles(); $search_hash{'order_by'} = $orderby; $search_hash{'where'} = \@extra_sql; +$search_hash{'addl_from'} = $addl_from; my $sql_query = FS::svc_acct->search(\%search_hash); my $count_query = delete($sql_query->{'count_query'}); diff --git a/httemplate/search/svc_broadband-map.html b/httemplate/search/svc_broadband-map.html index 4c660b016..fe3c0950b 100755 --- a/httemplate/search/svc_broadband-map.html +++ b/httemplate/search/svc_broadband-map.html @@ -1,6 +1,6 @@ <& /elements/header.html, 'Broadband Search Results' &> -<& elements/gmap.html, features => \@features &> +<& elements/gmap.html, features => \@features, overlays => \@overlays &> <& /elements/footer.html &> <%init> @@ -76,7 +76,6 @@ foreach my $svc_broadband (@rows) { } - my $tower = $towers{$towernum}; if ( $tower->latitude and $tower->longitude ) { push @features, { @@ -146,6 +145,19 @@ foreach my $tower (values(%towers)) { }; } +my @overlays; +foreach my $sector (values %sectors) { + if ( length($sector->image) > 0 ) { + my $o = { + url => $fsurl.'view/sector_map-png.cgi?' . $sector->sectornum + }; + foreach (qw(south north west east)) { + $o->{$_} = $sector->get($_) + 0; + } + push @overlays, $o; + }; +}; + </%init> <%def .svc_broadband> % my $svc = shift; diff --git a/httemplate/search/svc_broadband.cgi b/httemplate/search/svc_broadband.cgi index 6bf4f0850..0e52d5fb6 100755 --- a/httemplate/search/svc_broadband.cgi +++ b/httemplate/search/svc_broadband.cgi @@ -10,6 +10,8 @@ 'Router', @tower_header, 'IP Address', + @header_pkg, + emt('Pkg. Status'), FS::UI::Web::cust_header($cgi->param('cust_fields')), ], 'fields' => [ 'svcnum', @@ -20,25 +22,43 @@ }, @tower_fields, 'ip_addr', + @fields_pkg, + sub { + $cust_pkg_cache{$_[0]->svcnum} ||= $_[0]->cust_svc->cust_pkg; + return '' unless $cust_pkg_cache{$_[0]->svcnum}; + $cust_pkg_cache{$_[0]->svcnum}->ucfirst_status + }, \&FS::UI::Web::cust_fields, ], 'links' => [ $link, $link, '', #$link_router, (map '', @tower_fields), - $link, + $link, # ip_addr + @blank_pkg, + '', # pkg status ( map { $_ ne 'Cust. Status' ? $link_cust : '' } FS::UI::Web::cust_header($cgi->param('cust_fields')) ), ], - 'align' => 'rll'.('r' x @tower_fields).'r'. + 'align' => 'rll'.('r' x @tower_fields). + 'r'. # ip_addr + $align_pkg. + 'r'. # pkg status FS::UI::Web::cust_aligns(), 'color' => [ '', '', '', (map '', @tower_fields), - '', + '', # ip_addr + @blank_pkg, + sub { + $cust_pkg_cache{$_[0]->svcnum} ||= $_[0]->cust_svc->cust_pkg; + return '' unless $cust_pkg_cache{$_[0]->svcnum}; + my $c = FS::cust_pkg::statuscolors; + $c->{$cust_pkg_cache{$_[0]->svcnum}->status }; + }, # pkg status FS::UI::Web::cust_colors(), ], 'style' => [ @@ -46,7 +66,9 @@ '', '', (map '', @tower_fields), - '', + '', # ip_addr + @blank_pkg, + 'b', # pkg status FS::UI::Web::cust_styles(), ], @@ -56,6 +78,8 @@ die "access denied" unless $FS::CurrentUser::CurrentUser->access_right('List services'); +my %cust_pkg_cache; + my $conf = new FS::Conf; my %search_hash; @@ -68,6 +92,9 @@ if ( $cgi->param('magic') eq 'unlinked' ) { foreach (qw(pkgpart routernum towernum sectornum)) { $search_hash{$_} = [ $cgi->param($_) ] if $cgi->param($_); } + if ( defined($cgi->param('cancelled')) ) { + $search_hash{'cancelled'} = $cgi->param('cancelled') ? 1 : 0; + } } if ( $cgi->param('sortby') =~ /^(\w+)$/ ) { @@ -110,4 +137,25 @@ $html_init .= ' | ' . $fsurl . 'search/svc_broadband-map.html?' . $cgi->query_string . '">' . emt('View a map of these services') . '</a>'; +my (@header_pkg,@fields_pkg,@blank_pkg); +my $align_pkg = ''; +#false laziness with search/svc_acct.cgi +$cgi->param('cust_pkg_fields') =~ /^([\w\,]*)$/ or die "bad cust_pkg_fields"; +my @pkg_fields = split(',', $1); +foreach my $pkg_field ( @pkg_fields ) { + ( my $header = ucfirst($pkg_field) ) =~ s/_/ /; #:/ + push @header_pkg, $header; + + #not the most efficient to do it every field, but this is of niche use. so far + push @fields_pkg, sub { my $svc_x = shift; + my $cust_pkg = $svc_x->cust_svc->cust_pkg or return ''; + my $value = $cust_pkg->get($pkg_field);#closures help alot + $value ? time2str('%b %d %Y', $value ) : ''; + }; + + push @blank_pkg, ''; + $align_pkg .= 'c'; +} + + </%init> diff --git a/httemplate/search/svc_circuit.cgi b/httemplate/search/svc_circuit.cgi index 8f05e0488..3a85375ef 100644 --- a/httemplate/search/svc_circuit.cgi +++ b/httemplate/search/svc_circuit.cgi @@ -10,6 +10,7 @@ 'Termination', 'Circuit ID', 'IP Address', + emt('Pkg. Status'), FS::UI::Web::cust_header($cgi->param('cust_fields')), ], 'fields' => [ 'svcnum', @@ -18,6 +19,11 @@ 'termination', 'circuit_id', 'ip_addr', + sub { + $cust_pkg_cache{$_[0]->svcnum} ||= $_[0]->cust_svc->cust_pkg; + return '' unless $cust_pkg_cache{$_[0]->svcnum}; + $cust_pkg_cache{$_[0]->svcnum}->ucfirst_status + }, \&FS::UI::Web::cust_fields, ], 'links' => [ $link, @@ -26,15 +32,23 @@ '', $link, $link, + '', # pkg status FS::UI::Web::cust_links($cgi->param('cust_fields')), ], - 'align' => 'rlllll'. FS::UI::Web::cust_aligns(), + 'align' => 'rlllllr'. FS::UI::Web::cust_aligns(), 'color' => [ ('') x 6, + sub { + $cust_pkg_cache{$_[0]->svcnum} ||= $_[0]->cust_svc->cust_pkg; + return '' unless $cust_pkg_cache{$_[0]->svcnum}; + my $c = FS::cust_pkg::statuscolors; + $c->{$cust_pkg_cache{$_[0]->svcnum}->status }; + }, # pkg status FS::UI::Web::cust_colors(), ], 'style' => [ ('') x 6, + 'b', FS::UI::Web::cust_styles(), ], @@ -44,6 +58,8 @@ die "access denied" unless $FS::CurrentUser::CurrentUser->access_right('List services'); +my %cust_pkg_cache; + my $conf = new FS::Conf; my %search_hash; @@ -56,6 +72,9 @@ if ( $cgi->param('magic') eq 'unlinked' ) { foreach (qw(pkgpart routernum towernum sectornum)) { $search_hash{$_} = [ $cgi->param($_) ] if $cgi->param($_); } + if ( defined($cgi->param('cancelled')) ) { + $search_hash{'cancelled'} = $cgi->param('cancelled') ? 1 : 0; + } } my $query = FS::svc_circuit->search(\%search_hash); diff --git a/httemplate/search/svc_dish.cgi b/httemplate/search/svc_dish.cgi index 1f8cbc395..5c476085a 100755 --- a/httemplate/search/svc_dish.cgi +++ b/httemplate/search/svc_dish.cgi @@ -7,31 +7,45 @@ 'header' => [ '#', 'Service', 'Account #', + emt('Pkg. Status'), FS::UI::Web::cust_header(), ], 'fields' => [ 'svcnum', 'svc', 'acctnum', + sub { + $cust_pkg_cache{$_[0]->svcnum} ||= $_[0]->cust_svc->cust_pkg; + return '' unless $cust_pkg_cache{$_[0]->svcnum}; + $cust_pkg_cache{$_[0]->svcnum}->ucfirst_status + }, \&FS::UI::Web::cust_fields, ], 'links' => [ $link, $link, $link, + '', # pkg status ( map { $_ ne 'Cust. Status' ? $link_cust : '' } FS::UI::Web::cust_header() ), ], - 'align' => 'rll'. FS::UI::Web::cust_aligns(), + 'align' => 'rllr'. FS::UI::Web::cust_aligns(), 'color' => [ '', '', '', + sub { + $cust_pkg_cache{$_[0]->svcnum} ||= $_[0]->cust_svc->cust_pkg; + return '' unless $cust_pkg_cache{$_[0]->svcnum}; + my $c = FS::cust_pkg::statuscolors; + $c->{$cust_pkg_cache{$_[0]->svcnum}->status }; + }, # pkg status FS::UI::Web::cust_colors(), ], 'style' => [ '', '', '', + 'b', FS::UI::Web::cust_styles(), ], @@ -41,6 +55,8 @@ die "access denied" unless $FS::CurrentUser::CurrentUser->access_right('List services'); +my %cust_pkg_cache; + #my $conf = new FS::Conf; my $orderby = 'ORDER BY svcnum'; @@ -56,6 +72,13 @@ if ( $cgi->param('magic') =~ /^(all|unlinked)$/ ) { } } elsif ( $cgi->param('svcpart') =~ /^(\d+)$/ ) { push @extra_sql, "svcpart = $1"; + if (defined($cgi->param('cancelled'))) { + if ($cgi->param('cancelled')) { + push @extra_sql, "cust_pkg.cancel IS NOT NULL"; + } else { + push @extra_sql, "cust_pkg.cancel IS NULL"; + } + } } my $addl_from = ' LEFT JOIN cust_svc USING ( svcnum ) '. diff --git a/httemplate/search/svc_domain.cgi b/httemplate/search/svc_domain.cgi index 56cfa30c8..23eeba6a7 100755 --- a/httemplate/search/svc_domain.cgi +++ b/httemplate/search/svc_domain.cgi @@ -7,31 +7,45 @@ 'header' => [ '#', 'Service', 'Domain', + emt('Pkg. Status'), FS::UI::Web::cust_header(), ], 'fields' => [ 'svcnum', 'svc', 'domain', + sub { + $cust_pkg_cache{$_[0]->svcnum} ||= $_[0]->cust_svc->cust_pkg; + return '' unless $cust_pkg_cache{$_[0]->svcnum}; + $cust_pkg_cache{$_[0]->svcnum}->ucfirst_status + }, \&FS::UI::Web::cust_fields, ], 'links' => [ $link, $link, $link, + '', # pkg status ( map { $_ ne 'Cust. Status' ? $link_cust : '' } FS::UI::Web::cust_header() ), ], - 'align' => 'rll'. FS::UI::Web::cust_aligns(), + 'align' => 'rllr'. FS::UI::Web::cust_aligns(), 'color' => [ '', '', '', + sub { + $cust_pkg_cache{$_[0]->svcnum} ||= $_[0]->cust_svc->cust_pkg; + return '' unless $cust_pkg_cache{$_[0]->svcnum}; + my $c = FS::cust_pkg::statuscolors; + $c->{$cust_pkg_cache{$_[0]->svcnum}->status }; + }, # pkg status FS::UI::Web::cust_colors(), ], 'style' => [ '', '', '', + 'b', FS::UI::Web::cust_styles(), ], @@ -41,6 +55,8 @@ die "access denied" unless $FS::CurrentUser::CurrentUser->access_right('List services'); +my %cust_pkg_cache; + my $conf = new FS::Conf; my $orderby = 'ORDER BY svcnum'; @@ -58,6 +74,13 @@ if ( $cgi->param('magic') =~ /^(all|unlinked)$/ ) { } elsif ( $cgi->param('svcpart') =~ /^(\d+)$/ ) { push @extra_sql, "svcpart = $1"; + if (defined($cgi->param('cancelled'))) { + if ($cgi->param('cancelled')) { + push @extra_sql, "cust_pkg.cancel IS NOT NULL"; + } else { + push @extra_sql, "cust_pkg.cancel IS NULL"; + } + } } else { $cgi->param('domain') =~ /^([\w\-\.]+)$/; $svc_domain{'domain'} = $1; diff --git a/httemplate/search/svc_external.cgi b/httemplate/search/svc_external.cgi index b282939a7..426ac1645 100755 --- a/httemplate/search/svc_external.cgi +++ b/httemplate/search/svc_external.cgi @@ -8,29 +8,42 @@ 'Service', ( FS::Msgcat::_gettext('svc_external-id') || 'External ID' ), ( FS::Msgcat::_gettext('svc_external-title') || 'Title' ), + emt('Pkg. Status'), FS::UI::Web::cust_header(), ], 'fields' => [ 'svcnum', 'svc', 'id', 'title', + sub { + $cust_pkg_cache{$_[0]->svcnum} ||= $_[0]->cust_svc->cust_pkg; + return '' unless $cust_pkg_cache{$_[0]->svcnum}; + $cust_pkg_cache{$_[0]->svcnum}->ucfirst_status + }, \&FS::UI::Web::cust_fields, ], 'links' => [ $link, $link, $link, $link, + '', # pkg status ( map { $_ ne 'Cust. Status' ? $link_cust : '' } FS::UI::Web::cust_header() ), ], - 'align' => 'rlrr'. + 'align' => 'rlrrr'. FS::UI::Web::cust_aligns(), 'color' => [ '', '', '', '', + sub { + $cust_pkg_cache{$_[0]->svcnum} ||= $_[0]->cust_svc->cust_pkg; + return '' unless $cust_pkg_cache{$_[0]->svcnum}; + my $c = FS::cust_pkg::statuscolors; + $c->{$cust_pkg_cache{$_[0]->svcnum}->status }; + }, # pkg status FS::UI::Web::cust_colors(), ], 'style' => [ @@ -38,6 +51,7 @@ '', '', '', + 'b', FS::UI::Web::cust_styles(), ], @@ -47,6 +61,8 @@ die "access denied" unless $FS::CurrentUser::CurrentUser->access_right('List services'); +my %cust_pkg_cache; + my $conf = new FS::Conf; my %svc_external; @@ -69,6 +85,13 @@ if ( $cgi->param('magic') =~ /^(all|unlinked)$/ ) { } elsif ( $cgi->param('svcpart') =~ /^(\d+)$/ ) { push @extra_sql, "svcpart = $1"; + if (defined($cgi->param('cancelled'))) { + if ($cgi->param('cancelled')) { + push @extra_sql, "cust_pkg.cancel IS NOT NULL"; + } else { + push @extra_sql, "cust_pkg.cancel IS NULL"; + } + } } elsif ( $cgi->param('title') =~ /^(.*)$/ ) { diff --git a/httemplate/search/svc_fiber.html b/httemplate/search/svc_fiber.html index 0cb735c96..a07c5f541 100644 --- a/httemplate/search/svc_fiber.html +++ b/httemplate/search/svc_fiber.html @@ -10,6 +10,7 @@ 'ONT', 'Model', 'Serial', + emt('Pkg. Status'), FS::UI::Web::cust_header($cgi->param('cust_fields')), ], 'fields' => [ 'svcnum', @@ -20,6 +21,11 @@ 'ont_id', 'ont_description', 'ont_serial', + sub { + $cust_pkg_cache{$_[0]->svcnum} ||= $_[0]->cust_svc->cust_pkg; + return '' unless $cust_pkg_cache{$_[0]->svcnum}; + $cust_pkg_cache{$_[0]->svcnum}->ucfirst_status + }, \&FS::UI::Web::cust_fields, ], 'links' => [ $link, @@ -28,15 +34,23 @@ $link, $link, $link, + '', # pkg status FS::UI::Web::cust_links($cgi->param('cust_fields')), ], - 'align' => 'rlllll'. FS::UI::Web::cust_aligns(), + 'align' => 'rlllllr'. FS::UI::Web::cust_aligns(), 'color' => [ ('') x 6, + sub { + $cust_pkg_cache{$_[0]->svcnum} ||= $_[0]->cust_svc->cust_pkg; + return '' unless $cust_pkg_cache{$_[0]->svcnum}; + my $c = FS::cust_pkg::statuscolors; + $c->{$cust_pkg_cache{$_[0]->svcnum}->status }; + }, # pkg status FS::UI::Web::cust_colors(), ], 'style' => [ ('') x 6, + 'b', FS::UI::Web::cust_styles(), ], @@ -48,6 +62,8 @@ die "access denied" unless 'List services' ]); +my %cust_pkg_cache; + my $conf = new FS::Conf; my %search_hash; @@ -58,6 +74,9 @@ if ( $cgi->param('magic') eq 'unlinked' ) { ont_typenum oltnum shelf olt_port card vlan )) { $search_hash{$_} = $cgi->param($_) if defined($cgi->param($_)); } + if ( defined($cgi->param('cancelled')) ) { + $search_hash{'cancelled'} = $cgi->param('cancelled') ? 1 : 0; + } } my $query = FS::svc_fiber->search(\%search_hash); diff --git a/httemplate/search/svc_forward.cgi b/httemplate/search/svc_forward.cgi index 6a23bb3bb..c9b6012a7 100755 --- a/httemplate/search/svc_forward.cgi +++ b/httemplate/search/svc_forward.cgi @@ -8,28 +8,41 @@ 'Service', 'Mail to', 'Forwards to', + emt('Pkg. Status'), FS::UI::Web::cust_header(), ], 'fields' => [ 'svcnum', 'svc', $format_src, $format_dst, + sub { + $cust_pkg_cache{$_[0]->svcnum} ||= $_[0]->cust_svc->cust_pkg; + return '' unless $cust_pkg_cache{$_[0]->svcnum}; + $cust_pkg_cache{$_[0]->svcnum}->ucfirst_status + }, \&FS::UI::Web::cust_fields, ], 'links' => [ $link, $link, $link_src, $link_dst, + '', # pkg status ( map { $_ ne 'Cust. Status' ? $link_cust : '' } FS::UI::Web::cust_header() ), ], - 'align' => 'rlll'. FS::UI::Web::cust_aligns(), + 'align' => 'rlllr'. FS::UI::Web::cust_aligns(), 'color' => [ '', '', '', '', + sub { + $cust_pkg_cache{$_[0]->svcnum} ||= $_[0]->cust_svc->cust_pkg; + return '' unless $cust_pkg_cache{$_[0]->svcnum}; + my $c = FS::cust_pkg::statuscolors; + $c->{$cust_pkg_cache{$_[0]->svcnum}->status }; + }, # pkg status FS::UI::Web::cust_colors(), ], 'style' => [ @@ -37,6 +50,7 @@ '', '', '', + 'b', FS::UI::Web::cust_styles(), ], @@ -46,6 +60,8 @@ die "access denied" unless $FS::CurrentUser::CurrentUser->access_right('List services'); +my %cust_pkg_cache; + my $conf = new FS::Conf; my $orderby = 'ORDER BY svcnum'; @@ -62,6 +78,13 @@ if ( $cgi->param('magic') =~ /^(all|unlinked)$/ ) { } elsif ( $cgi->param('svcpart') =~ /^(\d+)$/ ) { push @extra_sql, "svcpart = $1"; + if (defined($cgi->param('cancelled'))) { + if ($cgi->param('cancelled')) { + push @extra_sql, "cust_pkg.cancel IS NOT NULL"; + } else { + push @extra_sql, "cust_pkg.cancel IS NULL"; + } + } } my $addl_from = ' LEFT JOIN cust_svc USING ( svcnum ) '. diff --git a/httemplate/search/svc_hardware.cgi b/httemplate/search/svc_hardware.cgi index 93fc2c391..c41cc5a6e 100644 --- a/httemplate/search/svc_hardware.cgi +++ b/httemplate/search/svc_hardware.cgi @@ -12,6 +12,7 @@ 'Hardware addr.', 'IP addr.', 'Smartcard', + emt('Pkg. Status'), FS::UI::Web::cust_header(), ], 'fields' => [ 'svcnum', @@ -22,24 +23,39 @@ 'display_hw_addr', 'ip_addr', 'smartcard', + sub { + $cust_pkg_cache{$_[0]->svcnum} ||= $_[0]->cust_svc->cust_pkg; + return '' unless $cust_pkg_cache{$_[0]->svcnum}; + $cust_pkg_cache{$_[0]->svcnum}->ucfirst_status + }, \&FS::UI::Web::cust_fields, ], 'links' => [ ($link_svc) x 8, + '', # pkg status ( map { $_ ne 'Cust. Status' ? $link_cust : '' } FS::UI::Web::cust_header() ) ], - 'align' => 'rlllllll' . FS::UI::Web::cust_aligns(), + 'align' => 'rlllllllr' . FS::UI::Web::cust_aligns(), 'color' => [ ('') x 8, - FS::UI::Web::cust_colors() ], + sub { + $cust_pkg_cache{$_[0]->svcnum} ||= $_[0]->cust_svc->cust_pkg; + return '' unless $cust_pkg_cache{$_[0]->svcnum}; + my $c = FS::cust_pkg::statuscolors; + $c->{$cust_pkg_cache{$_[0]->svcnum}->status }; + }, # pkg status + FS::UI::Web::cust_colors() ], 'style' => [ $svc_cancel_style, ('') x 7, - FS::UI::Web::cust_styles() ], + 'b', + FS::UI::Web::cust_styles() ], &> <%init> die "access denied" unless $FS::CurrentUser::CurrentUser->access_right('List services'); +my %cust_pkg_cache; + my $addl_from = ' LEFT JOIN cust_svc USING ( svcnum ) LEFT JOIN part_svc USING ( svcpart ) @@ -93,6 +109,13 @@ if ( $cgi->param('typenum') =~ /^(\d+)$/ ) { if ( $cgi->param('svcpart') =~ /^(\d+)$/ ) { push @extra_sql, "cust_svc.svcpart = $1"; + if (defined($cgi->param('cancelled'))) { + if ($cgi->param('cancelled')) { + push @extra_sql, "cust_pkg.cancel IS NOT NULL"; + } else { + push @extra_sql, "cust_pkg.cancel IS NULL"; + } + } } my ($orderby) = $cgi->param('orderby') =~ /^(\w+( ASC| DESC)?)$/i; diff --git a/httemplate/search/svc_phone.cgi b/httemplate/search/svc_phone.cgi index f3a056475..4c0b65487 100644 --- a/httemplate/search/svc_phone.cgi +++ b/httemplate/search/svc_phone.cgi @@ -9,6 +9,7 @@ 'Country code', 'Phone number', @header, + emt('Pkg. Status'), FS::UI::Web::cust_header($cgi->param('cust_fields')), ], 'fields' => [ 'svcnum', @@ -16,6 +17,11 @@ 'countrycode', 'phonenum', @fields, + sub { + $cust_pkg_cache{$_[0]->svcnum} ||= $_[0]->cust_svc->cust_pkg; + return '' unless $cust_pkg_cache{$_[0]->svcnum}; + $cust_pkg_cache{$_[0]->svcnum}->ucfirst_status + }, \&FS::UI::Web::cust_fields, ], 'links' => [ $link, @@ -23,12 +29,14 @@ $link, $link, ( map '', @header ), + '', # pkg status ( map { $_ ne 'Cust. Status' ? $link_cust : '' } FS::UI::Web::cust_header($cgi->param('cust_fields')) ), ], 'align' => 'rlrr'. join('', map 'r', @header). + 'r'. FS::UI::Web::cust_aligns(), 'color' => [ '', @@ -36,6 +44,12 @@ '', '', ( map '', @header ), + sub { + $cust_pkg_cache{$_[0]->svcnum} ||= $_[0]->cust_svc->cust_pkg; + return '' unless $cust_pkg_cache{$_[0]->svcnum}; + my $c = FS::cust_pkg::statuscolors; + $c->{$cust_pkg_cache{$_[0]->svcnum}->status }; + }, # pkg status FS::UI::Web::cust_colors(), ], 'style' => [ @@ -44,6 +58,7 @@ '', '', ( map '', @header ), + 'b', FS::UI::Web::cust_styles(), ], @@ -53,6 +68,8 @@ die "access denied" unless $FS::CurrentUser::CurrentUser->access_right('List services'); +my %cust_pkg_cache; + my $conf = new FS::Conf; my @select = (); @@ -132,6 +149,9 @@ if ( $cgi->param('magic') =~ /^(all|unlinked)$/ ) { } elsif ( $cgi->param('svcpart') =~ /^(\d+)$/ ) { $search_hash{'svcpart'} = [ $1 ]; + if ( defined($cgi->param('cancelled')) ) { + $search_hash{'cancelled'} = $cgi->param('cancelled') ? 1 : 0; + } } else { $cgi->param('phonenum') =~ /^([\d\- ]+)$/; my $phonenum = $1; diff --git a/httemplate/search/svc_www.cgi b/httemplate/search/svc_www.cgi index 7410262e8..6e9ba928e 100755 --- a/httemplate/search/svc_www.cgi +++ b/httemplate/search/svc_www.cgi @@ -8,6 +8,7 @@ 'Service', 'Zone', 'User', + emt('Pkg. Status'), FS::UI::Web::cust_header(), ], 'fields' => [ 'svcnum', @@ -20,22 +21,34 @@ ? $svc_acct->email : ''; }, + sub { + $cust_pkg_cache{$_[0]->svcnum} ||= $_[0]->cust_svc->cust_pkg; + return '' unless $cust_pkg_cache{$_[0]->svcnum}; + $cust_pkg_cache{$_[0]->svcnum}->ucfirst_status + }, \&FS::UI::Web::cust_fields, ], 'links' => [ $link, $link, '', $ulink, + '', # pkg status ( map { $_ ne 'Cust. Status' ? $link_cust : '' } FS::UI::Web::cust_header() ), ], - 'align' => 'rlll'. FS::UI::Web::cust_aligns(), + 'align' => 'rlllr'. FS::UI::Web::cust_aligns(), 'color' => [ '', '', '', '', + sub { + $cust_pkg_cache{$_[0]->svcnum} ||= $_[0]->cust_svc->cust_pkg; + return '' unless $cust_pkg_cache{$_[0]->svcnum}; + my $c = FS::cust_pkg::statuscolors; + $c->{$cust_pkg_cache{$_[0]->svcnum}->status }; + }, # pkg status FS::UI::Web::cust_colors(), ], 'style' => [ @@ -43,6 +56,7 @@ '', '', '', + 'b', FS::UI::Web::cust_styles(), ], @@ -52,6 +66,8 @@ die "access denied" unless $FS::CurrentUser::CurrentUser->access_right('List services'); +my %cust_pkg_cache; + #my $conf = new FS::Conf; my $orderby = 'ORDER BY svcnum'; @@ -68,6 +84,13 @@ if ( $cgi->param('magic') =~ /^(all|unlinked)$/ ) { } elsif ( $cgi->param('svcpart') =~ /^(\d+)$/ ) { push @extra_sql, "svcpart = $1"; + if (defined($cgi->param('cancelled'))) { + if ($cgi->param('cancelled')) { + push @extra_sql, "cust_pkg.cancel IS NOT NULL"; + } else { + push @extra_sql, "cust_pkg.cancel IS NULL"; + } + } } my $addl_from = ' LEFT JOIN cust_svc USING ( svcnum ) '. diff --git a/httemplate/search/tax_sales.cgi b/httemplate/search/tax_sales.cgi index 4b28c934a..91abd1bd3 100644 --- a/httemplate/search/tax_sales.cgi +++ b/httemplate/search/tax_sales.cgi @@ -113,7 +113,7 @@ while ($countdate < $enddate) { # run a report for each tax name foreach my $taxname (@taxnames) { $params{'taxname'} = $taxname; - my $report = FS::Report::Tax->report_internal(%params); + my $report = FS::Report::Tax::ByName->report(%params); # extract totals from report, kinda awkward my $pkgclass = ''; # this will get more complicated if we breakdown by pkgclass diff --git a/httemplate/view/cust_bill.cgi b/httemplate/view/cust_bill.cgi index cf0c3190a..d9525750c 100755 --- a/httemplate/view/cust_bill.cgi +++ b/httemplate/view/cust_bill.cgi @@ -95,8 +95,21 @@ function areyousure(href, message) { % } % if ( $curuser->access_right('Resend invoices') ) { + <A HREF="<% $p %>misc/send-invoice.cgi?method=print;<% $link %>"><% mt('Print this invoice') |h %></A> +% } + +% if ( $conf->exists('support-key') +% && $curuser->access_right('Print and mail invoices') +% ) +% { + | <& /elements/popup_link.html, + 'action' => $p."misc/post_fsinc-invoice.cgi?$link", + 'label' => 'Print and mail this invoice online', + 'actionlabel' => 'Invoice printing and mailing', + &> +% } - <A HREF="<% $p %>misc/send-invoice.cgi?method=print;<% $link %>"><% mt('Re-print this invoice') |h %></A> +% if ( $curuser->access_right('Resend invoices') ) { % if ( grep { $_ ne 'POST' } $cust_bill->cust_main->invoicing_list ) { | <A HREF="<% $p %>misc/send-invoice.cgi?method=email;<% $link %>"><% mt('Re-email this invoice') |h %></A> @@ -106,8 +119,11 @@ function areyousure(href, message) { | <A HREF="<% $p %>misc/send-invoice.cgi?method=fax;<% $link %>"><% mt('Re-fax this invoice') |h %></A> % } - <BR><BR> +% } +% if ( $curuser->access_right('Resend invoices') +% || $curuser->access_right('Print and mail invoices') ) { + <BR><BR> % } % my $br = 0; diff --git a/httemplate/view/cust_main.cgi b/httemplate/view/cust_main.cgi index 3cd7d2bb3..c5ee9203c 100755 --- a/httemplate/view/cust_main.cgi +++ b/httemplate/view/cust_main.cgi @@ -225,16 +225,19 @@ my $cust_main = qsearchs( { }); die "Customer not found!" unless $cust_main; -my $title = mt("Customer").' #'. $cust_main->display_custnum. ': '. - encode_entities($cust_main->name); +my $title = mt("Customer").' #'. $cust_main->display_custnum. ': '; +my $title_noescape = $title. encode_entities($cust_main->name); +$title .= $cust_main->name; if ( $curuser->num_agents ) { - $title = encode_entities($cust_main->agent->agent). " $title"; + $title_noescape = + encode_entities($cust_main->agent->agent). " $title_noescape"; + $title = $cust_main->agent->agent. " $title"; } my $status = $cust_main->status_label; $status .= ' (Cancelled)' if $cust_main->is_status_delay_cancel; -my $title_noescape = $title. ' (<B><FONT COLOR="#'. $cust_main->statuscolor. '">'. $status. '</FONT></B>)'; +$title_noescape .= ' (<B><FONT COLOR="#'. $cust_main->statuscolor. '">'. $status. '</FONT></B>)'; $title .= " ($status)"; #false laziness w/pref/pref.html and Conf.pm (cust_main-default_view) @@ -259,6 +262,15 @@ $views{$conf->config('cust_main-custom_title') || emt('Custom')} = 'custom' my %viewname = reverse %views; my $view = $cgi->param('show') || $curuser->default_customer_view; + +if ($view eq 'last') { + # something took us away from the page and is now bouncing back + $view = get_page_pref('last_view', $custnum); +} else { + # remember which view is open so we _can_ bounce back + set_page_pref('last_view', $custnum, $view); +} + $view = 'basics' if $view eq 'jumbo'; my $ie_compat = $conf->config('ie-compatibility_mode'); diff --git a/httemplate/view/cust_main/billing.html b/httemplate/view/cust_main/billing.html index 08b4323a8..894b2dfc0 100644 --- a/httemplate/view/cust_main/billing.html +++ b/httemplate/view/cust_main/billing.html @@ -26,79 +26,12 @@ % # customer base, and compare it to a graph of the overhead for generating this % # information. (and optimize it better, we could get it more from SQL) % if ( $cust_main->num_ncancelled_pkgs < 54 ) { -% my $sth = dbh->prepare(" -% SELECT DISTINCT freq FROM cust_pkg LEFT JOIN part_pkg USING (pkgpart) -% WHERE freq IS NOT NULL AND freq != '0' -% AND ( cancel IS NULL OR cancel = 0 ) -% AND custnum = ? -% ") or die $DBI::errstr; -% -% $sth->execute($cust_main->custnum) or die $sth->errstr; - -% #not really a numeric sort because freqs can actually be all sorts of things -% # but good enough for the 99% cases of ordering monthly quarterly annually -% my @freqs = sort { $a <=> $b } map { $_->[0] } @{ $sth->fetchall_arrayref }; -% -% foreach my $freq (@freqs) { -% my @cust_pkg = qsearch({ -% 'table' => 'cust_pkg', -% 'addl_from' => 'LEFT JOIN part_pkg USING (pkgpart)', -% 'hashref' => { 'custnum' => $cust_main->custnum, }, -% 'extra_sql' => 'AND ( cancel IS NULL OR cancel = 0 ) -% AND freq = '. dbh->quote($freq), -% 'order_by' => 'ORDER BY COALESCE(start_date,0), pkgnum', # to ensure old pkgs come before change_to_pkg -% }) or next; -% -% my $freq_pretty = $cust_pkg[0]->part_pkg->freq_pretty; -% -% my $amount = 0; -% my $skip_pkg = {}; -% foreach my $cust_pkg (@cust_pkg) { -% my $part_pkg = $cust_pkg->part_pkg; -% next if $cust_pkg->susp -% && ! $cust_pkg->option('suspend_bill') -% && ( ! $part_pkg->option('suspend_bill') -% || $cust_pkg->option('no_suspend_bill') -% ); -% -% #pkg change handling -% next if $skip_pkg->{$cust_pkg->pkgnum}; -% if ($cust_pkg->change_to_pkgnum) { -% #if change is on or before next bill date, use new pkg -% next if $cust_pkg->expire <= $cust_pkg->bill; -% #if change is after next bill date, use old (this) pkg -% $skip_pkg->{$cust_pkg->change_to_pkgnum} = 1; -% } -% -% my $pkg_amount = 0; -% -% #add recurring amounts for this package and its billing add-ons -% foreach my $l_part_pkg ( $part_pkg->self_and_bill_linked ) { -% $pkg_amount += $l_part_pkg->base_recur($cust_pkg); -% } -% -% #subtract amounts for any active discounts -% #(there should only be one at the moment, otherwise this makes no sense) -% foreach my $cust_pkg_discount ( $cust_pkg->cust_pkg_discount_active ) { -% my $discount = $cust_pkg_discount->discount; -% #and only one of these for each -% $pkg_amount -= $discount->amount; -% $pkg_amount -= $amount * $discount->percent/100; -% } -% -% $pkg_amount *= ( $cust_pkg->quantity || 1 ); -% -% $amount += $pkg_amount; -% -% } - +% foreach my $freq_info ($cust_main->display_recurring) { <TR> - <TH ALIGN="right"><% emt( ucfirst($freq_pretty). ' recurring' ) %></TH> - <TD><% $money_char. sprintf('%.2f', $amount) %></TD> - </TD> + <TH ALIGN="right"><% emt( ucfirst($freq_info->{'freq_pretty'}). ' recurring' ) %></TH> + <TD><% $money_char. sprintf('%.2f', $freq_info->{'amount'}) %></TD> </TR> % } - % } % if ( $conf->exists('cust_main-select-prorate_day') ) { diff --git a/httemplate/view/cust_main/change_history.html b/httemplate/view/cust_main/change_history.html index 09c1d6c6c..d46a4ffde 100644 --- a/httemplate/view/cust_main/change_history.html +++ b/httemplate/view/cust_main/change_history.html @@ -51,20 +51,25 @@ tie my %tables, 'Tie::IxHash', my $pkg_join = "JOIN cust_pkg USING ( pkgnum )"; my $svc_join = "JOIN cust_svc USING ( svcnum ) $pkg_join"; +my @svc_tables = qw( + svc_acct + svc_domain + svc_www + svc_forward + svc_broadband + svc_external + svc_phone + svc_cable +); + my %table_join = ( - 'svc_acct' => $svc_join, 'radius_usergroup' => $svc_join, - 'svc_domain' => $svc_join, - 'svc_www' => $svc_join, - 'svc_forward' => $svc_join, - 'svc_broadband' => $svc_join, - 'svc_external' => $svc_join, - 'svc_phone' => $svc_join, - 'svc_cable' => $svc_join, 'phone_device' => $svc_join, 'cust_pkg_discount'=> $pkg_join, ); +%table_join = (%table_join, map { $_ => $svc_join } @svc_tables); + # cust_main # cust_main_invoice @@ -134,6 +139,7 @@ my $newer_than = int( time - $years * 31556736 ); #60*60*24*365.24 local($FS::Record::nowarn_classload) = 1; +my %foundsvcs; foreach my $table ( keys %tables ) { my @items = qsearch({ 'table' => "h_$table", @@ -141,9 +147,51 @@ foreach my $table ( keys %tables ) { 'hashref' => { 'history_date' => { op=>'>=', value=>$newer_than }, }, 'extra_sql' => ' AND custnum = '. $cust_main->custnum, }); + %foundsvcs = (%foundsvcs, map { $_->svcnum => 1 } @items) + if $table =~ /^svc/; + push @history, @items; +} + +### Load svcs that are no longer linked (cust_svc has been deleted) + +# get list of all svcs for this customer from h_cust_svc +# could also load svcdb here by joining to part_svc on svcpart, +# but it would spoil database optimizations on this lookup +my @svcnumobj = qsearch({ + 'select' => 'DISTINCT svcnum', + 'hashref' => { 'history_date' => { op=>'>=', value=>$newer_than } }, + 'table' => 'h_cust_svc', + 'addl_from' => 'JOIN cust_pkg USING (pkgnum)', + 'extra_sql' => ' AND custnum = '. $cust_main->custnum, +}); + +# now grab those svcs explicitly +foreach my $svcnum ( map { $_->get('svcnum') } @svcnumobj ) { + # can skip the services we already found + next if $foundsvcs{$svcnum}; + # grab any given h_cust_svc record for this svcnum to get svcdb + my $svcdbobj = qsearchs({ + 'select' => 'svcdb', + 'hashref' => { svcnum => $svcnum }, + 'table' => 'h_cust_svc', + 'addl_from' => 'JOIN part_svc USING (svcpart)', + 'extra_sql' => 'LIMIT 1', + }); + unless ($svcdbobj && $svcdbobj->get('svcdb')) { + # don't think this ever happens, but just in case, don't break history page over it + warn "Could not load svcdb for svcnum $svcnum"; + next; + } + my @items = qsearch({ + 'table' => "h_".$svcdbobj->get('svcdb'), + 'hashref' => { 'history_date' => { op=>'>=', value=>$newer_than }, + 'svcnum' => $svcnum }, + }); push @history, @items; } +## legacy history + my @legacy_items = qsearch({ 'table' => 'legacy_cust_history', 'hashref' => { 'history_date' => { op=>'>=', value=>$newer_than }, }, diff --git a/httemplate/view/cust_main/menu.html b/httemplate/view/cust_main/menu.html index ab2d69d34..7c7303bc9 100644 --- a/httemplate/view/cust_main/menu.html +++ b/httemplate/view/cust_main/menu.html @@ -131,8 +131,6 @@ </style> -<& /elements/one_time_charge_link.html, form_only=>1 &> - <ul id="customer_menu"> % foreach my $submenu (@processed_menu) { <li> @@ -358,12 +356,7 @@ my @menu = ( { # it's just a popup, but there's some freaky CCH tax stuff in it label => 'One-time charge', - content => sub { - include( '/elements/one_time_charge_link.html', - custnum => shift->custnum, - no_form => 1, - ); - }, + url => "edit/quick-charge.html?custnum=$custnum", acl => 'One-time charge', }, { @@ -398,8 +391,17 @@ my @menu = ( url => "search/report_svc_acct.html?custnum=$custnum", }, { + label => 'View data usage', + popup => "search/report_sqlradius_usage-custnum.html?$custnum", + acl => 'Usage: RADIUS sessions', + actionlabel => 'Data usage report', + width => 480, + height => 245, + }, + { label => 'View CDRs', url => "search/report_cdr.html?custnum=$custnum", + # XXX should have a condition that the customer has any CDR packages }, ], [ diff --git a/httemplate/view/cust_main/packages/package.html b/httemplate/view/cust_main/packages/package.html index e167e2625..0a5305ed5 100644 --- a/httemplate/view/cust_main/packages/package.html +++ b/httemplate/view/cust_main/packages/package.html @@ -350,14 +350,8 @@ sub pkg_change_link { sub onetime_change_link { my $cust_pkg = shift; my $pkgnum = $cust_pkg->pkgnum; - include( '/elements/popup_link-cust_pkg.html', - 'action' => $p. "edit/quick-charge.html?change_pkgnum=$pkgnum", - 'label' => emt('Modify one-time charge'), - 'actionlabel' => emt('Modify'), - 'cust_pkg' => $cust_pkg, - 'width' => 690, - 'height' => 440, - ); + '<A HREF="' . $fsurl . "edit/quick-charge.html?change_pkgnum=$pkgnum" . + '">' . emt('Modify one-time charge') . '</A>' } sub pkg_change_location_link { diff --git a/httemplate/view/cust_main/packages/services.html b/httemplate/view/cust_main/packages/services.html index 39055f49a..a23a4c2fe 100644 --- a/httemplate/view/cust_main/packages/services.html +++ b/httemplate/view/cust_main/packages/services.html @@ -105,9 +105,25 @@ function clearhint_search_cust_svc(obj, str) { </TD> </TR> -% } +% } + +% } #foreach part_svc + +% if ($cust_pkg->get('cancel')) { +% foreach my $svc ( +% $cust_pkg->uncancel_svc_summary('summarize_size' => $opt{'cust_pkg-large_pkg_size'}, 'no_test_reprovision' => 1) +% ) { + <TR> + <TD ALIGN="right" VALIGN="top"><% $svc->{'svc'} |h %></TD> + <TD STYLE="padding-bottom:0px; font-style: italic"> + <% $svc->{'num_cust_svc'} + ? $svc->{'num_cust_svc'} . ' ' . emt('services in history') + : (defined($svc->{'label'}) ? $svc->{'label'} : emt('(cannot load svc label)')) |h %> + </TD> + </TR> +% } +% } -% } </TABLE> </TD> diff --git a/httemplate/view/cust_main/packages/status.html b/httemplate/view/cust_main/packages/status.html index 001c12876..3f629e12f 100644 --- a/httemplate/view/cust_main/packages/status.html +++ b/httemplate/view/cust_main/packages/status.html @@ -104,6 +104,14 @@ <TR> <TD COLSPAN=<%$opt{colspan}%>> <FONT SIZE=-1> +% if ( !$cust_pkg->change_to_pkgnum # because on a technical level, change won't propagate, +% # and there's not really a use case worth making that work +% and $part_pkg->freq # technically possible to have contract_end w/o freq, but nonsensical +% and $curuser->access_right('Change package contract end date') +% ) { + ( <% pkg_change_contract_end_link($cust_pkg) %> ) + <BR> +% } % if ( $cust_pkg->change_to_pkgnum ) { % # then you can modify the package change % if ( $curuser->access_right('Change customer package') ) { @@ -188,21 +196,6 @@ <% pkg_status_row_if($cust_pkg, emt('Start billing'), 'start_date', %opt) %> <% pkg_status_row_if($cust_pkg, emt('Un-cancelled'), 'uncancel', %opt ) %> -% if ( !$opt{no_links} -% and !$change_from -% and !$supplemental # can be changed from its main package -% and $curuser->access_right('Change package start date') ) -% { - - <TR> - <TD COLSPAN=<%$opt{colspan}%>> - <FONT SIZE=-1> - ( <% pkg_change_start_link($cust_pkg) %> ) - </FONT> - </TD> - </TR> -% } - % } % % } else { #setup @@ -286,6 +279,28 @@ <TR> <TD COLSPAN=<%$opt{colspan}%>> <FONT SIZE=-1> + +% #change date links +% if ( !$change_from and !$supplemental ) { +% my $has_date_links = 0; +% if ( !$cust_pkg->get('setup') +% and $curuser->access_right('Change package start date') +% ) { + ( <% pkg_change_start_link($cust_pkg) %> ) +% $has_date_links = 1; +% } +% if ( !$cust_pkg->change_to_pkgnum # because on a technical level, change won't propagate, +% # and there's not really a use case worth making that work +% and $curuser->access_right('Change package contract end date') +% ) { + ( <% pkg_change_contract_end_link($cust_pkg) %> ) +% $has_date_links = 1; +% } +% if ($has_date_links) { + <BR> +% } +% } + % # action links % if ( $change_from ) { % # nothing @@ -715,6 +730,8 @@ sub pkg_uncancel_link { 'actionlabel' => emt('Un-cancel'), #'color' => #? 'cust_pkg' => shift, + 'width' => 960, + 'height' => 740, ) } @@ -743,7 +760,7 @@ sub pkg_change_later_link { sub pkg_change_start_link { my $cust_pkg = shift; include( '/elements/popup_link-cust_pkg.html', - 'action' => $p . 'misc/change_pkg_start.html', + 'action' => $p . 'misc/change_pkg_date.html?field=start_date', 'label' => emt('Set start date'), 'actionlabel' => emt('Set start of billing for'), 'cust_pkg' => $cust_pkg, @@ -752,6 +769,18 @@ sub pkg_change_start_link { ) } +sub pkg_change_contract_end_link { + my $cust_pkg = shift; + include( '/elements/popup_link-cust_pkg.html', + 'action' => $p . 'misc/change_pkg_date.html?field=contract_end', + 'label' => emt('Set contract end'), + 'actionlabel' => emt('Set contract end for'), + 'cust_pkg' => $cust_pkg, + 'width' => 510, + 'height' => 310, + ) +} + sub svc_recharge_link { include( '/elements/popup_link-cust_svc.html', 'action' => $p. 'misc/recharge_svc.html', diff --git a/httemplate/view/cust_main/payment_history.html b/httemplate/view/cust_main/payment_history.html index 08d468104..9c18fbc15 100644 --- a/httemplate/view/cust_main/payment_history.html +++ b/httemplate/view/cust_main/payment_history.html @@ -136,29 +136,43 @@ % if ( $item->{'balance_forward'} ) { <& .balance_forward_row, $item->{'balance'}, $item->{'date'} &> -% } +% } %} # foreach $item +% if ( $old_history ) { +<& .hide_history_row, $old_history++ &> +% } + </TABLE> </TD> </TR> </TABLE> +<SCRIPT SRC="<% $fsurl %>elements/page_pref.js"></SCRIPT> <SCRIPT TYPE="text/javascript"> -function show_history () { - //alert('showing history!'); - +function show_history(show) { // but don't update pref var balance_forward_row = document.getElementById('balance_forward_row'); - balance_forward_row.style.display = 'none'; + balance_forward_row.style.display = show ? 'none' : ''; for ( var i = 0; i < <% $old_history %>; i++ ) { var oldRow = document.getElementById('old_history'+i); - oldRow.style.display = ''; + oldRow.style.display = show ? '' : 'none'; } +} + +function update_show_history (show) { + show = show ? 1 : 0; + show_history(show); + // update user pref (blind post, don't care about the output here) + set_page_pref('expand_old_history', '<% $custnum %>', show); } +$().ready(function() { + show_history(<% get_page_pref('expand_old_history', $custnum) %>); +}); + </SCRIPT> <%def .balance_forward_row> % my( $b, $date ) = @_; @@ -171,7 +185,7 @@ function show_history () { <TD CLASS="grid" BGCOLOR="#dddddd"> <I><% mt("Starting balance on [_1]", time2str($date_format, $date) ) |h %></I> - (<A HREF="javascript:void(0);" onClick="show_history();"><% mt('show prior history') |h %></A>) + (<A HREF="javascript:void(0);" onClick="update_show_history(true);"><% mt('show prior history') |h %></A>) </TD> <TD CLASS="grid" BGCOLOR="#dddddd"></TD> @@ -182,6 +196,17 @@ function show_history () { </TR> </%def> +<%def .hide_history_row> +% my $num = shift; + <TR ID="old_history<% $num %>" STYLE="display: none"> + <TD CLASS="grid" BGCOLOR="#dddddd"></TD> + <TD CLASS="grid" BGCOLOR="#dddddd"> + <I>(<A HREF="#" onclick="update_show_history(false)"><% mt('hide prior history') |h %></A>)</I> + </TD> + <TD CLASS="grid" BGCOLOR="#dddddd" COLSPAN=5></TD> + </TR> +</%def> + <%shared> my $conf = new FS::Conf; my $date_format = $conf->config('date_format') || '%m/%d/%Y'; diff --git a/httemplate/view/cust_main/payment_history/pending_payment.html b/httemplate/view/cust_main/payment_history/pending_payment.html index 31149231b..cf7ef7c08 100644 --- a/httemplate/view/cust_main/payment_history/pending_payment.html +++ b/httemplate/view/cust_main/payment_history/pending_payment.html @@ -12,6 +12,7 @@ my %statusaction = ( 'new' => 'delete', 'thirdparty' => 'delete', 'pending' => 'complete', + 'authorized' => 'complete', 'captured' => 'capture', ); diff --git a/httemplate/view/cust_pay.html b/httemplate/view/cust_pay.html index b34a90818..ba4cfdafc 100644 --- a/httemplate/view/cust_pay.html +++ b/httemplate/view/cust_pay.html @@ -91,13 +91,14 @@ <TD BGCOLOR="#FFFFFF"><B><% $cust_pay->auth %></B></TD> </TR> -% if ( $cust_pay->order_number ) { - <TR> - <TD ALIGN="right"><% mt('Order #') |h %></TD> - <TD BGCOLOR="#FFFFFF"><B><% $cust_pay->order_number %></B></TD> - </TR> -% } +% } +% # API allows setting this for any payby +% if ( $cust_pay->order_number ) { + <TR> + <TD ALIGN="right"><% mt('Order #') |h %></TD> + <TD BGCOLOR="#FFFFFF"><B><% $cust_pay->order_number %></B></TD> + </TR> % } % if ( $cust_pay->payby eq 'CASH' && $cust_pay->payinfo ) { diff --git a/httemplate/view/elements/svc_edit_link.html b/httemplate/view/elements/svc_edit_link.html index 3ff2f58b6..2de5ecf9c 100644 --- a/httemplate/view/elements/svc_edit_link.html +++ b/httemplate/view/elements/svc_edit_link.html @@ -1,18 +1,18 @@ -% if ( $cancel_date ) { -<I><% mt("Canceled [_1]", time2str('%b %o %Y', $cancel_date) ) |h %></I> -% } else { <SCRIPT> function areyousure_delete() { if (confirm(<% mt('Permanently delete this service?') |js_string %>) == true) window.location.href = '<% $cancel_url %>'; } </SCRIPT> +% if ( $cancel_date ) { +| <I><% mt("Canceled [_1]", time2str('%b %o %Y', $cancel_date) ) |h %></I> +% } else { % if ( $curuser->access_right('Provision customer service') ) { | <A HREF="<% $edit_url %>"><% mt("Edit this [_1]", $label) |h %></A> % } -% if ( $curuser->access_right('Unprovision customer service') ) { +% } +% if ( $curuser->access_right('Unprovision customer service') ) { | <A HREF="javascript:areyousure_delete()"><% mt('Unprovision this Service') |h %></A> -% } % } <& /elements/manage_device_link.html, 'svc' => $svc_x, diff --git a/httemplate/view/quotation.html b/httemplate/view/quotation.html index 5646fb973..58d398ca2 100755 --- a/httemplate/view/quotation.html +++ b/httemplate/view/quotation.html @@ -23,9 +23,13 @@ function areyousure(href, message) { % if ( $curuser->access_right('One-time charge') ) { <% $inrow ? ' | ' : '' %> - <& /elements/one_time_charge_link.html, - map { $_ => $quotation->$_ } qw( quotationnum custnum prospectnum ) - &> +% my $query = 'quotationnum=' . $quotation->get('quotationnum'); +% if ($quotation->custnum) { +% $query .= ';custnum=' . $quotation->custnum; +% } else { +% $query .= ';prospectnum=' . $quotation->prospectnum; +% } + <A HREF="<% $fsurl . 'edit/quick-charge.html?' . $query %>"><% emt('One-time charge') %></A> % $inrow++; % } @@ -63,7 +67,9 @@ function areyousure(href, message) { <BR><BR> % if ( $curuser->access_right('New customer') && $quotation->quotation_pkg ) { +% # if we end up with more than one option, combine these links and add an interstitial screen <A HREF="<%$p%>edit/process/quotation_convert.html?quotationnum=<% $quotation->quotationnum %>">Place order</A> + | <A HREF="<%$p%>edit/process/quotation_convert.html?quotationnum=<% $quotation->quotationnum %>&onhold=1">Order on hold</A> <BR><BR> % } diff --git a/httemplate/view/sector_map-png.cgi b/httemplate/view/sector_map-png.cgi new file mode 100644 index 000000000..7e7e799a8 --- /dev/null +++ b/httemplate/view/sector_map-png.cgi @@ -0,0 +1,8 @@ +<%init> +my ($sectornum) = $cgi->keywords; +my $sector = FS::tower_sector->by_key($sectornum); +if ( $sector and length($sector->image) > 0 ) { + http_header('Content-Type', 'image/png'); + $m->print($sector->image); +} +</%init> diff --git a/httemplate/view/svc_acct/basics.html b/httemplate/view/svc_acct/basics.html index 1b7d56b23..5532c22a5 100644 --- a/httemplate/view/svc_acct/basics.html +++ b/httemplate/view/svc_acct/basics.html @@ -1,18 +1,18 @@ <% &ntable("#cccccc") %><TR><TD><% &ntable("#cccccc",2) %> -<& /view/elements/tr.html, label=>mt('Service'), value=>$part_svc->svc &> +<& /view/elements/tr.html, label=> $part_svc->part_svc_column('svc')->columnlabel || mt('Service'), value=>$part_svc->svc &> % if ( $opt{cust_svc}->agent_svcid ) { <& /view/elements/tr.html, label=>mt('Legacy ID'), value=>$opt{cust_svc}->agent_svcid &> % } -<& /view/elements/tr.html, label=>mt('Username'), value=>$svc_acct->username &> -<& /view/elements/tr.html, label=>mt('Domain'), value=>$domain &> +<& /view/elements/tr.html, label=> $part_svc->part_svc_column('username')->columnlabel || mt('Username'), value=>$svc_acct->username &> +<& /view/elements/tr.html, label=> $part_svc->part_svc_column('domsvc')->columnlabel || mt('Domain'), value=>$domain &> % if ( $opt{'communigate'} ) { <& /view/elements/tr.html, label=>mt('Aliases'), value=>$svc_acct->cgp_aliases &> %} % if ( $svc_acct->pbxsvc ) { - <& /view/elements/tr.html, label=>mt('PBX'), value=>$svc_acct->pbx_title &> + <& /view/elements/tr.html, label=> $part_svc->part_svc_column('pbxsvc')->columnlabel || mt('PBX'), value=>$svc_acct->pbx_title &> %} % my $show_pw = ''; @@ -38,7 +38,7 @@ % # show nothing % } else { <TR> - <TD ALIGN="right"><% mt('Password') %></TD> + <TD ALIGN="right"><% $psc->columnlabel || mt('Password') %></TD> <TD STYLE="background-color: #ffffff; white-space: nowrap"> <% $show_pw %> % my $curuser = $FS::CurrentUser::CurrentUser; @@ -58,12 +58,12 @@ % } % if ( $conf->exists('security_phrase') ) { - <& /view/elements/tr.html, label=>mt('Security phrase'), value=>$svc_acct->sec_phrase &> + <& /view/elements/tr.html, label=> $part_svc->part_svc_column('sec_phrase')->columnlabel || mt('Security phrase'), value=>$svc_acct->sec_phrase &> % } % if ( $svc_acct->popnum ) { % my $svc_acct_pop = qsearchs('svc_acct_pop',{'popnum'=>$svc_acct->popnum}); - <& /view/elements/tr.html, label=>mt('Access number'), value=>$svc_acct_pop->text &> + <& /view/elements/tr.html, label=> $part_svc->part_svc_column('popnum')->columnlabel || mt('Access number'), value=>$svc_acct_pop->text &> % } % if ( $svc_acct->sectornum && $conf->exists('svc_acct-tower_sector') ) { @@ -72,7 +72,7 @@ % ? '<A HREF="http://'. $tower_sector->ip_addr. '">' % : ''; <& /view/elements/tr.html, - label => mt('Tower sector'), + label => $part_svc->part_svc_column('sectornum')->columnlabel || mt('Tower sector'), value => $link. $tower_sector->description. ($link ? '</A>' : ''), &> % } @@ -83,28 +83,28 @@ &> % if ($svc_acct->uid ne '') { - <& /view/elements/tr.html, label=>mt('UID'), value=>$svc_acct->uid &> + <& /view/elements/tr.html, label=> $part_svc->part_svc_column('uid')->columnlabel || mt('UID'), value=>$svc_acct->uid &> % } % if ($svc_acct->gid ne '') { - <& /view/elements/tr.html, label=>mt('GID'), value=>$svc_acct->gid &> + <& /view/elements/tr.html, label=> $part_svc->part_svc_column('gid')->columnlabel || mt('GID'), value=>$svc_acct->gid &> % } % if ($svc_acct->finger ne '') { - <& /view/elements/tr.html, label=>mt('Real Name'), value=>$svc_acct->finger &> + <& /view/elements/tr.html, label=> $part_svc->part_svc_column('finger')->columnlabel || mt('Real Name'), value=>$svc_acct->finger &> % } % if ($svc_acct->dir ne '') { - <& /view/elements/tr.html, label=>mt('Home directory'), value=>$svc_acct->dir &> + <& /view/elements/tr.html, label=> $part_svc->part_svc_column('dir')->columnlabel || mt('Home directory'), value=>$svc_acct->dir &> % } % if ($svc_acct->shell ne '') { - <& /view/elements/tr.html, label=>mt('Shell'), value=>$svc_acct->shell &> + <& /view/elements/tr.html, label=> $part_svc->part_svc_column('shell')->columnlabel || mt('Shell'), value=>$svc_acct->shell &> % } % if ($svc_acct->quota ne '' && ! $opt{'communigate'} ) { - <& /view/elements/tr.html, label=>mt('Quota'), value=>$svc_acct->quota &> + <& /view/elements/tr.html, label=> $part_svc->part_svc_column('quota')->columnlabel || mt('Quota'), value=>$svc_acct->quota &> % } elsif ( $opt{'communigate'} ) { @@ -133,7 +133,7 @@ sub slipip { % if ($svc_acct->slipip) { <& /view/elements/tr.html, - label=>mt('IP address'), + label=> $part_svc->part_svc_column('slipip')->columnlabel || mt('IP address'), value=> slipip($svc_acct) &> % } @@ -156,7 +156,7 @@ sub slipip { &> % } -<& /view/elements/tr.html, label=>mt('RADIUS groups'), +<& /view/elements/tr.html, label=> $part_svc->part_svc_column('usergroup')->columnlabel || mt('RADIUS groups'), value=>join('<BR>', $svc_acct->radius_groups('long_description')) &> <& router.html, 'svc_acct' => $svc_acct &> |