diff options
Diffstat (limited to 'httemplate')
231 files changed, 5367 insertions, 2185 deletions
diff --git a/httemplate/browse/msgcat.html b/httemplate/browse/msgcat.html index ac8a3a437..7509cf7d4 100644 --- a/httemplate/browse/msgcat.html +++ b/httemplate/browse/msgcat.html @@ -1,5 +1,5 @@ <& elements/browse.html, - title => mt('Message catalog'), + title => mt('Translation strings'), name_singular => 'string', #mt? no, we need to do it through the quant/PL stuff query => { 'table' => 'msgcat', 'hashref' => { 'locale' => $locale, }, diff --git a/httemplate/browse/part_export.cgi b/httemplate/browse/part_export.cgi index 91238a0fd..876633afc 100755 --- a/httemplate/browse/part_export.cgi +++ b/httemplate/browse/part_export.cgi @@ -38,6 +38,21 @@ function part_export_areyousure(href) { <TD CLASS="grid" BGCOLOR="<% $bgcolor %>"> <% $part_export->label_html %> (<A HREF="<% $p %>edit/part_export.cgi?<% $part_export->exportnum %>">edit</A> | <A HREF="javascript:part_export_areyousure('<% $p %>misc/delete-part_export.cgi?<% $part_export->exportnum %>')">delete</A>) +% if ( my @actions = $part_export->actions ) { + <P STYLE="position: absolute"> + Management: +% while (@actions) { +% my $label = shift @actions; +% my $path = shift @actions; + <& /elements/popup_link.html, + 'label' => $label, + 'action' => $fsurl.$path.'?'.$part_export->exportnum, + 'actionlabel' => $label, + &><% @actions ? ' | ' : '' %> +% } + </P> +% } #if @actions + </TD> <TD CLASS="inv" BGCOLOR="<% $bgcolor %>"> diff --git a/httemplate/browse/part_pkg.cgi b/httemplate/browse/part_pkg.cgi index 57a429747..bb5bc5215 100755 --- a/httemplate/browse/part_pkg.cgi +++ b/httemplate/browse/part_pkg.cgi @@ -1,6 +1,8 @@ <% include( 'elements/browse.html', 'title' => 'Package Definitions', + 'menubar' => \@menubar, 'html_init' => $html_init, + 'html_form' => $html_form, 'html_posttotal' => $html_posttotal, 'name' => 'package definitions', 'disableable' => 1, @@ -20,6 +22,9 @@ 'fields' => \@fields, 'links' => \@links, 'align' => $align, + 'link_field' => 'pkgpart', + 'html_init' => $html_init, + 'html_foot' => $html_foot, ) %> <%init> @@ -33,6 +38,7 @@ my $acl_edit_global = $curuser->access_right($edit_global); my $acl_config = $curuser->access_right('Configuration'); #to edit services #and agent types #and bulk change +my $acl_edit_bulk = $curuser->access_right('Bulk edit package definitions'); die "access denied" unless $acl_edit || $acl_edit_global; @@ -130,9 +136,7 @@ $select = " "; -my $html_init; -#unless ( $cgi->param('active') ) { - $html_init = qq! +my $html_init = qq! One or more service definitions are grouped together into a package definition and given pricing information. Customers purchase packages rather than purchase services directly.<BR><BR> @@ -144,7 +148,6 @@ my $html_init; </FORM> <BR><BR> !; -#} $cgi->param('dummy', 1); @@ -274,6 +277,18 @@ push @fields, sub { : () ), ], + ( map { my $dst_pkg = $_->dst_pkg; + [ + { data => 'Supplemental: '. + '<A HREF="#'. $dst_pkg->pkgpart . '">' . + $dst_pkg->pkg . '</A>', + align=> 'center', + colspan => 2, + } + ] + } + $part_pkg->supp_part_pkg_link + ), ( map { my $dst_pkg = $_->dst_pkg; [ @@ -423,6 +438,10 @@ if ( $taxclasses ) { $align .= 'l'; } +# make a table of report class optionnames => the actual +my %report_optionname_name = map { 'report_option_'.$_->num, $_->name } + qsearch('part_pkg_report_option', { disabled => '' }); + push @header, 'Plan options', 'Services'; #'Service', 'Quan', 'Primary'; @@ -433,8 +452,18 @@ push @fields, if ( $part_pkg->plan ) { my %options = $part_pkg->options; - - [ map { + # gather any options that are really report options, + # convert them to their user-friendly names, + # and sort them (I think?) + my @report_options = + sort { $a cmp $b } + map { $report_optionname_name{$_} } + grep { $options{$_} + and exists($report_optionname_name{$_}) } + keys %options; + + my @rows = ( + map { [ { 'data' => "$_: ", 'align' => 'right', @@ -445,11 +474,30 @@ push @fields, ]; } grep { $options{$_} =~ /\S/ } - grep { $_ !~ /^(setup|recur)_fee$/ } + grep { $_ !~ /^(setup|recur)_fee$/ + and $_ !~ /^report_option_\d+$/ } keys %options - ]; + ); + if ( @report_options ) { + push @rows, + [ { 'data' => 'Report classes', + 'align' => 'center', + 'style' => 'font-weight: bold', + 'colspan' => 2 + } ]; + foreach (@report_options) { + push @rows, [ + { 'data' => $_, + 'align' => 'center', + 'colspan' => 2 + } + ]; + } # foreach @report_options + } # if @report_options + + return \@rows; - } else { + } else { # should never happen... [ map { [ { 'data' => uc($_), @@ -470,6 +518,8 @@ push @fields, sub { my $part_pkg = shift; + my @part_pkg_usage = sort { $a->priority <=> $b->priority } + $part_pkg->part_pkg_usage; [ (map { @@ -512,7 +562,27 @@ push @fields, ] } $part_pkg->svc_part_pkg_link - ) + ), + ( scalar(@part_pkg_usage) ? + [ { data => 'Usage minutes', + align => 'center', + colspan => 2, + data_style => 'b', + link => $p.'browse/part_pkg_usage.html#pkgpart'. + $part_pkg->pkgpart + } ] + : () + ), + ( map { + [ { data => $_->minutes, + align => 'right' + }, + { data => $_->description, + align => 'left' + }, + ] + } @part_pkg_usage + ), ]; }; @@ -527,4 +597,25 @@ $extra_count = ( $count_extra_sql ? ' AND ' : ' WHERE ' ). $extra_count if $extra_count; my $count_query = "SELECT COUNT(*) FROM part_pkg $count_extra_sql $extra_count"; +my $html_form = ''; +my $html_foot = ''; +if ( $acl_edit_bulk ) { + # insert a checkbox column + push @header, ''; + push @fields, sub { + '<INPUT TYPE="checkbox" NAME="pkgpart" VALUE=' . $_[0]->pkgpart .'>'; + }; + push @links, ''; + $align .= 'c'; + $html_form = qq!<FORM ACTION="${p}edit/bulk-part_pkg.html" METHOD="POST">!; + $html_foot = include('/search/elements/checkbox-foot.html', + submit => 'edit report classes', # for now it's only report classes + ) . '</FORM>'; +} + +my @menubar; +# show this if there are any voip_cdr packages defined +if ( FS::part_pkg->count("plan = 'voip_cdr'") ) { + push @menubar, 'Per-package usage minutes' => $p.'browse/part_pkg_usage.html'; +} </%init> diff --git a/httemplate/browse/part_pkg_usage.html b/httemplate/browse/part_pkg_usage.html new file mode 100644 index 000000000..209fd3a01 --- /dev/null +++ b/httemplate/browse/part_pkg_usage.html @@ -0,0 +1,112 @@ +<& /elements/header.html, 'Package usage minutes' &> +<& /elements/menubar.html, 'Package definitions', $p.'browse/part_pkg.cgi' &> +<STYLE TYPE="text/css"> +.pkg_head { + background-color: #dddddd; + font-style: italic; +} +.pkg_head > td { + border-style: solid; + border-radius: 3px; + border-color: #555555; + border-width: 1px; +} +.usage > td { + text-align: center; +} +.error { + color: #ff0000; +} +</STYLE> +<FORM METHOD="POST" ACTION="<%$fsurl%>edit/process/part_pkg_usage.html"> + <TABLE STYLE="margin-top: 1em"> + <TR> + <TH>Minutes</TH> + <TH>Shared</TH> + <TH>Rollover</TH> + <TH>Description</TH> + <TH>Priority</TH> +% foreach my $class (@usage_class) { + <TH><% $class->classname %></TH> +% } + </TR> + +% my $error = $cgi->param('error'); +% foreach my $part_pkg (@part_pkg) { +% my $pkgpart = $part_pkg->pkgpart; +% my @part_pkg_usage; +% if ( $error ) { +% @part_pkg_usage = @{ $error->{$pkgpart} }; +% } else { +% @part_pkg_usage = $part_pkg->part_pkg_usage; +% foreach my $usage (@part_pkg_usage) { +% foreach ($usage->classnums) { +% $usage->set("class$_".'_', 'Y'); +% } +% } +% } + <TR CLASS="pkg_head" ID="pkgpart<%$pkgpart%>"> + <TD COLSPAN=<%$n_cols%>><% $part_pkg->pkg_comment %></TD> +% # make it easy to enumerate the pkgparts later + <INPUT TYPE="hidden" NAME="pkgpart" VALUE="<% $pkgpart %>"> + </TR> +% # template row + <TR id="pkgpart<%$pkgpart%>_template" CLASS="usage"> + <TD> + <INPUT TYPE="hidden" NAME="pkgusagepart"> + <INPUT TYPE="text" NAME="minutes" ID="minutes" SIZE=7> + </TD> +% foreach (qw(shared rollover)) { + <TD> + <INPUT TYPE="checkbox" NAME="<% $_ %>" ID="<% $_ %>" VALUE="Y"> + </TD> +% } + <TD> + <INPUT TYPE="text" NAME="description" ID="description" SIZE=20> + </TD> + <TD> + <INPUT TYPE="text" NAME="priority" ID="priority" SIZE=3> + </TD> +% foreach (@usage_class) { +% my $classnum = 'class' . $_->classnum . '_'; + <TD> + <INPUT TYPE="checkbox" NAME="<% $classnum %>" ID="<% $classnum %>" VALUE="Y"> + </TD> +% } + </TR> + <& /elements/auto-table.html, + table => "pkgpart$pkgpart", + template_row => "pkgpart$pkgpart".'_template', + data => \@part_pkg_usage, + &> +% } + </TABLE> + <BR> + <INPUT TYPE="submit"> +</FORM> +<& /elements/footer.html &> +<%init> +my $curuser = $FS::CurrentUser::CurrentUser; +die "access denied" + unless $curuser->access_right( + ['Edit package definitions', 'Edit global package definitions'] + ); + +my @where = ("(plan = 'voip_cdr' OR plan = 'voip_inbound')", + "freq != '0'", + "disabled IS NULL"); +push @where, FS::part_pkg->curuser_pkgs_sql + unless $curuser->access_right('Edit global package definitions'); +my $extra_sql = ' WHERE '.join(' AND ', @where); +my @part_pkg = qsearch({ + 'table' => 'part_pkg', + 'extra_sql' => $extra_sql, + 'order_by' => ' ORDER BY pkgpart', +}); + +my @usage_class = sort { $a->weight <=> $b->weight } + qsearch('usage_class', { disabled => '' }); + +my $n_usage_classes = scalar(@usage_class); +my $n_cols = $n_usage_classes + 5; # minutes, shared, rollover, desc, prio +</%init> diff --git a/httemplate/browse/part_svc.cgi b/httemplate/browse/part_svc.cgi index a8f4a7c84..0d3685355 100755 --- a/httemplate/browse/part_svc.cgi +++ b/httemplate/browse/part_svc.cgi @@ -82,6 +82,7 @@ function part_export_areyousure(href) { % } % @dfields ; % my $rowspan = scalar(@fields) || 1; +% $rowspan++ if $part_svc->restrict_edit_password; % my $url = "${p}edit/part_svc.cgi?". $part_svc->svcpart; % % if ( $bgcolor eq $bgcolor1 ) { @@ -174,24 +175,32 @@ function part_export_areyousure(href) { % my $value = &$formatter($part_svc->part_svc_column($field)->columnvalue); % if ( $flag =~ /^[MAH]$/ ) { % my $select_table = ($flag eq 'H') ? 'hardware_class' : 'inventory_class'; -% $select_class{$value} ||= -% qsearchs($select_table, { 'classnum' => $value } ); +% foreach my $classnum ( split(',', $value) ) { +% $select_class{$classnum} = +% qsearchs($select_table, { 'classnum' => $classnum } ); % - <% $select_class{$value} - ? $select_class{$value}->classname - : "WARNING: $select_table.classnum $value not found" %> + <% $select_class{$classnum} + ? $select_class{$classnum}->classname + : "WARNING: $select_table.classnum $classnum not found" %><BR> +% } % } else { <% $value %> -% } +% } </TD> % $n1="</TR><TR>"; -% } -% +% } #foreach $field +% if ( $part_svc->restrict_edit_password ) { + <TR> + <TD CLASS="grid" BGCOLOR="<% $bgcolor %>" COLSPAN=4 ALIGN="left"> + <B><% emt('Password editing restricted.') %></B> + </TD> + </TR> +% } </TR> -% } +% } #foreach $part_svc </TABLE> </BODY> diff --git a/httemplate/browse/rate_region.html b/httemplate/browse/rate_region.html index b958894cb..b0ce467c0 100644 --- a/httemplate/browse/rate_region.html +++ b/httemplate/browse/rate_region.html @@ -62,8 +62,14 @@ tie my %granularity, 'Tie::IxHash', FS::rate_detail::granularities(); die "access denied" unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); +my $sub_prefixes = sub { + my $region = shift; + $region->prefixes . + ($region->exact_match ? ' <I>(exact match only)</I>' : ''); +}; + my @header = ( '#', 'Region', 'Country code', 'Prefixes' ); -my @fields = ( 'regionnum', 'regionname', 'ccode', 'prefixes' ); +my @fields = ( 'regionnum', 'regionname', 'ccode', $sub_prefixes ); my @links = ( ($link) x 4 ); my @align = ( 'right', 'left', 'right', 'left' ); my @xls_format = ( ({ locked=>1, bg_color=>22 }) x 4 ); diff --git a/httemplate/docs/license.html b/httemplate/docs/license.html index e40b2436b..05a89c1f0 100644 --- a/httemplate/docs/license.html +++ b/httemplate/docs/license.html @@ -6,7 +6,7 @@ <P> -Copyright © 2005-2012 Freeside Internet Services, Inc.<BR> +Copyright © 2005-2013 Freeside Internet Services, Inc.<BR> Copyright © 2000-2005 Ivan Kohler<BR> Copyright © 1999 Silicon Interactive Software Design<BR> All rights reserved<BR> diff --git a/httemplate/edit/REAL_cust_pkg.cgi b/httemplate/edit/REAL_cust_pkg.cgi index 166a3b7ea..99e911ae5 100755 --- a/httemplate/edit/REAL_cust_pkg.cgi +++ b/httemplate/edit/REAL_cust_pkg.cgi @@ -9,6 +9,29 @@ <SCRIPT TYPE="text/javascript" SRC="../elements/calendar-en.js"></SCRIPT> <SCRIPT TYPE="text/javascript" SRC="../elements/calendar-setup.js"></SCRIPT> +<SCRIPT TYPE="text/javascript"> +var submit_fields = []; +function confirm_changes() { + var i; + var querystring = 'pkgnum=<%$pkgnum%>'; + var f = document.forms.formname; + for(i = 0; i < submit_fields.length; i++) { + querystring += ';' + + submit_fields[i] + + '=' + + encodeURIComponent(f.elements[submit_fields[i] + '_text'].value); + } + overlib( + OLiframeContent( + '<%$p%>/misc/confirm-cust_pkg-edit_dates.html?' + querystring, + 576, 576, 'confirm_popup' + ), + CAPTION, 'Package date changes', STICKY, AUTOSTATUSCAP, CLOSETEXT, '', + MIDX, 0, MIDY, 0, DRAGGABLE, BGCOLOR, '#333399', CGCOLOR, '#333399', + TEXTSIZE, 3 + ); +} +</SCRIPT> <FORM NAME="formname" ACTION="process/REAL_cust_pkg.cgi" METHOD="POST"> <INPUT TYPE="hidden" NAME="pkgnum" VALUE="<% $pkgnum %>"> @@ -31,6 +54,15 @@ <TD BGCOLOR="#ffffff"><% $part_pkg->pkg %></TD> </TR> +% if ( $cust_pkg->main_pkgnum ) { +% my $main_pkg = $cust_pkg->main_pkg; + <TR> + <TD ALIGN="right">Supplemental to</TD> + <TD BGCOLOR="#ffffff">Package #<% $cust_pkg->main_pkgnum%>: \ + <% $main_pkg->part_pkg->pkg %></TD> + </TR> + +% } <TR> <TD ALIGN="right">Custom</TD> <TD BGCOLOR="#ffffff"><% $part_pkg->custom %></TD> @@ -38,7 +70,7 @@ <TR> <TD ALIGN="right">Comment</TD> - <TD BGCOLOR="#ffffff"><% $part_pkg->comment %></TD> + <TD BGCOLOR="#ffffff"><% $part_pkg->comment |h %></TD> </TR> <TR> @@ -50,14 +82,14 @@ % if ( $cust_pkg->setup && ! $cust_pkg->start_date ) { <& .row_display, cust_pkg=>$cust_pkg, column=>'start_date', label=>'Start' &> % } else { - <& .row_edit, cust_pkg=>$cust_pkg, column=>'start_date', label=>'Start' &> + <& .row_edit, cust_pkg=>$cust_pkg, column=>'start_date', label=>'Start', if_primary=>1 &> % } - <& .row_edit, cust_pkg=>$cust_pkg, column=>'setup', label=>'Setup' &> + <& .row_edit, cust_pkg=>$cust_pkg, column=>'setup', label=>'Setup', if_primary=>1 &> <& .row_edit, cust_pkg=>$cust_pkg, column=>'last_bill', label=>$last_bill_or_renewed &> <& .row_edit, cust_pkg=>$cust_pkg, column=>'bill', label=>$next_bill_or_prepaid_until &> %#if ( $cust_pkg->contract_end or $part_pkg->option('contract_end_months',1) ) { - <& .row_edit, cust_pkg=>$cust_pkg, column=>'contract_end',label=>'Contract end' &> + <& .row_edit, cust_pkg=>$cust_pkg, column=>'contract_end',label=>'Contract end', if_primary=>1 &> %#} <& .row_display, cust_pkg=>$cust_pkg, column=>'adjourn', label=>'Adjournment', note=>'(will <b>suspend</b> this package when the date is reached)' &> <& .row_display, cust_pkg=>$cust_pkg, column=>'susp', label=>'Suspension' &> @@ -73,10 +105,17 @@ $column $label $note => '' + $if_primary => 0 </%args> % my $value = $cust_pkg->get($column); % $value = $value ? time2str($format, $value) : ""; - +% +% # if_primary for the dates that can't be edited on supplemental packages +% if ($if_primary and $cust_pkg->main_pkgnum) { + <INPUT TYPE="hidden" ID="<%$column%>_text" VALUE="<% $cust_pkg->get($column) %>"> + <SCRIPT>submit_fields.push('<%$column%>');</SCRIPT> + <& .row_display, %ARGS &> +% } else { <TR> <TD ALIGN="right"><% $label %> date</TD> <TD> @@ -104,8 +143,11 @@ button: "<% $column %>_button", align: "BR" }); - </SCRIPT> + submit_fields.push('<%$column%>'); + + </SCRIPT> +% } </%def> <%def .row_display> @@ -114,6 +156,7 @@ $column $label $note => '' + $is_primary => 0 #ignored </%args> % if ( $cust_pkg->get($column) ) { <TR> @@ -130,7 +173,7 @@ </TABLE> <BR> -<INPUT TYPE="submit" VALUE="<% mt('Apply changes') |h %>"> +<INPUT TYPE="button" VALUE="<% mt('Apply changes') |h %>" onclick="confirm_changes()"> </FORM> <% include('/elements/footer.html') %> @@ -160,38 +203,6 @@ if ( $cgi->param('error') ) { my @errors = (); my %errors = map { $_=>1 } split(',', $cgi->param('error')); $cgi->param('error', ''); - - if ( $errors{'_bill_areyousure'} ) { - if ( $cgi->param('bill') =~ /^([\s\d\/\:\-\(\w\)]*)$/ ) { - my $bill = $1; - push @errors, - "You are attempting to set the next bill date to $bill, which is - in the past. This will charge the customer for the interval - from $bill until now. Are you sure you want to do this? ". - '<INPUT TYPE="checkbox" NAME="bill_areyousure" VALUE="1">'; - } - } - - if ( $errors{'_setup_areyousure'} ) { - push @errors, - "You are attempting to remove the setup date. This will re-charge the - customer for the setup fee. Are you sure you want to do this? ". - '<INPUT TYPE="checkbox" NAME="setup_areyousure" VALUE="1">'; - } - - if ( $errors{'_setupadd_areyousure'} ) { - push @errors, - "You are attempting to add a setup date. This will prevent charging the - customer for the setup fee. Are you sure you want to do this? ". - '<INPUT TYPE="checkbox" NAME="setupadd_areyousure" VALUE="1">'; - } - - if ( $errors{'_start'} ) { - push @errors, - "You are attempting to add a start date to a package that has already - started billing."; - } - $error = join('<BR><BR>', @errors ); } diff --git a/httemplate/edit/bulk-part_pkg.html b/httemplate/edit/bulk-part_pkg.html new file mode 100644 index 000000000..a1c6f0c9b --- /dev/null +++ b/httemplate/edit/bulk-part_pkg.html @@ -0,0 +1,74 @@ +<& /elements/header.html, 'Edit package report classes' &> +%# change that title if we add any other editing controls + +%# this should be centralized somewhere +<STYLE TYPE="text/css"> +.row0 { background-color: #eeeeee; } +.row1 { background-color: #ffffff; } +</STYLE> + +<FORM ACTION="process/bulk-part_pkg.html" METHOD="POST"> +<DIV> +The following packages will be changed:<BR> +% foreach my $pkgpart (sort keys(%part_pkg)) { +<INPUT TYPE="hidden" NAME="pkgpart" VALUE="<% $pkgpart %>"> +<% $part_pkg{$pkgpart}->pkg_comment |h %><BR> +% } +</DIV> +<BR> +<& /elements/table-grid.html &>\ +<& /elements/tr-justtitle.html, value => mt('Report classes') &> +% my $row = 0; +% foreach my $num (sort keys %report_class) { + <TR CLASS="row<%$row % 2%>"> + <TD> +% if ( defined $initial_state{$num} ) { + <& /elements/checkbox.html, + field => 'report_option_'.$num, + value => 1, + curr_value => $initial_state{$num} + &> +% } else { +% # needs to be a tristate so that you can say "don't change it" + <& /elements/checkbox-tristate.html, field => 'report_option_'.$num &> +% } + </TD> + <TD><% $report_class{$num}->name %></TD> + </TR> +% $row++; +% } +</TABLE> +<BR> +<INPUT TYPE="submit"> +</FORM> +<& /elements/footer.html &> +<%init> +die "access denied" unless $FS::CurrentUser::CurrentUser->access_right('Bulk edit package definitions'); +my @pkgparts = $cgi->param('pkgpart') + or die "no package definitions selected"; + +my %part_pkg = map { $_ => FS::part_pkg->by_key($_) } @pkgparts; +my %part_pkg_option = map { $_ => { $part_pkg{$_}->options } } @pkgparts; +my %report_class = map { $_->num => $_ } + qsearch('part_pkg_report_option', { disabled => '' }); + +my %initial_state; +foreach my $num (keys %report_class) { + my $yes = 0; + my $no = 0; + foreach my $option (values %part_pkg_option) { + if ( $option->{"report_option_$num"} ) { + $yes = 1; + } else { + $no = 1; + } + } + if ( $yes and $no ) { + $initial_state{$num} = undef; + } elsif ( $yes ) { + $initial_state{$num} = 1; + } elsif ( $no ) { + $initial_state{$num} = 0; + } # else, uh, you didn't provide any pkgparts +} +</%init> diff --git a/httemplate/edit/credit-cust_bill_pkg.html b/httemplate/edit/credit-cust_bill_pkg.html index 3d1cf2438..a5ecb69e3 100644 --- a/httemplate/edit/credit-cust_bill_pkg.html +++ b/httemplate/edit/credit-cust_bill_pkg.html @@ -70,10 +70,11 @@ <TH ALIGN="right" COLSPAN=2>Total credit amount: </TD> <TH ALIGN="right" ID="total_td"><% $money_char %><% sprintf('%.2f', 0) %></TD> </TR> -<INPUT TYPE="hidden" NAME="amount" ID="total_el" VALUE="0.00"> </table> +<INPUT TYPE="hidden" NAME="amount" ID="total_el" VALUE="0.00"> + <table> <& /elements/tr-select-reason.html, @@ -244,7 +245,7 @@ function calc_total(what) { <%init> my $curuser = $FS::CurrentUser::CurrentUser; -die "access denied" unless $curuser->access_right('Post credit'); +die "access denied" unless $curuser->access_right('Credit line items'); #a tiny bit of false laziness w/search/cust_bill_pkg.cgi, but we're pretty # specialized and a piece of UI, not a report diff --git a/httemplate/edit/cust_location.cgi b/httemplate/edit/cust_location.cgi index 80b27c2b3..b90ba66b8 100755 --- a/httemplate/edit/cust_location.cgi +++ b/httemplate/edit/cust_location.cgi @@ -7,20 +7,32 @@ ACTION="<% $p %>edit/process/cust_location.cgi" METHOD=POST> <INPUT TYPE="hidden" NAME="locationnum" VALUE="<% $locationnum %>"> <% ntable('#cccccc') %> -<% include('/elements/location.html', - 'object' => $cust_location, - 'no_asterisks' => 1, - ) %> +<& /elements/location.html, + 'object' => $cust_location, + 'no_asterisks' => 1, + # these are service locations, so they need all this stuff + 'enable_coords' => 1, + 'enable_district' => 1, + 'enable_censustract' => 1, +&> +<& /elements/standardize_locations.html, + 'form' => 'EditLocationForm', + 'callback' => 'document.EditLocationForm.submit();', +&> </TABLE> <BR> <SCRIPT TYPE="text/javascript"> -function areyousure() { - return confirm('Modify this service location?'); +function go() { +% if ( FS::Conf->new->config('address_standardize_method') ) { + standardize_locations(); +% } else { + confirm('Modify this service location?') && + document.EditLocationForm.submit(); +% } } </SCRIPT> -<INPUT TYPE="submit" VALUE="Submit" onclick="return areyousure()"> - +<INPUT TYPE="button" NAME="submitButton" VALUE="Submit" onclick="go()"> </FORM> </BODY> </HTML> diff --git a/httemplate/edit/cust_main.cgi b/httemplate/edit/cust_main.cgi index be00213e2..2908848c6 100755 --- a/httemplate/edit/cust_main.cgi +++ b/httemplate/edit/cust_main.cgi @@ -48,7 +48,7 @@ <TD STYLE="width:650px"> %#; padding-right:2px; vertical-align:top"> <FONT CLASS="fsinnerbox-title"><% mt('Billing address') |h %></FONT> - <TABLE CLASS="fsinnerbox"> + <TABLE CLASS="fsinnerbox" WIDTH="100%"> <& cust_main/before_bill_location.html, $cust_main &> <& /elements/location.html, object => $cust_main->bill_location, @@ -62,7 +62,6 @@ <TR><TD STYLE="height:40px"></TD></TR> <TR> <TD STYLE="width:650px"> -%#; padding-left:2px; vertical-align:top"> <FONT CLASS="fsinnerbox-title"><% mt('Service address') |h %></FONT> <INPUT TYPE="checkbox" NAME="same" @@ -72,19 +71,17 @@ VALUE="Y" <% $has_ship_address ? '' : 'CHECKED' %> ><% mt('same as billing address') |h %> - <TABLE CLASS="fsinnerbox" ID="table_ship_location"> - <& /elements/location.html, - object => $cust_main->ship_location, - prefix => 'ship_', - enable_censustract => 1, - enable_district => 1, - enable_coords => 1, - &> - </TABLE> - <TABLE CLASS="fsinnerbox" ID="table_ship_location_blank" - STYLE="display:none"> - <TR><TD></TD></TR> - </TABLE> + <DIV CLASS="fsinnerbox"> + <TABLE ID="table_ship_location" WIDTH="100%"> + <& /elements/location.html, + object => $cust_main->ship_location, + prefix => 'ship_', + enable_censustract => 1, + enable_district => 1, + enable_coords => 1, + &> + </TABLE> + </DIV> </TD> </TR></TABLE> @@ -94,19 +91,14 @@ function samechanged(what) { %# document.getElementById('table_ship_location').style.visibility = %# what.checked ? 'hidden' : 'visible'; var t1 = document.getElementById('table_ship_location'); - var t2 = document.getElementById('table_ship_location_blank'); if ( what.checked ) { - t2.style.width = t1.clientWidth + 'px'; - t2.style.height = t1.clientHeight + 'px'; - t1.style.display = 'none'; - t2.style.display = ''; + t1.style.visibility = 'hidden'; } else { - t2.style.display = 'none'; - t1.style.display = ''; + t1.style.visibility = 'visible' } } -samechanged(document.getElementById('same')); +//samechanged(document.getElementById('same')); </SCRIPT> <BR> @@ -285,7 +277,8 @@ if ( $cgi->param('error') ) { my( $query ) = $cgi->keywords; $query =~ /^(\d+)$/; $custnum=$1; - $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } ); + $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } ) + or die "custnum $custnum not found"; if ( $cust_main->dbdef_table->column('paycvv') && length($cust_main->paycvv) ) { my $paycvv = $cust_main->paycvv; diff --git a/httemplate/edit/cust_main/billing.html b/httemplate/edit/cust_main/billing.html index 2925ca87c..5a66f0a60 100644 --- a/httemplate/edit/cust_main/billing.html +++ b/httemplate/edit/cust_main/billing.html @@ -444,10 +444,11 @@ <TR><TD> </TD></TR> +% my $curuser = $FS::CurrentUser::CurrentUser; % my @exempt_groups = grep /\S/, $conf->config('tax-cust_exempt-groups'); - % if ( $conf->exists('cust_class-tax_exempt') % || $conf->exists('tax-cust_exempt-groups-require_individual_nums') +% || ! $curuser->access_right('Edit customer tax exemptions') % ) % { @@ -461,14 +462,16 @@ % } -% foreach my $exempt_group ( @exempt_groups ) { -% my $cust_main_exemption = $cust_main->tax_exemption($exempt_group); -% #escape $exempt_group for NAME etc. -% my $checked = ($cust_main_exemption || $cgi->param("tax_$exempt_group")); - <TR> - <TD> <INPUT TYPE="checkbox" NAME="tax_<% $exempt_group %>" ID="tax_<% $exempt_group %>" VALUE="Y" <% $checked ? 'CHECKED' : '' %> onChange="tax_changed(this)"> Tax Exempt (<% $exempt_group %> taxes)</TD> - <TD> - Exemption number <INPUT TYPE="text" NAME="tax_<% $exempt_group %>_num" ID="tax_<% $exempt_group %>_num" VALUE="<% $cgi->param("tax_$exempt_group".'_num') || ( $cust_main_exemption ? $cust_main_exemption->exempt_number : '' ) |h %>" <% $checked ? '' : 'DISABLED' %>></TD> - </TR> +% if ( $curuser->access_right('Edit customer tax exemptions') ) { +% foreach my $exempt_group ( @exempt_groups ) { +% my $cust_main_exemption = $cust_main->tax_exemption($exempt_group); +% #escape $exempt_group for NAME etc. +% my $checked = ($cust_main_exemption || $cgi->param("tax_$exempt_group")); + <TR> + <TD> <INPUT TYPE="checkbox" NAME="tax_<% $exempt_group %>" ID="tax_<% $exempt_group %>" VALUE="Y" <% $checked ? 'CHECKED' : '' %> onChange="tax_changed(this)"> Tax Exempt (<% $exempt_group %> taxes)</TD> + <TD> - Exemption number <INPUT TYPE="text" NAME="tax_<% $exempt_group %>_num" ID="tax_<% $exempt_group %>_num" VALUE="<% $cgi->param("tax_$exempt_group".'_num') || ( $cust_main_exemption ? $cust_main_exemption->exempt_number : '' ) |h %>" <% $checked ? '' : 'DISABLED' %>></TD> + </TR> +% } % } % unless ( $conf->exists('emailinvoiceonly') ) { @@ -518,7 +521,13 @@ <% $conf->exists('cust_main-require_invoicing_list_email', $agentnum) ? $r : '' %>Email address(es) </TD> - <TD WIDTH="408"><INPUT TYPE="text" NAME="invoicing_list" VALUE="<% join(', ', grep { $_ !~ /^(POST|FAX)$/ } @invoicing_list ) %>"></TD> + <TD WIDTH="408"><INPUT TYPE="text" NAME="invoicing_list" VALUE="<% join(', ', grep { $_ !~ /^(POST|FAX)$/ } @invoicing_list ) %>"> + <INPUT TYPE="checkbox" NAME="message_noemail" VALUE="Y" <% + ( $cust_main->message_noemail eq 'Y' ) + ? 'CHECKED' + : '' + %>> <% emt('Do not send notices') %> + </TD> </TR> % } diff --git a/httemplate/edit/cust_main/bottomfixup.js b/httemplate/edit/cust_main/bottomfixup.js index 1cfa52d8f..0de6d9dab 100644 --- a/httemplate/edit/cust_main/bottomfixup.js +++ b/httemplate/edit/cust_main/bottomfixup.js @@ -70,8 +70,8 @@ function copy_payby_fields() { <& /elements/standardize_locations.js, 'callback' => 'submit_continue();', - 'main_prefix' => 'bill_', - 'no_company' => 1, + 'billship' => 1, + 'with_census' => 1, # no with_firm, apparently &> function copyelement(from, to) { diff --git a/httemplate/edit/cust_main/choose_tax_location.html b/httemplate/edit/cust_main/choose_tax_location.html deleted file mode 100644 index ac475c54b..000000000 --- a/httemplate/edit/cust_main/choose_tax_location.html +++ /dev/null @@ -1,87 +0,0 @@ -<FORM NAME="choosegeocodeform"> -<CENTER><BR><B>Choose tax location</B><BR><BR> -<P>the geocode is:<% $header %></P> -<P STYLE="<% $style %>"><% $header %></P> - -<SELECT NAME='geocodes' ID='geocodes' STYLE="<% $style %>"> -% foreach my $location (@cust_tax_location) { -% my %value = ( zip => $zip5, -% map { $_ => $location->$_ } -% qw ( city state geocode ) -% ); -% map { $value{$_} = $location{$_} } qw ( city state ) -% if $location{country} eq 'CA'; -% -% my $value = encode_entities(objToJson({ %value }) -% ); -% my $content = ''; -% $content .= $location->$_. ' ' x ( $max{$_} - length($location->$_) ) -% foreach qw( city county state ); -% $content .= $location->cityflag eq 'I' ? 'Y' : 'N' ; -% my $selected = '' ; -% if ($geocode && $location->geocode eq $geocode) { -% $selected = 'SELECTED'; -% } - <OPTION VALUE="<% $value %>" STYLE="<% $style %>" <% $selected %>><% $content %> -% } -</SELECT><BR><BR> - -<TABLE><TR> - <TD> <BUTTON TYPE="button" onClick="set_geocode(document.getElementById('geocodes'));"><IMG SRC="<%$p%>images/tick.png" ALT=""> Set location </BUTTON></TD> - <TD><BUTTON TYPE="button" onClick="document.CustomerForm.submitButton.disabled=false; parent.cClick();"><IMG SRC="<%$p%>images/cross.png" ALT=""> Cancel submission </BUTTON></TD> -</TR> -</TABLE> - -</CENTER> -</FORM> -<%init> - -my $conf = new FS::Conf; - -my %location = (); - -($location{data_vendor}) = $cgi->param('data_vendor') =~ /^([-\w]+)$/; -($location{city}) = $cgi->param('city') =~ /^([\w ]+)$/; -($location{state}) = $cgi->param('state') =~ /^(\w+)$/; -($location{zip}) = $cgi->param('zip') =~ /^([-\w ]+)$/; -($location{country}) = $cgi->param('country') =~ /^([\w ]+)$/; - -my($geocode) = $cgi->param('geocode') =~ /^([\w]+)$/; - -my($zip5, $zip4) = split('-', $location{zip}); - -#only support US & CA -my $hashref = { 'data_vendor' => $location{data_vendor} }; -$hashref->{zip} = $location{country} eq 'CA' ? substr($zip5,0,1) : $zip5, - -my @keys = keys(%$hashref); -my @cust_tax_location = (); -until ( @cust_tax_location ) { - @cust_tax_location = qsearch({ table => 'cust_tax_location', - hashref => $hashref, - order_by => 'LIMIT 50', - }); - last unless scalar(@keys); - delete $hashref->{ shift @keys }; -} - -my %max = ( city => 4, county => 6, state => 5); -foreach my $location (@cust_tax_location) { - foreach ( qw( city county state ) ) { - my $length = length($location->$_); - $max{$_} = ($length > $max{$_}) ? $length : $max{$_}; - } -} -foreach ( qw( city county state ) ) { - $max{$_} = $location{$_} if $location{$_} > $max{$_}; - $max{$_}++; -} - -my $header = ' '; -$header .= $_. ' ' x ( $max{lc($_)} - length($_) ) - foreach qw( City County State ); -$header .= "In city?"; - -my $style = "font-family:monospace;"; - -</%init> diff --git a/httemplate/edit/cust_main/top_misc.html b/httemplate/edit/cust_main/top_misc.html index cfed8e4f6..b7e86ba78 100644 --- a/httemplate/edit/cust_main/top_misc.html +++ b/httemplate/edit/cust_main/top_misc.html @@ -32,6 +32,44 @@ document.getElementById('contacts_div').style.display = 'none'; } } + + var ship_locked_agents = <% encode_json(\%ship_locked_agents) %>; + var ship_fields = ['address1', 'city', 'state', 'zip', 'country', + 'latitude', 'longitude', 'district']; + function agent_changed(what) { + var agentnum = what.value; + var f = what.form; + if ( ship_locked_agents[agentnum] ) { +% # For this agent, the service location (except address2) +% # should be locked to the agent's location. +% # Set the ship_ fields to those values (just for display) and +% # then disable them. + for(var x in ship_locked_agents[agentnum]) { + f['ship_'+x].value = ship_locked_agents[agentnum][x]; + f['ship_'+x].disabled = true; + } + f['same'].checked = false; + f['same'].disabled = true; + } else { +% # Unlock the ship_ location fields. If they were previously +% # disabled, then they contain some agent's address, which is +% # no longer meaningful. So set them back to the customer's +% # current location. + for(var i=0; i<ship_fields.length; i++) { + x = ship_fields[i]; + if ( f['ship_'+x].disabled ) { + f['ship_'+x].value = f['old_ship_'+x].value; + } + f['ship_'+x].disabled = false; + } + f['same'].disabled = false; + } + samechanged(f['same']); + } + window.onload = function() { + agent_changed(document.getElementById('agentnum')); + } + </SCRIPT> % foreach my $field ($cust_main->virtual_fields) { @@ -51,12 +89,13 @@ % $cust_main->agentnum($agentnum); <INPUT TYPE="hidden" NAME="lock_agentnum" VALUE="<% $agentnum %>"> - <INPUT TYPE="hidden" NAME="agentnum" VALUE="<% $agentnum %>"> + <INPUT TYPE="hidden" NAME="agentnum" ID="agentnum" + VALUE="<% $agentnum %>"> <TR> <TD ALIGN="right"><% mt('Agent') |h %></TD> <TD CLASS="fsdisabled"><% $cust_main->agent->agent |h %></TD> </TR> - + % } else { <& /elements/tr-select-agent.html, @@ -65,6 +104,7 @@ 'empty_label' => emt('Select agent'), 'disable_empty' => ( $cust_main->agentnum ? 1 : 0 ), 'viewall_right' => emt('None'), + 'onchange' => 'agent_changed(this)', &> % } @@ -201,4 +241,17 @@ my $curuser = $FS::CurrentUser::CurrentUser; my $r = qq!<font color="#ff0000">*</font> !; +# which agents lock the service address, if any +my %ship_locked_agents; +foreach (qsearch('agent',{})) { + my $agentnum = $_->agentnum; + next unless $conf->exists('agent-ship_address', $_->agentnum); + my $cust_main = $_->agent_cust_main or next; + my $agent_ship_location = $cust_main->ship_location; + $ship_locked_agents{$agentnum} = +{ + map { $_ => $agent_ship_location->$_ } + qw(address1 city state zip country latitude longitude district) + }; +} + </%init> diff --git a/httemplate/edit/cust_pkg.cgi b/httemplate/edit/cust_pkg.cgi index dd1ed335f..88e925460 100755 --- a/httemplate/edit/cust_pkg.cgi +++ b/httemplate/edit/cust_pkg.cgi @@ -7,7 +7,6 @@ <INPUT TYPE="hidden" NAME="custnum" VALUE="<% $custnum %>"> %#current packages -%my @cust_pkg = qsearch('cust_pkg', { 'custnum' => $custnum, 'cancel' => '' } ); %if (@cust_pkg) { Current packages - select to remove (services are moved to a new package below) @@ -18,13 +17,7 @@ </TR> <BR><BR> % -% -% foreach ( sort { $all_pkg{ $a->getfield('pkgpart') } -% cmp $all_pkg{ $b->getfield('pkgpart') } -% } -% @cust_pkg -% ) -% { +% foreach ( @main_pkgs ) { % my($pkgnum,$pkgpart)=( $_->getfield('pkgnum'), $_->getfield('pkgpart') ); % my $checked = $remove_pkg{$pkgnum} ? ' CHECKED' : ''; % @@ -36,6 +29,13 @@ <TD ALIGN="right"><% $pkgnum %>:</TD> <TD><% $all_pkg{$pkgpart} %> - <% $all_comment{$pkgpart} %></TD> </TR> +% foreach my $supp_pkg ( @{ $supp_pkgs_of{$pkgnum} } ) { + <TR> + <TD></TD> + <TD></TD> + <TD>+ <% $all_pkg{$supp_pkg->pkgpart} %> - <% $all_comment{$supp_pkg->pkgpart} %></TD> + </TR> +% } % } @@ -147,4 +147,24 @@ if ( $cgi->param('error') ) { my $p1 = popurl(1); +my @cust_pkg = qsearch('cust_pkg', { 'custnum' => $custnum, 'cancel' => '' } ); +my @main_pkgs; +my %supp_pkgs_of; # main pkgnum => arrayref of cust_pkgs + + +foreach my $cust_pkg + ( sort { $all_pkg{ $a->pkgpart } cmp $all_pkg{ $b->getfield('pkgpart') } } + @cust_pkg + ) + # XXX does not properly handle recursive supplemental links +{ + if ( my $main_pkgnum = $cust_pkg->main_pkgnum ) { + $supp_pkgs_of{$main_pkgnum} ||= []; + push @{ $supp_pkgs_of{$main_pkgnum} }, $cust_pkg; + } else { + push @main_pkgs, $cust_pkg; + $supp_pkgs_of{$cust_pkg->pkgnum} ||= []; + } +} + </%init> diff --git a/httemplate/edit/cust_pkg_detail.html b/httemplate/edit/cust_pkg_detail.html index 009ed5c6e..5e107066d 100644 --- a/httemplate/edit/cust_pkg_detail.html +++ b/httemplate/edit/cust_pkg_detail.html @@ -28,7 +28,7 @@ <TR> <TD ALIGN="right">Comment</TD> - <TD BGCOLOR="#ffffff"><% $part_pkg->comment %></TD> + <TD BGCOLOR="#ffffff"><% $part_pkg->comment |h %></TD> </TR> <TR> diff --git a/httemplate/edit/cust_pkg_quantity.html b/httemplate/edit/cust_pkg_quantity.html new file mode 100755 index 000000000..ec47ed6cb --- /dev/null +++ b/httemplate/edit/cust_pkg_quantity.html @@ -0,0 +1,49 @@ +<& /elements/header-popup.html, "Change Quantity" &> +<& /elements/error.html &> + +<FORM ACTION="<% $p %>edit/process/cust_pkg_quantity.html" METHOD=POST> +<INPUT TYPE="hidden" NAME="pkgnum" VALUE="<% $pkgnum %>"> +<& /elements/table-grid.html, 'bgcolor' => '#cccccc', 'cellpadding' => 2 &> + + <TR> + <TH ALIGN="right">Current package </TH> + <TD CLASS="grid"> + <% $curuser->option('show_pkgnum') ? $cust_pkg->pkgnum.': ' : '' %><B><% $part_pkg->pkg |h %></B> - <% $part_pkg->comment |h %> + </TD> + </TR> + +<& /elements/tr-input-text.html, + 'field' => 'quantity', + 'curr_value' => $cust_pkg->quantity, + 'label' => emt('Quantity') +&> + +</TABLE> + +<BR> +<INPUT NAME="submit" TYPE="submit" VALUE="Change"> + +</FORM> +</BODY> +</HTML> + +<%init> + +#some false laziness w/misc/change_pkg.cgi + +my $conf = new FS::Conf; + +my $curuser = $FS::CurrentUser::CurrentUser; + +die "access denied" + unless $curuser->access_right('Change customer package'); + +my $pkgnum = scalar($cgi->param('pkgnum')); +$pkgnum =~ /^(\d+)$/ or die "illegal pkgnum $pkgnum"; +$pkgnum = $1; + +my $cust_pkg = FS::cust_pkg->by_key($pkgnum) or die "unknown pkgnum $pkgnum"; + +my $part_pkg = $cust_pkg->part_pkg; + +</%init> diff --git a/httemplate/edit/cust_refund.cgi b/httemplate/edit/cust_refund.cgi index 656d5ebb5..df42e63ae 100755 --- a/httemplate/edit/cust_refund.cgi +++ b/httemplate/edit/cust_refund.cgi @@ -59,12 +59,12 @@ </TD> </TR> % } - +% if ( $cust_pay->processor ) { <TR> <TD ALIGN="right">Processor</TD> <TD BGCOLOR="#ffffff"><% $cust_pay->processor %></TD> </TR> -% if ( length($auth) ) { +% if ( length($cust_pay->auth) ) { <TR> <TD ALIGN="right">Authorization</TD> @@ -78,10 +78,10 @@ <TD BGCOLOR="#ffffff"><% $cust_pay->order_number %></TD> </TR> % } -% } #if $cust_pay +% } # if ($cust_pay->processor) </TABLE> -% } +% } #if $cust_pay <BR>Refund diff --git a/httemplate/edit/elements/edit.html b/httemplate/edit/elements/edit.html index a24f23805..3e6bd5bec 100644 --- a/httemplate/edit/elements/edit.html +++ b/httemplate/edit/elements/edit.html @@ -329,6 +329,7 @@ Example: % qw( country ), #select-country % qw( width height ), #htmlarea % qw( alt_format ), #select-cust_location +% qw( classnum ), # select-inventory_item % ; % % #select-table diff --git a/httemplate/edit/elements/part_svc_column.html b/httemplate/edit/elements/part_svc_column.html new file mode 100644 index 000000000..d03c49d2f --- /dev/null +++ b/httemplate/edit/elements/part_svc_column.html @@ -0,0 +1,303 @@ +<%doc> +To be called from part_svc.cgi. +<& elements/part_svc_column.html, + 'svc_acct', + # options... + 'part_svc' => $part_svc, # the existing part_svc to edit + 'clone' => 0, # or a svcpart to clone from +&> + +</%doc> +<%once> +# the semantics of this could be better + +# all of these conditions are when NOT to allow that flag choice +# don't allow the 'inventory' flags (M, A) to be chosen for +# fields that aren't free-text +my $inv_sub = sub { $_[0]->{disable_inventory} || $_[0]->{type} ne 'text' }; +tie my %flag, 'Tie::IxHash', + '' => { 'desc' => 'No default', 'condition' => sub { 0 } }, + 'D' => { 'desc' => 'Default', + 'condition' => + sub { $_[0]->{disable_default } } + }, + 'F' => { 'desc' => 'Fixed (unchangeable)', + 'condition' => + sub { $_[0]->{disable_fixed} }, + }, + 'S' => { 'desc' => 'Selectable Choice', + 'condition' => + sub { $_[0]->{disable_select} }, + }, + 'M' => { 'desc' => 'Manual selection from inventory', + 'condition' => $inv_sub, + }, + 'A' => { 'desc' => 'Automatically fill in from inventory', + 'condition' => $inv_sub, + }, + 'H' => { 'desc' => 'Select from hardware class', + 'condition' => sub { $_[0]->{type} ne 'select-hardware' }, + }, + 'X' => { 'desc' => 'Excluded', + 'condition' => sub { 1 }, # obsolete + }, +; + +# the semantics of this could be much better +sub flag_condition { + my $f = shift; + not &{ $flag{$f}->{'condition'} }(@_); +} + +my %communigate_fields = ( + 'svc_acct' => { map { $_=>1 } + qw( file_quota file_maxnum file_maxsize + password_selfchange password_recover + ), + grep /^cgp_/, fields('svc_acct') + }, + 'svc_domain' => { map { $_=>1 } + qw( max_accounts trailer parent_svcnum ), + grep /^(cgp|acct_def)_/, fields('svc_domain') + }, +); +</%once> +<INPUT TYPE="hidden" NAME="svcdb" VALUE="<% $svcdb %>"> +<BR><BR> +<& /elements/table.html &> + <TR><TH COLSPAN=<% $columns %>>Exports</TH></TR> + <TR> +% # exports +% foreach my $part_export (@part_export) { + <TD> + <INPUT TYPE="checkbox" \ + NAME="exportnum<% $part_export->exportnum %>" \ + VALUE=1 \ + <% $has_export_svc{$part_export->exportnum} ? 'CHECKED' : '' %>> + <% $part_export->label_html %> + </TD> +% $count++; +% if ( $count % $columns == 0 ) { + </TR> + <TR> +% } +% } + </TR> +</TABLE><BR><BR> +For the selected table, you can give fields default or fixed (unchangeable) +values, or select an inventory class to manually or automatically fill in +that field. +<& /elements/table-grid.html, cellpadding => 4 &> + <TR> + <TH BGCOLOR="#cccccc">Field</TH> + <TH BGCOLOR="#cccccc">Label</TH> + <TH BGCOLOR="#cccccc" COLSPAN=2>Modifier</TH> + </TR> +% $part_svc->set('svcpart' => $opt{'clone'}) if $opt{'clone'}; # for now +% my $i = 0; +% foreach my $field (@fields) { +% my $def = shift @defs; +% my $part_svc_column = $part_svc->part_svc_column($field); +% my $flag = $part_svc_column->columnflag; +% my $formatter = $def->{'format'} || sub { shift }; +% my $value = &{$formatter}($part_svc_column->columnvalue); + <TR CLASS="row<%$i%>"> + <TD ROWSPAN=2 CLASS="grid" ALIGN="right"> + <% $def->{'label'} || $field %> + </TD> + <TD ROWSPAN=2 CLASS="grid"> + <INPUT NAME="<% $svcdb %>__<% $field %>_label" + STYLE="text-align: right" + VALUE="<% $part_svc_column->columnlabel || $def->{'label'} |h %>"> + </TD> + + <TD ROWSPAN=1 CLASS="grid"> +% # flag selection +% if ( $def->{'type'} eq 'disabled' ) { +% $flag = ''; + No default +% } else { +% my $name = $svcdb.'__'.$field.'_flag'; + <SELECT NAME="<%$name%>" + ID="<%$name%>" + STYLE="width:100%" + onchange="flag_changed(this)"> +% foreach my $f (keys %flag) { +% if ( flag_condition($f, $def, $svcdb, $field) ) { + <OPTION VALUE="<%$f%>"<% $flag eq $f ? ' SELECTED' : ''%>> + <% $flag{$f}->{desc} %> + </OPTION> +% } +% } + </SELECT> +% } # if $def->{'type'} eq 'disabled' + </TD> + <TD CLASS="grid"> +% # value entry/selection +% my $name = $svcdb.'__'.$field; +% # These are all MANDATORY SELECT types. Regardless of the flag value, +% # there will never be a text input (either in svc_* or in part_svc) for +% # these fields. +% if ( $def->{'type'} eq 'checkbox' ) { + <& /elements/checkbox.html, + 'field' => $name, + 'curr_value' => $value, + 'value' => 'Y' &> +% +% } elsif ( $def->{'type'} eq 'select' ) { +% +% if ( $def->{'select_table'} ) { + <& /elements/select-table.html, + 'field' => $name, + 'id' => $name.'_select', + 'table' => $def->{'select_table'}, + 'name_col' => $def->{'select_label'}, + 'value_col' => $def->{'select_key'}, + 'order_by' => dbdef->table($def->{'select_table'})->primary_key, + 'multiple' => $def->{'multiple'}, + 'disable_empty' => 1, + 'curr_value' => $value, + &> +% } else { +% my (@options, %labels); +% if ( $def->{'select_list'} ) { +% @options = @{ $def->{'select_list'} }; +% @labels{@options} = @options; +% } elsif ( $def->{'select_hash'} ) { +% if ( ref($def->{'select_hash'}) eq 'ARRAY' ) { +% tie my %hash, 'Tie::IxHash', @{ $def->{'select_hash'} }; +% $def->{'select_hash'} = \%hash; +% } +% @options = keys( %{ $def->{'select_hash'} } ); +% %labels = %{ $def->{'select_hash'} }; +% } + <& /elements/select.html, + 'field' => $name, + 'id' => $name.'_select', + 'options' => \@options, + 'labels' => \%labels, + 'multiple' => $def->{'multiple'}, + 'curr_value' => $value, + &> +% } +% } elsif ( $def->{'type'} =~ /select-(.*?).html/ ) { + <& '/elements/'.$def->{'type'}, + 'field' => $name, + 'id' => $name.'_select', + 'multiple' => $def->{'multiple'}, + 'curr_value' => $value, + &> +% } elsif ( $def->{'type'} eq 'communigate_pro-accessmodes' ) { + <& /elements/communigate_pro-accessmodes.html, + 'element_name_prefix' => $name.'_', + 'curr_value' => $value, + &> +% } elsif ( $def->{'type'} eq 'textarea' ) { +% # special cases + <TEXTAREA NAME="<%$name%>"><% $value |h %></TEXTAREA> +% } elsif ( $def->{'type'} eq 'disabled' ) { + <INPUT TYPE="hidden" NAME="<%$name%>" VALUE=""> +% } else { +% # the normal case: a text input, and a _select which is an inventory +% # or hardware class + <INPUT TYPE="text" + NAME="<%$name%>" + ID="<%$name%>" + VALUE="<%$value%>"> +% # inventory class selection + <& /elements/select-table.html, + 'field' => $name.'_classnum', + 'id' => $name.'_select', + 'table' => 'inventory_class', + 'name_col' => 'classname', + 'curr_value' => $value, + 'empty_label' => 'Select inventory class', + 'multiple' => 1, + &> +% } + </TD> + </TR> + <TR CLASS="row<%$i%>"> + <TD COLSPAN=2 CLASS="def_info"> +% if ( $def->{def_info} ) { + (<% $def->{def_info} %>) + </TD> + </TR> +% } +% $i = 1-$i; +% } # foreach my $field +% +% # special case: svc_acct password edit ACL +% if ( $svcdb eq 'svc_acct' ) { +% push @fields, 'restrict_edit_password'; + <TR> + <TD COLSPAN=3 ALIGN="right"> + <% emt('Require "Provision" access right to edit password') %> + </TD> + <TD> + <INPUT TYPE="checkbox" NAME="restrict_edit_password" VALUE="Y" \ + <% $part_svc->restrict_edit_password ? 'CHECKED' : '' %>> + </TD> + </TR> +% } +</TABLE> +<& /elements/progress-init.html, + $svcdb, #form name + [ # form fields to send + qw(svc svcpart classnum selfservice_access disabled preserve exportnum), + @fields + ], + 'process/part_svc.cgi', # target + $p.'browse/part_svc.cgi', # redirect landing + $svcdb, #key +&> +% $svcpart = '' if $opt{clone}; +<BR> +<INPUT NAME="submit" + TYPE="button" + VALUE="<% emt($svcpart ? 'Apply changes' : 'Add service') %>" + onclick="fixup_submit('<%$svcdb%>')" +> +<%init> +my $svcdb = shift; +my %opt = @_; +my $columns = 3; +my $count = 0; +my $communigate = 0; +my $conf = FS::Conf->new; + +my $part_svc = $opt{'part_svc'} || FS::part_svc->new; + +my @part_export; +my $export_info = FS::part_export::export_info($svcdb); +foreach (keys %{ $export_info }) { + push @part_export, qsearch('part_export', { exporttype => $_ }); +} +$communigate = scalar(grep {$_->exporttype =~ /^communigate/} @part_export); + +my $svcpart = $opt{'clone'} || $part_svc->svcpart; +my %has_export_svc; +if ( $svcpart ) { + foreach (qsearch('export_svc', { svcpart => $svcpart })) { + $has_export_svc{$_->exportnum} = 1; + } +} + +my @fields; +if ( defined( dbdef->table($svcdb) ) ) { # when is it ever not defined? + @fields = grep { + $_ ne 'svcnum' + and ( $communigate || ! $communigate_fields{$svcdb}->{$_} ) + and ( !FS::part_svc->svc_table_fields($svcdb)->{$_}->{disable_part_svc_column} + || $part_svc->part_svc_column($_)->columnflag ) + } fields($svcdb); +} +if ( $svcdb eq 'svc_acct' + or ( $svcdb eq 'svc_broadband' and $conf->exists('svc_broadband-radius') ) + ) +{ + push @fields, 'usergroup'; +} + +my @defs = map { FS::part_svc->svc_table_fields($svcdb)->{$_} } @fields; +</%init> diff --git a/httemplate/edit/elements/svc_Common.html b/httemplate/edit/elements/svc_Common.html index 0d9d36c07..d46d1cb42 100644 --- a/httemplate/edit/elements/svc_Common.html +++ b/httemplate/edit/elements/svc_Common.html @@ -88,30 +88,13 @@ } elsif ( $flag eq 'A' ) { $f->{'type'} = 'hidden'; } elsif ( $flag eq 'M' ) { + $f->{'type'} = 'select-inventory_item'; $f->{'empty_label'} = 'Select inventory item'; - $f->{'type'} = 'select-table'; - $f->{'table'} = 'inventory_item'; - $f->{'name_col'} = 'item'; - $f->{'value_col'} = 'item'; - $f->{'agent_virt'} = 1; - $f->{'agent_null'} = 1; - $f->{'hashref'} = { - 'classnum'=>$columndef->columnvalue, - #'svcnum' => '', - }; - $f->{'extra_sql'} = 'AND ( svcnum IS NULL '; - $f->{'extra_sql'} .= ' OR svcnum = '. $object->svcnum - if $object->svcnum; - $f->{'extra_sql'} .= ' ) '; + $f->{'extra_sql'} = 'WHERE ( svcnum IS NULL ' . + ($object->svcnum && ' OR svcnum = '.$object->svcnum) . + ')'; + $f->{'classnum'} = $columndef->columnvalue; $f->{'disable_empty'} = $object->svcnum ? 1 : 0; - if ( $f->{'field'} eq 'mac_addr' ) { - $f->{'compare_sub'} = sub { - my($a, $b) = @_; - $a =~ s/[-: ]//g; - $b =~ s/[-: ]//g; - lc($a) eq lc($b); - }; - } } elsif ( $flag eq 'H' ) { $f->{'type'} = 'select-hardware_type'; $f->{'hashref'} = { diff --git a/httemplate/edit/part_export.cgi b/httemplate/edit/part_export.cgi index 4dd253be8..2897cf39d 100644 --- a/httemplate/edit/part_export.cgi +++ b/httemplate/edit/part_export.cgi @@ -2,6 +2,34 @@ <% include('/elements/error.html') %> +<SCRIPT TYPE="text/javascript"> + function svc_machine_changed (what, layer) { + if ( what.checked ) { + var machine = document.getElementById(layer + "_machine"); + var part_export_machine = + document.getElementById(layer + "_part_export_machine"); + if ( what.value == 'Y' ) { + machine.disabled = true; + part_export_machine.disabled = false; + } else if ( what.value == 'N' ) { + machine.disabled = false; + part_export_machine.disabled = true; + } + } + } + + function part_export_machine_changed (what, layer) { + var select_default = document.getElementById(layer + '_default_machine'); + var selected = select_default.value; + select_default.options.length = 0; + var choices = what.value.split("\n"); + for (var i = 0; i < choices.length; i++) { + select_default.options[i] = new Option(choices[i]); + } + select_default.value = selected; + } + +</SCRIPT> <FORM NAME="dummy"> <INPUT TYPE="hidden" NAME="exportnum" VALUE="<% $part_export->exportnum %>"> @@ -58,7 +86,6 @@ my $widget = new HTML::Widgets::SelectLayers( 'form_name' => 'dummy', 'form_action' => 'process/part_export.cgi', 'form_text' => [qw( exportnum exportname )], -# 'form_checkbox' => [qw()], 'html_between' => "</TD></TR></TABLE>\n", 'layer_callback' => sub { my $layer = shift; @@ -87,7 +114,8 @@ my $widget = new HTML::Widgets::SelectLayers( if ( $exports->{$layer}{svc_machine} ) { my( $N_CHK, $Y_CHK) = ( 'CHECKED', '' ); my( $machine_DISABLED, $pem_DISABLED) = ( '', 'DISABLED' ); - my $part_export_machine = ''; + my @part_export_machine; + my $default_machine = ''; if ( $cgi->param('svc_machine') eq 'Y' || $machine eq '_SVC_MACHINE' ) @@ -97,38 +125,43 @@ my $widget = new HTML::Widgets::SelectLayers( $machine_DISABLED = 'DISABLED'; $pem_DISABLED = ''; $machine = ''; - $part_export_machine = - $cgi->param('part_export_machine') - || join "\n", + @part_export_machine = $cgi->param('part_export_machine'); + if (!@part_export_machine) { + @part_export_machine = map $_->machine, grep ! $_->disabled, $part_export->part_export_machine; + } + $default_machine = + $cgi->param('default_machine_name') + || $part_export->default_export_machine; } - my $oc = qq(onChange="${layer}_svc_machine_changed(this)"); + my $oc = qq(onChange="svc_machine_changed(this, '$layer')"); $html .= qq[ <INPUT TYPE="radio" NAME="svc_machine" VALUE="N" $N_CHK $oc> <INPUT TYPE="text" NAME="machine" ID="${layer}_machine" VALUE="$machine" $machine_DISABLED> <BR> <INPUT TYPE="radio" NAME="svc_machine" VALUE="Y" $Y_CHK $oc> - Selected in each customer service from these choices - <TEXTAREA NAME="part_export_machine" ID="${layer}_part_export_machine" $pem_DISABLED>$part_export_machine</TEXTAREA> - - <SCRIPT TYPE="text/javascript"> - function ${layer}_svc_machine_changed (what) { - if ( what.checked ) { - var machine = document.getElementById("${layer}_machine"); - var part_export_machine = document.getElementById("${layer}_part_export_machine"); - if ( what.value == 'Y' ) { - machine.disabled = true; - part_export_machine.disabled = false; - } else if ( what.value == 'N' ) { - machine.disabled = false; - part_export_machine.disabled = true; - } - } - } - </SCRIPT> + <DIV STYLE="display:inline-block; vertical-align: top; text-align: right"> + Selected in each customer service from these choices: + <TEXTAREA STYLE="vertical-align: top" NAME="part_export_machine" + ID="${layer}_part_export_machine" + onchange="part_export_machine_changed(this, '$layer')" + $pem_DISABLED>] . + + join("\n", @part_export_machine) . + + qq[</TEXTAREA> + <BR> + Default: + <SELECT NAME="default_machine_name" ID="${layer}_default_machine"> ]; + foreach (@part_export_machine) { + $_ = encode_entities($_); # oh noes, XSS + my $sel = ($default_machine eq $_) ? ' SELECTED' : ''; + $html .= qq!<OPTION VALUE="$_"$sel>$_</OPTION>\n!; + } + $html .= '</DIV></SELECT>' } else { $html .= qq(<INPUT TYPE="text" NAME="machine" VALUE="$machine">). '<INPUT TYPE="hidden" NAME="svc_machine" VALUE=N">'; diff --git a/httemplate/edit/part_pkg.cgi b/httemplate/edit/part_pkg.cgi index c3f4f88b6..fadde354e 100755 --- a/httemplate/edit/part_pkg.cgi +++ b/httemplate/edit/part_pkg.cgi @@ -28,7 +28,8 @@ 'labels' => { 'pkgpart' => 'Package Definition', - 'pkg' => 'Package (customer-visible)', + 'pkg' => 'Package', + %locale_field_labels, 'comment' => 'Comment (customer-hidden)', 'classnum' => 'Package class', 'addon_classnum' => 'Restrict additional orders to package class', @@ -53,6 +54,7 @@ 'discountnum' => 'Offer discounts for longer terms', 'bill_dst_pkgpart' => 'Include line item(s) from package', 'svc_dst_pkgpart' => 'Include services of package', + 'supp_dst_pkgpart' => 'Include complete package', 'report_option' => 'Report classes', 'fcc_ds0s' => 'Voice-grade equivalents', 'fcc_voip_class' => 'Category', @@ -79,6 +81,7 @@ size => 40, #32 maxlength => 50, }, + #@locale_fields, {field=>'comment', type=>'text', size=>40 }, #32 { field => 'agentnum', type => 'select-agent', @@ -239,6 +242,19 @@ }, { 'type' => 'tablebreak-tr-title', + 'value' => 'Supplemental packages', + 'colspan' => '4', + }, + { 'field' => 'supp_dst_pkgpart', + 'type' => 'select-part_pkg', + 'm2_label' => 'Include complete package', + 'm2m_method' => 'supp_part_pkg_link', + 'm2m_dstcol' => 'dst_pkgpart', + 'm2_error_callback' => + &{$m2_error_callback_maker}('supp'), + }, + + { 'type' => 'tablebreak-tr-title', 'value' => 'Pricing add-ons', 'colspan' => 4, }, @@ -323,6 +339,22 @@ my $agent_clone_extra_sql = my $conf = new FS::Conf; my $taxproducts = $conf->exists('enable_taxproducts'); +my @locales = grep { ! /^en_/i } $conf->config('available-locales'); #should filter from the default locale lang instead of en_ +my %locale_labels = map { + ( $_ => 'Package -- '. FS::Locales->description($_) ) +} @locales; +@locales = + sort { $locale_labels{$a} cmp $locale_labels{$b} } + @locales; + +my $n = 0; +my %locale_field_labels = ( + map { + ( 'pkgpartmsgnum'. $n++. '_pkg' => $locale_labels{$_} ); + } + @locales +); + my $sth = dbh->prepare("SELECT COUNT(*) FROM part_pkg_report_option". " WHERE disabled IS NULL OR disabled = '' ") or die dbh->errstr; @@ -354,6 +386,42 @@ 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 ) = @_; @@ -394,6 +462,16 @@ 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' + ); + }; my $new_hashref_callback = sub { { 'plan' => 'flat' }; }; @@ -459,6 +537,20 @@ 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 { @@ -473,6 +565,8 @@ my $new_callback = sub { $options{'suspend_bill'}=1 if $conf->exists('part_pkg-default_suspend_bill'); + &$splice_locale_fields($fields, '', ''); + }; my $clone_callback = sub { @@ -506,6 +600,16 @@ my $clone_callback = sub { foreach (qw( setup_fee recur_fee disable_line_item_date_ranges )); $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 { diff --git a/httemplate/edit/part_svc.cgi b/httemplate/edit/part_svc.cgi index 007c24629..58c237efd 100755 --- a/httemplate/edit/part_svc.cgi +++ b/httemplate/edit/part_svc.cgi @@ -1,11 +1,111 @@ -<& /elements/header.html, "$action Service Definition", - menubar('View all service definitions' => "${p}browse/part_svc.cgi"), +<& /elements/header.html, "$action Service Definition" &> +<& /elements/menubar.html, + 'View all service definitions' => "${p}browse/part_svc.cgi" #" onLoad=\"visualize()\"" &> <& /elements/init_overlib.html &> -<BR> +<BR><BR> + +<STYLE TYPE="text/css"> +.disabled { + background-color: #dddddd; +} +.hidden { + display: none; +} +.enabled { + background-color: #ffffff; +} +.row0 TD { + background-color: #eeeeee; +} +.row1 TD { + background-color: #ffffff; +} +.def_info { + text-align: center; + padding: 0px; + border-top: none; + font-size: smaller; + font-style: italic; +} +</STYLE> +<SCRIPT TYPE="text/javascript"> +function fixup_submit(layer) { + document.forms[layer].submit.disabled = true; + fixup(document.forms[layer]); + window[layer+'process'].call(); +} + +function flag_changed(obj) { + var newflag = obj.value; + var a = obj.name.match(/(.*)__(.*)_flag/); + var layer = a[1]; + var field = a[2]; + var input = document.getElementById(layer + '__' + field); + // for fields that have both 'input' and 'select', 'select' is 'select from + // inventory class'. + var select = document.getElementById(layer + '__' + field + '_select'); + if (newflag == "" || newflag == "X") { // disable + if ( input ) { + input.disabled = true; + input.className = 'disabled'; + } + if ( select ) { + select.disabled = true; + select.className = 'hidden'; + } + } else if ( newflag == 'D' || newflag == 'F' || newflag == 'S' ) { + if ( input ) { + // enable text box, disable inventory select + input.disabled = false; + input.className = 'enabled'; + if ( select ) { + select.disabled = false; + select.className = 'hidden'; + } + } else if ( select ) { + // enable select + select.disabled = false; + select.className = 'enabled'; + if ( newflag == 'S' || select.getAttribute('should_be_multiple') ) { + select.multiple = true; + } else { + select.multiple = false; + } + } + } else if ( newflag == 'M' || newflag == 'A' || newflag == 'H' ) { + // these all require a class selection + if ( select ) { + select.disabled = false; + select.className = 'enabled'; + if ( input ) { + input.disabled = false; + input.className = 'hidden'; + } + } + } +} + +window.onload = function() { + var selects = document.getElementsByTagName('SELECT'); + for(i = 0; i < selects.length; i++) { + var obj = selects[i]; + if ( obj.multiple ) { + obj.setAttribute('should_be_multiple', true); + } + } + for(i = 0; i < selects.length; i++) { + var obj = selects[i]; + if ( obj.name.match(/_flag$/) ) { + flag_changed(obj); + } + } +}; + +</SCRIPT> <FORM NAME="dummy"> @@ -53,386 +153,6 @@ <BR> -% my %vfields; -% #code duplication w/ edit/part_svc.cgi, should move this hash to part_svc.pm -% # and generalize the subs -% # condition sub is tested to see whether to disable display of this choice -% # params: ( $def, $layer, $field ) (see SUB below) -% my $inv_sub = sub { -% $_[0]->{disable_inventory} -% || $_[0]->{'type'} ne 'text' -% }; -% tie my %flag, 'Tie::IxHash', -% '' => { 'desc' => 'No default', }, -% 'D' => { 'desc' => 'Default', -% 'condition' => -% sub { $_[0]->{disable_default} }, -% }, -% 'F' => { 'desc' => 'Fixed (unchangeable)', -% 'condition' => -% sub { $_[0]->{disable_fixed} }, -% }, -% 'S' => { 'desc' => 'Selectable Choice', -% 'condition' => -% sub { !ref($_[0]) || $_[0]->{disable_select} }, -% }, -% 'M' => { 'desc' => 'Manual selection from inventory', -% 'condition' => $inv_sub, -% }, -% 'A' => { 'desc' => 'Automatically fill in from inventory', -% 'condition' => $inv_sub, -% }, -% 'H' => { 'desc' => 'Select from hardware class', -% 'condition' => sub { $_[0]->{type} ne 'select-hardware' }, -% }, -% 'X' => { 'desc' => 'Excluded', -% 'condition' => -% sub { ! $vfields{$_[1]}->{$_[2]} }, -% -% }, -% ; -% -% my @dbs = $hashref->{svcdb} -% ? ( $hashref->{svcdb} ) -% : FS::part_svc->svc_tables(); -% -% my $help = ''; -% unless ( $hashref->{svcpart} ) { -% $help = ' '. -% include('/elements/popup_link.html', -% 'action' => $p.'docs/part_svc-table.html', -% 'label' => 'help', -% 'actionlabel' => 'Service table help', -% 'width' => 763, -% #'height' => 400, -% ); -% } -% -% tie my %svcdb, 'Tie::IxHash', map { $_=>$_ } grep dbdef->table($_), @dbs; -% my $widget = new HTML::Widgets::SelectLayers( -% #'selected_layer' => $p_svcdb, -% 'selected_layer' => $hashref->{svcdb} || 'svc_acct', -% 'options' => \%svcdb, -% 'form_name' => 'dummy', -% #'form_action' => 'process/part_svc.cgi', -% 'form_action' => 'part_svc.cgi', #self -% 'form_elements' => [qw( svc svcpart classnum selfservice_access -% disabled preserve -% )], -% 'html_between' => $help, -% 'layer_callback' => sub { -% my $layer = shift; -% -% my $html = qq!<INPUT TYPE="hidden" NAME="svcdb" VALUE="$layer">!; -% -% #$html .= $svcdb_info; -% -% my $columns = 3; -% my $count = 0; -% my $communigate = 0; -% my @part_export = -% map { qsearch( 'part_export', {exporttype => $_ } ) } -% keys %{FS::part_export::export_info($layer)}; -% $html .= '<BR><BR>'. include('/elements/table.html') . -% "<TR><TH COLSPAN=$columns>Exports</TH></TR><TR>"; -% foreach my $part_export ( @part_export ) { -% $communigate++ if $part_export->exporttype =~ /^communigate/; -% $html .= '<TD><INPUT TYPE="checkbox"'. -% ' NAME="exportnum'. $part_export->exportnum. '" VALUE="1" '; -% $html .= 'CHECKED' -% if ( $clone || $part_svc->svcpart ) #null svcpart search causing error -% && qsearchs( 'export_svc', { -% exportnum => $part_export->exportnum, -% svcpart => $clone || $part_svc->svcpart }); -% $html .= '>'. $part_export->label_html. '</TD>'; -% $count++; -% $html .= '</TR><TR>' unless $count % $columns; -% } -% $html .= '</TR></TABLE><BR><BR>'. $mod_info; -% -% $html .= include('/elements/table-grid.html', 'cellpadding' => 4 ). -% '<TR>'. -% '<TH CLASS="grid" BGCOLOR="#cccccc">Field</TH>'. -% '<TH CLASS="grid" BGCOLOR="#cccccc">Label</TH>'. -% '<TH CLASS="grid" BGCOLOR="#cccccc" COLSPAN=2>Modifier</TH>'. -% '</TR>'; -% -% my $bgcolor1 = '#eeeeee'; -% my $bgcolor2 = '#ffffff'; -% my $bgcolor; -% -% #yucky kludge -% my @fields = (); -% if ( defined( dbdef->table($layer) ) ) { -% @fields = grep { -% $_ ne 'svcnum' -% && ( $communigate || !$communigate_fields{$layer}->{$_} ) -% && ( !FS::part_svc->svc_table_fields($layer) -% ->{$_}->{disable_part_svc_column} -% || $part_svc->part_svc_column($_)->columnflag -% ) -% } fields($layer); -% } -% push @fields, 'usergroup' -% if $layer eq 'svc_acct' -% or ( $layer eq 'svc_broadband' and -% $conf->exists('svc_broadband-radius') ); # double kludge -% # (but we do want to check the config, right?) -% $part_svc->svcpart($clone) if $clone; #haha, undone below -% -% -% foreach my $field (@fields) { -% -% #a few lines of false laziness w/browse/part_svc.cgi -% my $def = FS::part_svc->svc_table_fields($layer)->{$field}; -% my $def_info = $def->{'def_info'}; -% my $formatter = $def->{'format'} || sub { shift }; -% -% my $part_svc_column = $part_svc->part_svc_column($field); -% my $label = $part_svc_column->columnlabel || $def->{'label'}; -% my $value = &$formatter($part_svc_column->columnvalue); -% my $flag = $part_svc_column->columnflag; -% -% if ( $bgcolor eq $bgcolor1 ) { -% $bgcolor = $bgcolor2; -% } else { -% $bgcolor = $bgcolor1; -% } -% -% $html .= qq!<TR><TD ROWSPAN=2 CLASS="grid" BGCOLOR="$bgcolor" ALIGN="right">!. -% ( $def->{'label'} || $field ). -% "</TD>"; -% -% $html .= qq!<TD ROWSPAN=2 CLASS="grid" BGCOLOR="$bgcolor"><INPUT NAME="${layer}__${field}_label" VALUE="!. encode_entities($label). '" STYLE="text-align:right"></TD>'; -% -% $flag = '' if $def->{type} eq 'disabled'; -% -% $html .= qq!<TD CLASS="grid" BGCOLOR="$bgcolor">!; -% -% if ( $def->{type} eq 'disabled' ) { -% -% $html .= 'No default'; -% -% } else { -% -% $html .= qq!<SELECT NAME="${layer}__${field}_flag"!. -% qq! onChange="${layer}__${field}_flag_changed(this)">!; -% -% foreach my $f ( keys %flag ) { -% -% # need to template-ize more httemplate/edit/svc_* first -% next if $f eq 'M' and $layer !~ /^svc_(broadband|external|phone|dish)$/; -% -% #here is where the SUB from above is called, to skip some choices -% next if $flag{$f}->{condition} -% && &{ $flag{$f}->{condition} }( $def, $layer, $field ); -% -% $html .= qq!<OPTION VALUE="$f"!. -% ' SELECTED'x($flag eq $f ). -% '>'. $flag{$f}->{desc}; -% -% } -% -% $html .= '</SELECT>'; -% -% $html .= join("\n", -% '<SCRIPT>', -% " function ${layer}__${field}_flag_changed(what) {", -% ' var f = what.options[what.selectedIndex].value;', -% ' if ( f == "" || f == "X" ) { //disable', -% " what.form.${layer}__${field}.disabled = true;". -% " what.form.${layer}__${field}.style.backgroundColor = '#dddddd';". -% " if ( what.form.${layer}__${field}_classnum ) {". -% " what.form.${layer}__${field}_classnum.disabled = true;". -% " what.form.${layer}__${field}_classnum.style.backgroundColor = '#dddddd';". -% " }". -% ' } else if ( f == "D" || f == "F" || f =="S" ) { //enable, text box', -% " what.form.${layer}__${field}.disabled = false;". -% " what.form.${layer}__${field}.style.backgroundColor = '#ffffff';". -% " if ( f == 'S' || '${field}' == 'usergroup' ) {". # kludge -% " what.form.${layer}__${field}.multiple = true;". -% " } else {". -% " what.form.${layer}__${field}.multiple = false;". -% " }". -% " what.form.${layer}__${field}.style.display = '';". -% " if ( what.form.${layer}__${field}_classnum ) {". -% " what.form.${layer}__${field}_classnum.disabled = false;". -% " what.form.${layer}__${field}_classnum.style.backgroundColor = '#ffffff';". -% " what.form.${layer}__${field}_classnum.style.display = 'none';". -% " }". -% ' } else if ( f == "M" || f == "A" || f == "H" ) { '. -% '//enable, inventory', -% " what.form.${layer}__${field}.disabled = false;". -% " what.form.${layer}__${field}.style.backgroundColor = '#ffffff';". -% " what.form.${layer}__${field}.style.display = 'none';". -% " if ( what.form.${layer}__${field}_classnum ) {". -% " what.form.${layer}__${field}_classnum.disabled = false;". -% " what.form.${layer}__${field}_classnum.style.backgroundColor = '#ffffff';". -% " what.form.${layer}__${field}_classnum.style.display = '';". -% " }". -% ' }', -% ' }', -% '</SCRIPT>', -% ); -% -% } -% -% $html .= qq!</TD><TD CLASS="grid" BGCOLOR="$bgcolor">!; -% -% my $disabled = $flag ? '' -% : 'DISABLED STYLE="background-color: #dddddd"'; -% my $nodisplay = ' STYLE="display:none"'; -% -% if ( !$def->{type} || $def->{type} eq 'text' ) { -% -% my $is_inv = ( $flag =~ /^[MA]$/ ); -% -% $html .= -% qq!<INPUT TYPE="text" NAME="${layer}__${field}" VALUE="$value" !. -% $disabled. -% ( $is_inv ? $nodisplay : $disabled ). -% '>'; -% -% $html .= include('/elements/select-table.html', -% 'element_name' => "${layer}__${field}_classnum", -% 'id' => "${layer}__${field}_classnum", -% 'element_etc' => ( $is_inv -% ? $disabled -% : $nodisplay -% ), -% 'table' => 'inventory_class', -% 'name_col' => 'classname', -% 'value' => $value, -% 'empty_label' => 'Select inventory class', -% ); -% -% } elsif ( $def->{type} eq 'checkbox' ) { -% -% $html .= include('/elements/checkbox.html', -% 'field' => $layer.'__'.$field, -% 'curr_value' => $value, -% 'value' => 'Y', -% ); -% -% } elsif ( $def->{type} eq 'select' ) { -% -% $html .= qq!<SELECT NAME="${layer}__${field}" $disabled!; -% $html .= ' MULTIPLE' if $flag eq 'S'; -% $html .= '>'; -% $html .= '<OPTION> </OPTION>' unless $value; -% if ( $def->{select_table} ) { -% foreach my $record ( qsearch( $def->{select_table}, {} ) ) { -% my $rvalue = $record->getfield($def->{select_key}); -% my $select_label = $def->{select_label}; -% $html .= qq!<OPTION VALUE="$rvalue"!. -% (grep(/^$rvalue$/, split(',',$value)) ? ' SELECTED>' : '>' ). -% $record->$select_label(). '</OPTION>'; -% } #next $record -% } elsif ( $def->{select_list} ) { -% foreach my $item ( @{$def->{select_list}} ) { -% $html .= qq!<OPTION VALUE="$item"!. -% (grep(/^$item$/, split(',',$value)) ? ' SELECTED>' : '>' ). -% $item. '</OPTION>'; -% } #next $item -% } elsif ( $def->{select_hash} ) { -% if ( ref($def->{select_hash}) eq 'ARRAY' ) { -% tie my %hash, 'Tie::IxHash', @{ $def->{select_hash} }; -% $def->{select_hash} = \%hash; -% } -% foreach my $key ( keys %{$def->{select_hash}} ) { -% $html .= qq!<OPTION VALUE="$key"!. -% (grep(/^$key$/, split(',',$value)) ? ' SELECTED>' : '>' ). -% $def->{select_hash}{$key}. '</OPTION>'; -% } #next $key -% } #endif -% $html .= '</SELECT>'; -% -% } elsif ( $def->{type} eq 'textarea' ) { -% -% $html .= -% qq!<TEXTAREA NAME="${layer}__${field}">!. encode_entities($value). -% '</TEXTAREA>'; -% -% } elsif ( $def->{type} =~ /select-(.*?).html/ ) { -% -% $html .= include("/elements/".$def->{type}, -% 'curr_value' => $value, -% 'element_name' => "${layer}__${field}", -% 'element_etc' => $disabled, -% 'multiple' => ($def->{multiple} || -% $flag eq 'S'), -% # allow the table def to force 'multiple' -% ); -% -% } elsif ( $def->{type} eq 'communigate_pro-accessmodes' ) { -% -% $html .= include('/elements/communigate_pro-accessmodes.html', -% 'element_name_prefix' => "${layer}__${field}_", -% 'curr_value' => $value, -% #doesn't work#'element_etc' => $disabled, -% ); -% -% } elsif ( $def->{type} eq 'select-hardware' ) { -% -% $html .= qq!<INPUT TYPE="text" NAME="${layer}__${field}" $disabled>!; -% $html .= include('/elements/select-hardware_class.html', -% 'curr_value' => $value, -% 'element_name' => "${layer}__${field}_classnum", -% 'id' => "${layer}__${field}_classnum", -% 'element_etc' => $flag ne 'H' && $nodisplay, -% 'empty_label' => 'Select hardware class', -% ); -% -% } elsif ( $def->{type} eq 'disabled' ) { -% -% $html .= -% qq!<INPUT TYPE="hidden" NAME="${layer}__${field}" VALUE="">!; -% -% } else { -% -% $html .= '<font color="#ff0000">unknown type '. $def->{type}; -% -% } -% -% $html .= "</TD></TR>\n"; - -% $def_info = "($def_info)" if $def_info; -% $html .= -% qq!<TR>!. -% qq! <TD COLSPAN=2 BGCOLOR="$bgcolor" ALIGN="center" !. -% qq! STYLE="padding:0; border-top: none">!. -% qq! <FONT SIZE="-1"><I>$def_info</I></FONT>!. -% qq! </TD>!. -% qq!</TR>\n!; -% -% } #foreach my $field (@fields) { -% -% $part_svc->svcpart('') if $clone; #undone -% $html .= "</TABLE>"; -% -% $html .= include('/elements/progress-init.html', -% $layer, #form name -% [ qw(svc svcpart classnum selfservice_access -% disabled preserve -% exportnum), -% @fields ], -% 'process/part_svc.cgi', -% $p.'browse/part_svc.cgi', -% $layer, -% ); -% $html .= '<BR><INPUT NAME="submit" TYPE="button" VALUE="'. -% ($hashref->{svcpart} ? 'Apply changes' : 'Add service'). '" '. -% ' onClick="document.'. "$layer.submit.disabled=true; ". -% "fixup(document.$layer); $layer". 'process();">'; -% -% #$html .= '<BR><INPUT TYPE="submit" VALUE="'. -% # ($hashref->{svcpart} ? 'Apply changes' : 'Add service'). '">'; -% -% $html; -% -% }, -% ); - <BR> Table <% $widget->html %> @@ -465,28 +185,43 @@ my $action = $part_svc->svcpart ? 'Edit' : 'Add'; my $hashref = $part_svc->hashref; # my $p_svcdb = $part_svc->svcdb || 'svc_acct'; -my %communigate_fields = ( - 'svc_acct' => { map { $_=>1 } - qw( file_quota file_maxnum file_maxsize - password_selfchange password_recover - ), - grep /^cgp_/, fields('svc_acct') - }, - 'svc_domain' => { map { $_=>1 } - qw( max_accounts trailer parent_svcnum ), - grep /^(cgp|acct_def)_/, fields('svc_domain') - }, - #'svc_forward' => { map { $_=>1 } qw( ) }, - #'svc_mailinglist' => { map { $_=>1 } qw( ) }, - #'svc_cert' => { map { $_=>1 } qw( ) }, -); -my $mod_info = ' -For the selected table, you can give fields default or fixed (unchangable) -values, or select an inventory class to manually or automatically fill in -that field. -'; +my @dbs = $hashref->{svcdb} + ? ( $hashref->{svcdb} ) + : FS::part_svc->svc_tables(); + +my $help = ''; +unless ( $hashref->{svcpart} ) { + $help = ' '. + include('/elements/popup_link.html', + 'action' => $p.'docs/part_svc-table.html', + 'label' => 'help', + 'actionlabel' => 'Service table help', + 'width' => 763, + #'height' => 400, + ); +} +tie my %svcdb, 'Tie::IxHash', map { $_=>$_ } grep dbdef->table($_), @dbs; +my $widget = new HTML::Widgets::SelectLayers( + #'selected_layer' => $p_svcdb, + 'selected_layer' => $hashref->{svcdb} || 'svc_acct', + 'options' => \%svcdb, + 'form_name' => 'dummy', + #'form_action' => 'process/part_svc.cgi', + 'form_action' => 'part_svc.cgi', #self + 'form_elements' => [qw( svc svcpart classnum selfservice_access + disabled preserve + )], + 'html_between' => $help, + 'layer_callback' => sub { + include('elements/part_svc_column.html', + shift, + 'part_svc' => $part_svc, + 'clone' => $clone + ) + } +); </%init> diff --git a/httemplate/edit/part_tag.html b/httemplate/edit/part_tag.html index 5712560c1..2cf34c6e8 100644 --- a/httemplate/edit/part_tag.html +++ b/httemplate/edit/part_tag.html @@ -8,7 +8,7 @@ { field=>'by_default', type=>'checkbox', value=>'Y' }, $tagcolor, ], - 'labels' => { 'tagnum' => 'Tag #', + 'labels' => { 'tagnum' => 'Tag', 'tagname' => 'Tag', 'tagdesc' => 'Message', 'tagcolor' => 'Highlight Color', diff --git a/httemplate/edit/payment_gateway.html b/httemplate/edit/payment_gateway.html index dfe52f109..a469beb7f 100644 --- a/httemplate/edit/payment_gateway.html +++ b/httemplate/edit/payment_gateway.html @@ -19,7 +19,7 @@ <SCRIPT TYPE="text/javascript"> - var modulesForNamespace = <% to_json(\%modules_for_namespace, {canonical=>1}) %>; + var modulesForNamespace = <% encode_json(\%modules_for_namespace, {canonical=>1}) %>; function changeNamespace(what) { var ns = what.value; var select_module = document.getElementById('gateway_module'); diff --git a/httemplate/edit/phone_device.html b/httemplate/edit/phone_device.html index 4aec63e5a..7bc88a8c7 100644 --- a/httemplate/edit/phone_device.html +++ b/httemplate/edit/phone_device.html @@ -32,12 +32,11 @@ %> <%init> -my @deviceparts_with_inventory; -my @part_device = qsearch('part_device', {} ); -foreach my $part_device ( @part_device ) { - push @deviceparts_with_inventory, $part_device->devicepart - if $part_device->inventory_classnum; -} +my @deviceparts_with_inventory = + map $_->devicepart, + qsearch({ 'table' => 'part_device', + 'extra_sql' => 'WHERE inventory_classnum IS NOT NULL', + }); my $html_foot = sub { my $js = " @@ -72,9 +71,9 @@ my $html_foot = sub { var devicepart = what.options[what.selectedIndex].value; - var deviceparts_with_inventory = new Array(\""; -$js .= join("\",\"",@deviceparts_with_inventory); -$js .= "\"); + var deviceparts_with_inventory = new Array("; +$js .= join(',', map qq("$_"), @deviceparts_with_inventory); +$js .= "); var hasInventory = false; for ( i = 0; i < deviceparts_with_inventory.length; i++ ) { diff --git a/httemplate/edit/process/REAL_cust_pkg.cgi b/httemplate/edit/process/REAL_cust_pkg.cgi index 3e0ef59c1..fd2893487 100755 --- a/httemplate/edit/process/REAL_cust_pkg.cgi +++ b/httemplate/edit/process/REAL_cust_pkg.cgi @@ -19,36 +19,41 @@ die "access denied" my $pkgnum = $cgi->param('pkgnum') or die; my $old = qsearchs('cust_pkg',{'pkgnum'=>$pkgnum}); my %hash = $old->hash; -$hash{$_}= $cgi->param($_) ? parse_datetime($cgi->param($_)) : '' - foreach qw( start_date setup bill last_bill contract_end ); +foreach ( qw( start_date setup bill last_bill contract_end ) ) { + if ( $cgi->param($_) =~ /^(\d+)$/ ) { + $hash{$_} = $1; + } else { + $hash{$_} = ''; + } # adjourn, expire, resume not editable this way - -my @errors = (); - -push @errors, '_bill_areyousure' - if $hash{'bill'} != $old->bill # if the next bill date was changed - && $hash{'bill'} < time # to a date in the past - && ! $cgi->param('bill_areyousure'); # and it wasn't confirmed - -push @errors, '_setup_areyousure' - if ! $hash{'setup'} && $old->setup # if the setup date was removed - && ! $cgi->param('setup_areyousure'); # and it wasn't confirmed - -push @errors, '_setupadd_areyousure' - if $hash{'setup'} && ! $old->setup # if the setup date was added - && ! $cgi->param('setupadd_areyousure'); # and it wasn't confirmed - -push @errors, '_start' - if $hash{'start_date'} && !$old->start_date # if a start date was added - && $hash{'setup'}; # but there's a setup date +} my $new; my $error; -if ( @errors ) { - $error = join(',', @errors); -} else { - $new = new FS::cust_pkg \%hash; - $error = $new->replace($old); +$new = new FS::cust_pkg \%hash; +$error = $new->replace($old); + +if (!$error) { + my @supp_pkgs = $old->supplemental_pkgs; + foreach $new (@supp_pkgs) { + foreach ( qw( start_date setup contract_end ) ) { + # propagate these to supplementals + $new->set($_, $hash{$_}); + } + if ( $hash{'bill'} ne $old->get('bill') ) { + if ( $hash{'bill'} and $old->get('bill') ) { + # adjust by the same interval + my $diff = $hash{'bill'} - $old->get('bill'); + $new->set('bill', $new->get('bill') + $diff); + } else { + # absolute date + $new->set('bill', $hash{'bill'}); + } + } + $error = $new->replace; + $error .= ' (supplemental package '.$new->pkgnum.')' if $error; + last if $error; + } } </%init> diff --git a/httemplate/edit/process/bulk-part_pkg.html b/httemplate/edit/process/bulk-part_pkg.html new file mode 100644 index 000000000..4775a9334 --- /dev/null +++ b/httemplate/edit/process/bulk-part_pkg.html @@ -0,0 +1,30 @@ +% if ( $error ) { +% $cgi->param('error', $error); +<% $cgi->redirect(popurl(3).'/edit/bulk-part_pkg.cgi?', $cgi->query_string) %> +% } else { +<% $cgi->redirect(popurl(3).'/browse/part_pkg.cgi') %> +% } +<%init> +die "access denied" unless $FS::CurrentUser::CurrentUser->access_right('Bulk edit package definitions'); + +my @pkgparts = $cgi->param('pkgpart') + or die "no package definitions selected"; + +my %changes; +foreach my $param (grep { /^report_option_\d+$/ } $cgi->param) { + if ( length($cgi->param($param)) ) { + if ( $cgi->param($param) == 1 ) { + $changes{$param} = 1; + } else { + $changes{$param} = ''; + } + } +} + +my $error; +foreach my $pkgpart (@pkgparts) { + my $part_pkg = FS::part_pkg->by_key($pkgpart); + my %options = ( $part_pkg->options, %changes ); + $error ||= $part_pkg->replace( options => \%options ); +} +</%init> diff --git a/httemplate/edit/process/change-cust_pkg.html b/httemplate/edit/process/change-cust_pkg.html index 2770f3283..77f261d56 100644 --- a/httemplate/edit/process/change-cust_pkg.html +++ b/httemplate/edit/process/change-cust_pkg.html @@ -32,11 +32,11 @@ my %change = map { $_ => scalar($cgi->param($_)) } $change{'keep_dates'} = 1; if ( $cgi->param('locationnum') == -1 ) { - my $cust_location = new FS::cust_location { + my $cust_location = FS::cust_location->new_or_existing({ 'custnum' => $cust_pkg->custnum, map { $_ => scalar($cgi->param($_)) } qw( address1 address2 city county state zip country ) - }; + }); $change{'cust_location'} = $cust_location; } diff --git a/httemplate/edit/process/credit-cust_bill_pkg.html b/httemplate/edit/process/credit-cust_bill_pkg.html index cbcf619ca..8e66368d4 100644 --- a/httemplate/edit/process/credit-cust_bill_pkg.html +++ b/httemplate/edit/process/credit-cust_bill_pkg.html @@ -10,7 +10,7 @@ <%init> die "access denied" - unless $FS::CurrentUser::CurrentUser->access_right('Post credit'); + unless $FS::CurrentUser::CurrentUser->access_right('Credit line items'); my @billpkgnum_setuprecurs = map { $_ =~ /^billpkgnum(\d+\-\w*)$/ or die 'gm#23'; $1; } diff --git a/httemplate/edit/process/cust_location.cgi b/httemplate/edit/process/cust_location.cgi index b9f93db8b..56c3968f6 100644 --- a/httemplate/edit/process/cust_location.cgi +++ b/httemplate/edit/process/cust_location.cgi @@ -28,11 +28,10 @@ my $cust_location = qsearchs({ }); die "unknown locationnum $locationnum" unless $cust_location; -my $new = FS::cust_location->new({ +my $new = FS::cust_location->new_or_existing({ custnum => $cust_location->custnum, prospectnum => $cust_location->prospectnum, - map { $_ => scalar($cgi->param($_)) } - qw( address1 address2 city county state zip country ) + map { $_ => scalar($cgi->param($_)) } FS::cust_main->location_fields }); my $error = $cust_location->move_to($new); diff --git a/httemplate/edit/process/cust_main.cgi b/httemplate/edit/process/cust_main.cgi index 31ec4ab12..c1f815550 100755 --- a/httemplate/edit/process/cust_main.cgi +++ b/httemplate/edit/process/cust_main.cgi @@ -16,8 +16,8 @@ my $DEBUG = 0; </%once> <%init> -die "access denied" - unless $FS::CurrentUser::CurrentUser->access_right('Edit customer'); +my $curuser = $FS::CurrentUser::CurrentUser; +die "access denied" unless $curuser->access_right('Edit customer'); my $conf = new FS::Conf; @@ -62,6 +62,18 @@ $cgi->param('invoicing_list', join(',', @invoicing_list) ); $cgi->param('duplicate_of_custnum') =~ /^(\d+)$/; my $duplicate_of = $1; +# if this is enabled, enforce it +if ( $conf->exists('agent-ship_address', $cgi->param('agentnum')) ) { + my $agent = FS::agent->by_key($cgi->param('agentnum')); + my $agent_cust_main = $agent->agent_cust_main; + if ( $agent_cust_main ) { + my $agent_location = $agent_cust_main->ship_location; + foreach (qw(address1 city state zip country latitude longitude district)) { + $cgi->param("ship_$_", $agent_location->get($_)); + } + } +} + my %locations; for my $pre (qw(bill ship)) { @@ -71,10 +83,7 @@ for my $pre (qw(bill ship)) { } $hash{'custnum'} = $cgi->param('custnum'); warn Dumper \%hash if $DEBUG; - # if we can qsearchs it, then it's unchanged, so use that - $locations{$pre} = qsearchs('cust_location', \%hash) - || FS::cust_location->new( \%hash ); - + $locations{$pre} = FS::cust_location->new_or_existing(\%hash); } if ( ($cgi->param('same') || '') eq 'Y' ) { @@ -156,9 +165,14 @@ foreach my $dfield (qw( $new->setfield('paid', $cgi->param('paid') ) if $cgi->param('paid'); -my @exempt_groups = grep /\S/, $conf->config('tax-cust_exempt-groups'); -my @tax_exempt = grep { $cgi->param("tax_$_") eq 'Y' } @exempt_groups; -my %tax_exempt = map { $_ => scalar($cgi->param("tax_$_".'_num')) } @tax_exempt; +my %options = (); +if ( $curuser->access_right('Edit customer tax exemptions') ) { + my @exempt_groups = grep /\S/, $conf->config('tax-cust_exempt-groups'); + my @tax_exempt = grep { $cgi->param("tax_$_") eq 'Y' } @exempt_groups; + $options{'tax_exemption'} = { + map { $_ => scalar($cgi->param("tax_$_".'_num')) } @tax_exempt + }; +} #perhaps this stuff should go to cust_main.pm if ( $new->custnum eq '' or $duplicate_of ) { @@ -266,8 +280,8 @@ if ( $new->custnum eq '' or $duplicate_of ) { else { # create the customer $error ||= $new->insert( \%hash, \@invoicing_list, - 'tax_exemption'=> \%tax_exempt, - 'prospectnum' => scalar($cgi->param('prospectnum')), + %options, + prospectnum => scalar($cgi->param('prospectnum')), ); my $conf = new FS::Conf; @@ -328,7 +342,7 @@ if ( $new->custnum eq '' or $duplicate_of ) { warn Dumper({ new => $new, old => $old }) if $DEBUG; $error ||= $new->replace( $old, \@invoicing_list, - 'tax_exemption' => \%tax_exempt, + %options, ); warn "$me returned from replace" if $DEBUG; diff --git a/httemplate/edit/process/cust_pkg_quantity.html b/httemplate/edit/process/cust_pkg_quantity.html new file mode 100644 index 000000000..fb2657252 --- /dev/null +++ b/httemplate/edit/process/cust_pkg_quantity.html @@ -0,0 +1,33 @@ +% if ($error) { +% $cgi->param('error', $error); +% $cgi->redirect(popurl(3). 'edit/cust_pkg_quantity.html?'. $cgi->query_string ); +% } else { + + <& /elements/header-popup.html, "Quantity changed" &> + <SCRIPT TYPE="text/javascript"> + window.top.location.reload(); + </SCRIPT> + </BODY> + </HTML> + +% } +<%init> + +my $curuser = $FS::CurrentUser::CurrentUser; + +die "access denied" + unless $curuser->access_right('Change customer package'); + +my $cust_pkg = qsearchs({ + 'table' => 'cust_pkg', + 'addl_from' => 'LEFT JOIN cust_main USING ( custnum )', + 'hashref' => { 'pkgnum' => scalar($cgi->param('pkgnum')), }, + 'extra_sql' => ' AND '. $curuser->agentnums_sql, +}); +die 'unknown pkgnum' unless $cust_pkg; + +$cgi->param('quantity') =~ /^(\d+)$/; +my $quantity = $1; +my $error = $cust_pkg->set_quantity($1); + +</%init> diff --git a/httemplate/edit/process/cust_svc.cgi b/httemplate/edit/process/cust_svc.cgi index e22cbb201..7cb1d6d8f 100644 --- a/httemplate/edit/process/cust_svc.cgi +++ b/httemplate/edit/process/cust_svc.cgi @@ -6,7 +6,7 @@ %} <%init> -die 'access deined' +die 'access denied' unless $FS::CurrentUser::CurrentUser->access_right('Change customer service'); my $svcnum = $cgi->param('svcnum'); diff --git a/httemplate/edit/process/elements/process.html b/httemplate/edit/process/elements/process.html index 2d39e9dce..fb1ee7a27 100644 --- a/httemplate/edit/process/elements/process.html +++ b/httemplate/edit/process/elements/process.html @@ -263,6 +263,9 @@ foreach my $value ( @values ) { if ( !$error ) { if ( $old_pkey ) { + + &{ $opt{'edit_callback'} }( $new, $old ) if $opt{'edit_callback'}; + $error = $new->replace($old, @args); } else { $error = $new->insert(@args); diff --git a/httemplate/edit/process/elements/svc_Common.html b/httemplate/edit/process/elements/svc_Common.html index 5a8afbd6c..06f4c00b1 100644 --- a/httemplate/edit/process/elements/svc_Common.html +++ b/httemplate/edit/process/elements/svc_Common.html @@ -10,5 +10,10 @@ my %opt = @_; my $table = $opt{'table'}; $opt{'fields'} ||= [ fields($table) ]; push @{ $opt{'fields'} }, qw( pkgnum svcpart ); +foreach (fields($table)) { + if ( $cgi->param($_.'_classnum') ) { + push @{ $opt{'fields'} }, $_.'_classnum'; + } +} </%init> diff --git a/httemplate/edit/process/part_export.cgi b/httemplate/edit/process/part_export.cgi index bcb9c0df1..e0c470675 100644 --- a/httemplate/edit/process/part_export.cgi +++ b/httemplate/edit/process/part_export.cgi @@ -56,6 +56,7 @@ my $new = new FS::part_export ( { if ( $cgi->param('svc_machine') eq 'Y' ) { $new->machine('_SVC_MACHINE'); $new->part_export_machine_textarea( $cgi->param('part_export_machine') ); + $new->default_machine_name( $cgi->param('default_machine_name') ); } my $error; diff --git a/httemplate/edit/process/part_pkg.cgi b/httemplate/edit/process/part_pkg.cgi index c388676df..932e33b1d 100755 --- a/httemplate/edit/process/part_pkg.cgi +++ b/httemplate/edit/process/part_pkg.cgi @@ -10,6 +10,7 @@ 'precheck_callback' => $precheck_callback, 'args_callback' => $args_callback, 'process_m2m' => \@process_m2m, + 'process_o2m' => \@process_o2m, ) %> <%init> @@ -185,6 +186,15 @@ my @process_m2m = ( grep /^svc_dst_pkgpart/, $cgi->param ], }, + { 'link_table' => 'part_pkg_link', + 'target_table' => 'part_pkg', + 'base_field' => 'src_pkgpart', + 'target_field' => 'dst_pkgpart', + 'hashref' => { 'link_type' => 'supp', 'hidden' => '' }, + 'params' => [ map $cgi->param($_), + grep /^supp_dst_pkgpart/, $cgi->param + ], + }, map { my $hidden = $_; { 'link_table' => 'part_pkg_link', @@ -235,4 +245,11 @@ if ( $cgi->param('pkgpart') || ! $conf->exists('agent_defaultpkg') ) { }; } +my @process_o2m = ( + { + 'table' => 'part_pkg_msgcat', + 'fields' => [qw( locale pkg )], + }, +); + </%init> diff --git a/httemplate/edit/process/part_pkg_usage.html b/httemplate/edit/process/part_pkg_usage.html new file mode 100644 index 000000000..eb6c37b82 --- /dev/null +++ b/httemplate/edit/process/part_pkg_usage.html @@ -0,0 +1,67 @@ +% if ( $is_error ) { +% $cgi->param('error' => \%part_pkg_usage); +% # internal redirect, because it's a lot of state to pass through +<& /browse/part_pkg_usage.html &> +% } else { +% # uh, not quite sure... +<% $cgi->redirect($fsurl.'browse/part_pkg.cgi') %> +% } +<%init> +my %vars = $cgi->Vars; +my %part_pkg_usage; +my $is_error; +foreach my $pkgpart ($cgi->param('pkgpart')) { + next unless $pkgpart =~ /^\d+$/; + my $part_pkg = FS::part_pkg->by_key($pkgpart) + or die "unknown pkgpart $pkgpart"; + my %old = map { $_->pkgusagepart => $_ } $part_pkg->part_pkg_usage; + $part_pkg_usage{$pkgpart} ||= []; + my @rows; + foreach (grep /^pkgpart$pkgpart/, keys %vars) { + /^pkgpart\d+_(\w+\D)(\d+)$/ or die "misspelled field name '$_'"; + my $value = delete $vars{$_}; + my $field = $1; + my $row = $2; + $rows[$row] ||= {}; + $rows[$row]->{$field} = $value; + } + + foreach my $row (@rows) { + next if !defined($row); + my $error; + my %classes; + foreach my $class (grep /^class/, keys %$row) { + $class =~ /^class(\d+)_$/; + my $classnum = $1; + $classes{$classnum} = delete $row->{$class}; + } + my $usage = FS::part_pkg_usage->new($row); + $usage->set('pkgpart', $pkgpart); + if ( $usage->pkgusagepart and $row->{minutes} > 0 ) { + $error = $usage->replace(\%classes); + # and don't delete the existing one + delete($old{$usage->pkgusagepart}); + } elsif ( $row->{minutes} > 0 ) { + $error = $usage->insert(\%classes); + } else { + next; + } + if ( $error ) { + $usage->set('error', $error); + $is_error = 1; + } + push @{ $part_pkg_usage{$pkgpart} }, $usage; + } + + foreach my $usage (values %old) { + # all of these were not sent back by the client, so delete them + my $error = $usage->delete; + if ( $error ) { + $usage->set('error', $error); + $is_error = 1; + unshift @{ $part_pkg_usage{$pkgpart} }, $usage; + } + } + +} +</%init> diff --git a/httemplate/edit/process/quick-cust_pkg.cgi b/httemplate/edit/process/quick-cust_pkg.cgi index 2dadbccdc..0cc17d36b 100644 --- a/httemplate/edit/process/quick-cust_pkg.cgi +++ b/httemplate/edit/process/quick-cust_pkg.cgi @@ -70,6 +70,9 @@ my $quantity = $1 || 1; $cgi->param('refnum') =~ /^(\d*)$/ or die 'illegal refnum '. $cgi->param('refnum'); my $refnum = $1; +$cgi->param('contactnum') =~ /^(\-?\d*)$/ + or die 'illegal contactnum '. $cgi->param('contactnum'); +my $contactnum = $1; $cgi->param('locationnum') =~ /^(\-?\d*)$/ or die 'illegal locationnum '. $cgi->param('locationnum'); my $locationnum = $1; @@ -109,6 +112,7 @@ my %hash = ( : '' ), 'refnum' => $refnum, + 'contactnum' => $contactnum, 'locationnum' => $locationnum, 'discountnum' => $discountnum, #for the create a new discount case @@ -142,11 +146,19 @@ if ( $quotationnum ) { my %opt = ( 'cust_pkg' => $cust_pkg ); + if ( $contactnum == -1 ) { + my $contact = FS::contact->new({ + 'custnum' => scalar($cgi->param('custnum')), + map { $_ => scalar($cgi->param("contactnum_$_")) } qw( first last ) + }); + $opt{'contact'} = $contact; + } + if ( $locationnum == -1 ) { - my $cust_location = new FS::cust_location { + my $cust_location = FS::cust_location->new_or_existing({ map { $_ => scalar($cgi->param($_)) } - qw( custnum address1 address2 city county state zip country geocode ) - }; + ('custnum', FS::cust_main->location_fields) + }); $opt{'cust_location'} = $cust_location; } diff --git a/httemplate/edit/process/svc_phone.html b/httemplate/edit/process/svc_phone.html index 7a3b43d32..9983ea2cb 100644 --- a/httemplate/edit/process/svc_phone.html +++ b/httemplate/edit/process/svc_phone.html @@ -2,6 +2,7 @@ 'table' => 'svc_phone', 'args_callback' => $args_callback, 'value_callback' => $value_callback, + 'edit_callback' => $edit_callback, %opt, &> <%init> @@ -28,6 +29,9 @@ my $right = $opt{'bulk'} ? 'Bulk provision customer service' die "access denied" unless $FS::CurrentUser::CurrentUser->access_right($right); +$cgi->param('phonenum', $cgi->param('phonenum_manual') ) + if $cgi->param('phonenum_which') eq 'phonenum_manual'; + my $tollfreephonenum = $cgi->param('tollfreephonenum'); $cgi->param('phonenum',$tollfreephonenum) if $tollfreephonenum =~ /^\d+$/; @@ -36,10 +40,10 @@ my $args_callback = sub { my %opt = (); if ( $cgi->param('locationnum') == -1 ) { - my $cust_location = new FS::cust_location { + my $cust_location = FS::cust_location->new_or_existing({ map { $_ => scalar($cgi->param($_)) } qw( custnum address1 address2 city county state zip country ) - }; + }); $opt{'cust_location'} = $cust_location; } @@ -48,8 +52,13 @@ my $args_callback = sub { }; my $value_callback = sub { - my ($field, $value) = @_; - ($field =~ /_date$/) ? parse_datetime($value) : $value; + my ($field, $value) = @_; + ($field =~ /_date$/) ? parse_datetime($value) : $value; +}; + +my $edit_callback = sub { + my( $new, $old ) = @_; + $new->sip_password( $old->sip_password ) if $new->sip_password eq '*HIDDEN*'; }; </%init> diff --git a/httemplate/edit/quick-charge.html b/httemplate/edit/quick-charge.html index 1d9647f2f..466091dfa 100644 --- a/httemplate/edit/quick-charge.html +++ b/httemplate/edit/quick-charge.html @@ -145,7 +145,6 @@ function bill_now_changed (what) { <% mt('with terms') |h %> <& /elements/select-terms.html, 'curr_value' => scalar($cgi->param('invoice_terms')), - 'empty_value' => $default_terms, 'disabled' => ( $cgi->param('bill_now') ? 0 : 1 ), &> </TD> diff --git a/httemplate/edit/rate_region.cgi b/httemplate/edit/rate_region.cgi index 367bbafb6..a1c1bcb7d 100644 --- a/httemplate/edit/rate_region.cgi +++ b/httemplate/edit/rate_region.cgi @@ -33,6 +33,14 @@ </TD> </TR> + <& /elements/tr-checkbox.html, + label => 'Exact match', + field => 'exact_match', + cell_style => 'font-weight: bold', + value => 'Y', + curr_value => $rate_region->exact_match + &> + </TABLE> <BR> diff --git a/httemplate/edit/svc_acct.cgi b/httemplate/edit/svc_acct.cgi index c1f74551d..627791ba7 100755 --- a/httemplate/edit/svc_acct.cgi +++ b/httemplate/edit/svc_acct.cgi @@ -9,19 +9,6 @@ <BR> % } -<SCRIPT TYPE="text/javascript"> -function randomPass() { - var i=0; - var pw_set='<% join('', 'a'..'z', 'A'..'Z', '0'..'9' ) %>'; - var pass=''; - while(i < 8) { - i++; - pass += pw_set.charAt(Math.floor(Math.random() * pw_set.length)); - } - document.OneTrueForm.clear_password.value = pass; -} -</SCRIPT> - <FORM NAME="OneTrueForm" ACTION="<% $p1 %>process/svc_acct.cgi" METHOD=POST> <INPUT TYPE="hidden" NAME="svcnum" VALUE="<% $svcnum %>"> <INPUT TYPE="hidden" NAME="pkgnum" VALUE="<% $pkgnum %>"> @@ -57,10 +44,11 @@ function randomPass() { %if ( $part_svc->part_svc_column('_password')->columnflag ne 'F' ) { <TR> +% #XXX eventually should require "Edit Password" ACL <TD ALIGN="right"><% mt('Password') |h %></TD> <TD> - <INPUT TYPE="text" NAME="clear_password" VALUE="<% $password %>" SIZE=<% $pmax2 %> MAXLENGTH=<% $pmax %>> - <INPUT TYPE="button" VALUE="<% mt('Generate') |h %>" onclick="randomPass();"> + <INPUT TYPE="text" ID="clear_password" NAME="clear_password" VALUE="<% $password %>" SIZE=<% $pmax2 %> MAXLENGTH=<% $pmax %>> + <& /elements/random_pass.html, 'clear_password' &> </TD> </TR> %}else{ diff --git a/httemplate/edit/svc_broadband.cgi b/httemplate/edit/svc_broadband.cgi index 0d4b9897b..1b85460e6 100644 --- a/httemplate/edit/svc_broadband.cgi +++ b/httemplate/edit/svc_broadband.cgi @@ -104,8 +104,12 @@ my @fields = ( { field=>'sectornum', type=>'select-tower_sector', }, { field=>'routernum', type=>'select-router_block_ip' }, { field=>'mac_addr' , type=>'input-mac_addr' }, - qw( latitude longitude altitude vlan_profile - performance_profile authkey plan_id ) + qw( + latitude longitude altitude + radio_serialnum radio_location poe_location rssi suid + ), + { field=>'shared_svcnum', type=>'search-svc_broadband', }, + qw( vlan_profile performance_profile authkey plan_id ), ); if ( $conf->exists('svc_broadband-radius') ) { diff --git a/httemplate/edit/svc_phone.cgi b/httemplate/edit/svc_phone.cgi index 9647b6887..13bbe82a1 100644 --- a/httemplate/edit/svc_phone.cgi +++ b/httemplate/edit/svc_phone.cgi @@ -6,6 +6,11 @@ my( $cgi, $svc_x, $part_svc, $cust_pkg, $fields, $opt ) = @_; $svc_x->locationnum($cust_pkg->locationnum) if $cust_pkg; }, + 'svc_edit_callback' => sub { + my( $cgi, $svc_x, $part_svc, $cust_pkg, $fields, $opt) = @_; + my $conf = new FS::Conf; + $svc_x->sip_password('*HIDDEN*') unless $conf->exists('showpasswords'); + }, &> <%init> @@ -28,6 +33,11 @@ my $begin_callback = sub { type => 'select-did', label => 'Phone number', multiple => $bulk, + }, + { field => 'sim_imsi', + type => 'text', + size => 15, + maxlength => 15, }; push @$fields, { field => 'domsvc', diff --git a/httemplate/elements/auto-table.html b/httemplate/elements/auto-table.html index 9aff94e67..5118b91ff 100644 --- a/httemplate/elements/auto-table.html +++ b/httemplate/elements/auto-table.html @@ -50,7 +50,7 @@ var <%$pre%>next_rownum; var <%$pre%>set_rownum; var <%$pre%>addRow; var <%$pre%>deleteRow; -var <%$pre%>fieldorder = <% to_json($fieldorder) %>; +var <%$pre%>fieldorder = <% encode_json($fieldorder) %>; function <%$pre%>possiblyAddRow_factory(obj) { var callback = obj.onchange; @@ -70,8 +70,8 @@ function <%$pre%>set_rownum(obj, rownum) { if ( obj.id ) { obj.id = obj.id + rownum; } - if ( obj.name ) { - obj.name = obj.name + rownum; + if ( obj.getAttribute('name') ) { + obj.setAttribute('name', obj.getAttribute('name') + rownum); // also, in this case it's a form field that will be part of the record // so set up an onchange handler obj.onchange = <%$pre%>possiblyAddRow_factory(obj); @@ -96,17 +96,32 @@ function <%$pre%>addRow(data) { <%$pre%>set_rownum(row, this_rownum); if(data instanceof Array) { for (i = 0; i < data.length && i < <%$pre%>fieldorder.length; i++) { - var el = document.getElementsByName(<%$pre%>fieldorder[i] + this_rownum)[0]; + var el = document.getElementsByName(<%$pre |js_string%> + + <%$pre%>fieldorder[i] + + this_rownum)[0]; if (el) { - el.value = data[i]; + if ( el.tagName.toLowerCase() == 'span' ) { + el.innerHTML = data[i]; + } else if ( el.type == 'checkbox' ) { + el.checked = (el.value == data[i]); + } else { + el.value = data[i]; + } } } } else if (data instanceof Object) { for (var field in data) { - var el = document.getElementsByName(field + this_rownum)[0]; + var el = document.getElementsByName(<%$pre |js_string%> + + field + + this_rownum)[0]; if (el) { - el.value = data[field]; -% # doesn't work for checkbox + if ( el.tagName.toLowerCase() == 'span' ) { + el.innerHTML = data[field]; + } else if ( el.type == 'checkbox' ) { + el.checked = (el.value == data[field]); + } else { + el.value = data[field]; + } } } } // else nothing @@ -123,6 +138,20 @@ function <%$pre%>deleteRow(rownum) { <%$pre%>tbody.removeChild(r); } +function <%$pre%>set_prefix(obj) { + if ( obj.id ) { + obj.id = <%$pre |js_string%> + obj.id; + } + if ( obj.getAttribute('name') ) { + obj.setAttribute('name', <%$pre |js_string%> + obj.getAttribute('name')); + } + for (var i = 0; i < obj.children.length; i++) { + if ( obj.children[i] instanceof Node ) { + <%$pre%>set_prefix(obj.children[i]); + } + } +} + function <%$pre%>init() { <%$pre%>template = document.getElementById(<% $template_row |js_string%>); <%$pre%>tbody = document.getElementById('<%$pre%>autotable'); @@ -131,8 +160,10 @@ function <%$pre%>init() { var table = <%$pre%>template.parentNode; table.removeChild(<%$pre%>template); // give it an id - <%$pre%>template.id = <%$pre |js_string%> + 'row'; - // and a magic identifier so we know it's been submitted + <%$pre%>template.id = 'row'; + // prefix the ids and names of the TR object and all its descendants + <%$pre%>set_prefix(<%$pre%>template); + // add a magic identifier so we know it's been submitted var magic = document.createElement('INPUT'); magic.setAttribute('type', 'hidden'); magic.setAttribute('name', '<%$pre%>magic'); @@ -140,18 +171,26 @@ function <%$pre%>init() { // and a delete button %# should this be enclosed in an actual <button> for aesthetics? var delete_button = document.createElement('IMG'); - delete_button.id = 'delete_button'; + delete_button.id = '<%$pre%>delete_button'; delete_button.src = '<%$fsurl%>images/cross.png'; delete_button.alt = 'X'; // use an inline string for this so that it will be cloned properly delete_button.setAttribute('onclick', "<%$pre%>deleteRow(this.rownum);"); + // and an error display + var error_span = document.createElement('SPAN'); + error_span.className = 'error'; + error_span.style.color = '#FF0000'; + error_span.setAttribute('name', '<%$pre%>error'); + error_span.style.padding = '5px'; var delete_cell = document.createElement('TD'); + delete_cell.style.textAlign = 'left'; delete_cell.appendChild(delete_button); delete_cell.appendChild(magic); // it has to go somewhere + delete_cell.appendChild(error_span); <%$pre%>template.appendChild(delete_cell); // preload rows - var rows = <% to_json(\@rows) %>; + var rows = <% encode_json(\@rows) %>; for (var i = 0; i < rows.length; i++) { <%$pre%>addRow(rows[i]); } diff --git a/httemplate/elements/change_history_common.html b/httemplate/elements/change_history_common.html index 232664e39..34ce70b6c 100644 --- a/httemplate/elements/change_history_common.html +++ b/httemplate/elements/change_history_common.html @@ -15,13 +15,7 @@ <TH CLASS="grid" BGCOLOR="#cccccc">Description</TH> </TR> -% foreach my $item ( sort { $a->history_date <=> $b->history_date -% #|| table order -% || $a->historynum <=> $b->historynum -% } -% @history -% ) -% { +% foreach my $item ( @history ) { % my $history_other = ''; % my $act = $item->history_action; % if ( $act =~ /^replace/ ) { @@ -196,4 +190,11 @@ $cust_pkg_date_format .= ' %l:%M:%S%P' if $conf->exists('cust_pkg-display_times') || $curuser->option('cust_pkg-display_times'); +@history = sort { $a->history_date <=> $b->history_date + || $a->historynum <=> $b->historynum } @history; + +if ( $curuser->option('history_order') eq 'newest' ) { + @history = reverse @history; +} + </%init> diff --git a/httemplate/elements/change_password.html b/httemplate/elements/change_password.html new file mode 100644 index 000000000..625ba1fb5 --- /dev/null +++ b/httemplate/elements/change_password.html @@ -0,0 +1,41 @@ +<STYLE> +.passwordbox { + border: 1px solid #7e0079; + padding: 2px; + position: absolute; + font-size: 80%; + background-color: #ffffff; + display: none; +} +</STYLE> +<A ID="<%$pre%>link" HREF="#" onclick="<%$pre%>toggle(true)">(<% mt('change') %>)</A> +<DIV ID="<%$pre%>form" CLASS="passwordbox"> + <FORM METHOD="POST" ACTION="<%$fsurl%>misc/process/change-password.html"> + <INPUT TYPE="hidden" NAME="svcnum" VALUE="<% $svc_acct->svcnum |h%>"> + <INPUT TYPE="text" ID="<%$pre%>password" NAME="password" VALUE="<% $curr_value |h%>"> + <& /elements/random_pass.html, $pre.'password', 'randomize' &> + <INPUT TYPE="submit" VALUE="change"> + <INPUT TYPE="button" VALUE="cancel" onclick="<%$pre%>toggle(false)"> +% if ( $error ) { + <BR><SPAN STYLE="color: #ff0000"><% $error |h %></SPAN> +% } + </FORM> +</DIV> +<SCRIPT TYPE="text/javascript"> +function <%$pre%>toggle(val) { + document.getElementById('<%$pre%>form').style.display = + val ? 'inline-block' : 'none'; + document.getElementById('<%$pre%>link').style.display = + val ? 'none' : 'inline'; +} +% if ( $error ) { +<%$pre%>toggle(true); +% } +</SCRIPT> +<%init> +my %opt = @_; +my $svc_acct = $opt{'svc_acct'}; +my $curr_value = $opt{'curr_value'} || ''; +my $pre = 'changepw'.$svc_acct->svcnum.'_'; +my $error = $cgi->param($pre.'error'); +</%init> diff --git a/httemplate/elements/checkbox-tristate.html b/httemplate/elements/checkbox-tristate.html new file mode 100644 index 000000000..4c26ed74e --- /dev/null +++ b/httemplate/elements/checkbox-tristate.html @@ -0,0 +1,78 @@ +<%doc> +A tristate checkbox (with three values: true, false, and null). +Internally, this creates a checkbox, coupled via javascript to a hidden +field that actually contains the value. For now, the only values these +can have are 1, 0, and empty. Clicking the checkbox cycles between them. +</%doc> +<%shared> +my $init = 0; +</%shared> +% if ( !$init ) { +% $init = 1; +<SCRIPT TYPE="text/javascript"> +function tristate_onclick() { + var checkbox = this; + var input = checkbox.input; + if ( input.value == "" ) { + input.value = "0"; + checkbox.checked = false; + checkbox.indeterminate = false; + } else if ( input.value == "0" ) { + input.value = "1"; + checkbox.checked = true; + checkbox.indeterminate = false; + } else if ( input.value == "1" ) { + input.value = ""; + checkbox.checked = true; + checkbox.indeterminate = true + } +} + +var tristates = []; +var tristate_boxes = []; +window.onload = function() { // don't do this until all of the checkboxes exist +%# tristates = document.getElementsByClassName('tristate'); # curse you, IE8 + var all_inputs = document.getElementsByTagName('input'); + for (var i=0; i < all_inputs.length; i++) { + if ( all_inputs[i].className == 'tristate' ) { + tristates.push(all_inputs[i]); + } + } + for (var i=0; i < tristates.length; i++) { + tristate_boxes[i] = + document.getElementById('checkbox_' + tristates[i].name); + // make sure they can find each other + tristate_boxes[i].input = tristates[i]; + tristates[i].checkbox = tristate_boxes[i]; + // set event handler + tristate_boxes[i].onclick = tristate_onclick; + // set initial value + if ( tristates[i].value == "" ) { + tristate_boxes[i].indeterminate = true + } + if ( tristates[i].value != "0" ) { + tristate_boxes[i].checked = true; + } + } +}; +</SCRIPT> +% } # end of $init +<INPUT TYPE="hidden" NAME="<% $opt{field} %>" + ID="<% $opt{id} %>" + VALUE="<% $curr_value %>" + CLASS="tristate"> +<INPUT TYPE="checkbox" ID="checkbox_<%$opt{field}%>" CLASS="partial"> +<%init> + +my %opt = @_; + +# might be useful but I'm not implementing it yet +#my $onchange = $opt{'onchange'} +# ? 'onChange="'. $opt{'onchange'}. '(this)"' +# : ''; + +$opt{'id'} ||= 'hidden_'.$opt{'field'}; +my $curr_value = $opt{curr_value}; +$curr_value = undef + unless $curr_value eq '0' or $curr_value eq '1'; +</%init> diff --git a/httemplate/elements/contact.html b/httemplate/elements/contact.html index 490ba2303..3d5177612 100644 --- a/httemplate/elements/contact.html +++ b/httemplate/elements/contact.html @@ -2,9 +2,9 @@ <INPUT TYPE="hidden" NAME="<%$name%>" ID="<%$id%>" VALUE="<% $curr_value %>"> - <TABLE> + <TABLE STYLE="display:inline"> <TR> -% if ( @contact_class ) { +% if ( @contact_class && ! $opt{name_only} ) { <TD> <SELECT NAME="<%$name%>_classnum" <% $onchange %>> <OPTION VALUE=""> @@ -106,6 +106,6 @@ foreach my $phone_type ( qsearch({table=>'phone_type', order_by=>'weight'}) ) { $label{'comment'} = 'Comment'; -my @fields = keys %label; +my @fields = $opt{'name_only'} ? qw( first last ) : keys %label; </%init> diff --git a/httemplate/elements/dashboard-toplist.html b/httemplate/elements/dashboard-toplist.html index f4a372519..b80af7883 100644 --- a/httemplate/elements/dashboard-toplist.html +++ b/httemplate/elements/dashboard-toplist.html @@ -169,7 +169,6 @@ if ( $FS::TicketSystem::system eq 'RT_Internal' ObjectCustomFieldValues.ObjectId = cust_tickets.Id ) GROUP BY cust_tickets.custnum, ObjectCustomFieldValues.Content"; - #warn $sql."\n"; } else { # no custom_priority_field $sql = "SELECT cust_tickets.custnum, @@ -181,10 +180,8 @@ if ( $FS::TicketSystem::system eq 'RT_Internal' my $sth = dbh->prepare($sql) or die dbh->errstr; $sth->execute or die $sth->errstr; while ( my $row = $sth->fetchrow_hashref ) { - #warn to_json($row)."\n"; $num_tickets_by_priority{ $row->{priority} }->{ $row->{custnum} } = $row->{num_tickets}; } } -#warn Dumper \%num_tickets_by_priority; </%init> diff --git a/httemplate/elements/fckeditor/fckeditor.js b/httemplate/elements/fckeditor/fckeditor.js index 8e0126bae..eb7d339af 100644 --- a/httemplate/elements/fckeditor/fckeditor.js +++ b/httemplate/elements/fckeditor/fckeditor.js @@ -304,7 +304,7 @@ function FCKeditor_IsCompatibleBrowser() // Internet Explorer 5.5+
if ( /*@cc_on!@*/false && sAgent.indexOf("mac") == -1 )
{
- var sBrowserVersion = navigator.appVersion.match(/MSIE (.\..)/)[1] ;
+ var sBrowserVersion = navigator.appVersion.match(/MSIE ([\d.]+)/)[1] ;
return ( sBrowserVersion >= 5.5 ) ;
}
diff --git a/httemplate/elements/location.html b/httemplate/elements/location.html index 873fe1621..685523314 100644 --- a/httemplate/elements/location.html +++ b/httemplate/elements/location.html @@ -200,7 +200,7 @@ Example: </TR> % } else { % foreach (qw(latitude longitude)) { -<INPUT TYPE="hidden" NAME="<% $_ %>" VALUE="<% $object->get($_) |h%>"> +<INPUT TYPE="hidden" NAME="<% $_ %>" ID="<% $_ %>" VALUE="<% $object->get($_) |h%>"> % } % } <INPUT TYPE="hidden" NAME="<%$pre%>coord_auto" VALUE="<% $object->coord_auto %>"> @@ -226,12 +226,13 @@ Example: <TD COLSPAN=8> <INPUT TYPE="text" SIZE=15 NAME="<%$pre%>district" + ID="<%$pre%>district" VALUE="<% $object->district |h %>"> <% '(automatic)' %> </TD> </TR> % } else { - <INPUT TYPE="hidden" NAME="<%$pre%>district" VALUE="<% $object->district %>"> + <INPUT TYPE="hidden" ID="<%$pre%>" NAME="<%$pre%>district" VALUE="<% $object->district %>"> % } % } @@ -239,7 +240,7 @@ Example: %# keep a clean copy of the address so we know if we need %# to re-standardize % foreach (qw(address1 city state country zip latitude -% longitude censustract addr_clean) ) { +% longitude censustract district addr_clean) ) { <INPUT TYPE="hidden" NAME="old_<%$pre.$_%>" ID="old_<%$pre.$_%>" VALUE="<% $object->get($_) |h%>"> % } %# Placeholders diff --git a/httemplate/elements/menu.html b/httemplate/elements/menu.html index 4e6109687..5689b12d2 100644 --- a/httemplate/elements/menu.html +++ b/httemplate/elements/menu.html @@ -194,7 +194,7 @@ foreach my $svcdb ( FS::part_svc->svc_tables() ) { } elsif ( $svcdb eq 'svc_phone' ) { $report_svc{"${name}' total usage by time period"} = - [ $fsurl. 'search/report_svc_phone.html', + [ $fsurl. 'search/report_svc_phone_usage.html', 'Total usage (minutes, and amount billed) for the specified time period, per phone number.', ]; @@ -209,7 +209,7 @@ foreach my $svcdb ( FS::part_svc->svc_tables() ) { $report_svc{"Advanced $lcsname reports"} = [ $fsurl."search/report_$svcdb.html", '' ] - if $svcdb =~ /^svc_(acct|broadband|hardware)$/ + if $svcdb =~ /^svc_(acct|broadband|hardware|phone)$/ && $curuser->access_right("Services: $name: Advanced search"); if ( $svcdb eq 'svc_phone' ) { @@ -236,7 +236,7 @@ tie my %report_packages, 'Tie::IxHash'; $report_packages{'Package definitions (by # active)'} = [ $fsurl.'browse/part_pkg.cgi?active=1', 'Package definitions by number of active packages' ] if $curuser->access_right('Edit package definitions') || $curuser->access_right('Edit global package definitions'); -$report_packages{'Package Costs Report'} = [ $fsurl.'graph/report_cust_pkg_cost.html', 'Package setup and recurring costs graph' ] +$report_packages{'Package costs'} = [ $fsurl.'graph/report_cust_pkg_cost.html', 'Package setup and recurring costs graph' ] if $curuser->access_right('Financial reports'); $report_packages{'separator'} = '' if keys %report_packages; @@ -294,9 +294,11 @@ tie my %report_ticketing, 'Tie::IxHash', 'Advanced ticket reports' => [ $fsurl.'rt/Search/Build.html?NewQuery=1', 'List tickets by any criteria' ], ; -tie my %report_employees, 'Tie::IxHash', - 'Employee Commission Report' => [ $fsurl.'search/report_employee_commission.html', '' ], - 'Employee Audit Report' => [ $fsurl.'search/report_employee_audit.html', 'Employee audit report' ], +tie my %report_employees, 'Tie::IxHash'; +$report_employees{'Employee Commission Report'} = [ $fsurl.'search/report_employee_commission.html', '' ] + if $curuser->access_right('Employees: Commission Report'); +$report_employees{'Employee Audit Report'} = [ $fsurl.'search/report_employee_audit.html', 'Employee audit report' ] + if $curuser->access_right('Employees: Audit Report'); ; tie my %report_bill_event, 'Tie::IxHash', @@ -336,7 +338,8 @@ tie my %report_sales, 'Tie::IxHash', 'Daily Sales, Credits and Receipts' => [ $fsurl.'graph/report_money_time_daily.html', 'Sales, credits and receipts (broken down by day) summary graph' ], 'Sales Report' => [ $fsurl.'graph/report_cust_bill_pkg.html', 'Sales report and graph (by agent, package class and/or date range)' ], 'Rated Call Sales Report' => [ $fsurl.'graph/report_cust_bill_pkg_detail.html', 'Sales report and graph (by agent, package class, usage class and/or date range)' ], - 'Sales With Advertising Source' => [ $fsurl.'search/report_cust_bill_pkg_referral.html' ], + 'Sales with Advertising Source' => [ $fsurl.'search/report_cust_bill_pkg_referral.html' ], + 'Sales with Agent Commissions' => [ $fsurl.'search/report_agent_commission.html' ], ; tie my %report_financial, 'Tie::IxHash'; @@ -396,7 +399,7 @@ $report_menu{'Tickets'} = [ \%report_ticketing, 'Ticket reports' ] if $conf->config('ticket_system') ;#&& FS::TicketSystem->access_right(\%session, 'Something'); $report_menu{'Employees'} = [ \%report_employees, 'Employee reports' ] - if $curuser->access_right('Financial reports'); + if keys %report_employees; $report_menu{'Billing events'} = [ \%report_bill_event, 'Billing events' ] if $curuser->access_right('Billing event reports'); $report_menu{'Financial'} = [ \%report_financial, 'Financial reports' ] @@ -463,6 +466,8 @@ $tools_menu{'Job Queue'} = [ $fsurl.'search/queue.html', 'View pending job queu if $curuser->access_right('Job queue'); $tools_menu{'Ticketing'} = [ \%tools_ticketing, 'Ticketing tools' ] if $conf->config('ticket_system'); +$tools_menu{'Customer email settings'} = [ $fsurl.'misc/manage_cust_email.html' ] + if $curuser->access_right('Edit customer'); $tools_menu{'Business card scan'} = [ $fsurl.'edit/prospect_main-upload.html' ] if $curuser->access_right('New prospect'); $tools_menu{'Time Queue'} = [ $fsurl.'search/report_timeworked.html', 'View pending support time' ] @@ -649,7 +654,7 @@ $config_misc{'Advertising sources'} = [ $fsurl.'browse/part_referral.html', 'Whe || $curuser->access_right('Edit global advertising sources'); if ( $curuser->access_right('Configuration') ) { $config_misc{'Custom fields'} = [ $fsurl.'browse/part_virtual_field.html', 'Locally defined fields', ]; - $config_misc{'Message catalog'} = [ $fsurl.'browse/msgcat.html', 'Change error messages and other customizable labels for each locale' ]; + $config_misc{'Translation strings'} = [ $fsurl.'browse/msgcat.html', 'Translations and other customizable labels for each locale' ]; } $config_misc{'Inventory classes and inventory'} = [ $fsurl.'browse/inventory_class.html', 'Setup inventory classes and stock inventory' ] if $curuser->access_right('Edit inventory') diff --git a/httemplate/elements/order_pkg.js b/httemplate/elements/order_pkg.js index 8c1efd93a..1069a0ee4 100644 --- a/httemplate/elements/order_pkg.js +++ b/httemplate/elements/order_pkg.js @@ -44,4 +44,5 @@ function standardize_new_location() { function submit_abort() { document.OrderPkgForm.submitButton.disabled = false; + nd(1); } diff --git a/httemplate/elements/progress-init.html b/httemplate/elements/progress-init.html index 7a282a34c..cef54b824 100644 --- a/httemplate/elements/progress-init.html +++ b/httemplate/elements/progress-init.html @@ -108,7 +108,7 @@ function <%$key%>process () { function <%$key%>myCallback( jobnum ) { - overlib( OLiframeContent('<%$p%>elements/progress-popup.html?jobnum=' + jobnum + ';<%$url_or_message_link%>;formname=<%$formname%>' , 444, 168, '<% $popup_name %>'), CAPTION, 'Please wait...', STICKY, AUTOSTATUSCAP, CLOSETEXT, '', CLOSECLICK, MIDX, 0, MIDY, 0 ); + overlib( OLiframeContent('<%$fsurl%>elements/progress-popup.html?jobnum=' + jobnum + ';<%$url_or_message_link%>;formname=<%$formname%>' , 444, 168, '<% $popup_name %>'), CAPTION, 'Please wait...', STICKY, AUTOSTATUSCAP, CLOSETEXT, '', CLOSECLICK, MIDX, 0, MIDY, 0 ); } diff --git a/httemplate/elements/random_pass.html b/httemplate/elements/random_pass.html new file mode 100644 index 000000000..b215b77d9 --- /dev/null +++ b/httemplate/elements/random_pass.html @@ -0,0 +1,17 @@ +<INPUT TYPE="button" VALUE="<% emt($label) %>" onclick="randomPass()"> +<SCRIPT TYPE="text/javascript"> +function randomPass() { + var i=0; + var pw_set='<% join('', 'a'..'z', 'A'..'Z', '0'..'9' ) %>'; + var pass=''; + while(i < 8) { + i++; + pass += pw_set.charAt(Math.floor(Math.random() * pw_set.length)); + } + document.getElementById('<% $id %>').value = pass; +} +</SCRIPT> +<%init> +my $id = shift; +my $label = shift || 'Generate'; +</%init> diff --git a/httemplate/elements/search-svc_broadband.html b/httemplate/elements/search-svc_broadband.html new file mode 100644 index 000000000..d83516172 --- /dev/null +++ b/httemplate/elements/search-svc_broadband.html @@ -0,0 +1,204 @@ +<%doc> + +Example: + + include( '/elements/search-svc_broadband.html, + 'field' => 'svcnum', + #slightly deprecated old synonym for field#'field_name'=>'svcnum', + 'find_button' => 1, #add a "find" button to the field + 'curr_value' => 54, #current value + 'value => 32, #deprecated synonym for curr_value + ); + +</%doc> +<INPUT TYPE="hidden" NAME="<% $field %>" ID="<% $field %>" VALUE="<% $value %>"> + +<!-- some false laziness w/ misc/batch-cust_pay.html, though not as bad as i'd thought at first... --> + +<INPUT TYPE = "text" + NAME = "<% $field %>_search" + ID = "<% $field %>_search" + SIZE = "32" + VALUE="<% $svc_broadband ? $svc_broadband->label : '(svcnum, ip or mac)' %>" + onFocus="clearhint_<% $field %>_search(this);" + onClick="clearhint_<% $field %>_search(this);" + onChange="smart_<% $field %>_search(this);" +> + +% if ( $opt{'find_button'} ) { + <INPUT TYPE = "button" + VALUE = 'Find', + NAME = "<% $field %>_findbutton" + onClick = "smart_<% $field %>_search(this.form.<% $field %>_search);" + > +% } + +<SELECT NAME="<% $field %>_select" ID="<% $field %>_select" STYLE="color:#ff0000; display:none" onChange="select_<% $field %>(this);"> +</SELECT> + +<% include('/elements/xmlhttp.html', + 'url' => $p. 'misc/xmlhttp-svc_broadband-search.cgi', + 'subs' => [ 'smart_search' ], + ) +%> + +<SCRIPT TYPE="text/javascript"> + + function clearhint_<% $field %>_search (what) { + + what.style.color = '#000000'; + + if ( what.value == '(svcnum, ip or mac)' ) + what.value = ''; + + if ( what.value.indexOf('Service not found: ') == 0 ) + what.value = what.value.substr(20); + + } + + var <% $field %>_search_active = false; + + function smart_<% $field %>_search(what) { + + if ( <% $field %>_search_active ) + return; + + var service = what.value; + + if ( service == 'searching...' || service == '' + || service.indexOf('Service not found: ') == 0 ) + return; + + if ( what.getAttribute('magic') == 'nosearch' ) { + what.setAttribute('magic', ''); + return; + } + + //what.value = 'searching...' + what.disabled = true; + what.style.color= '#000000'; + what.style.backgroundColor = '#dddddd'; + + var service_select = document.getElementById('<% $field %>_select'); + + //alert("search for customer " + customer); + + function <% $field %>_search_update(services) { + + //alert('customers returned: ' + customers); + + var serviceArray = eval('(' + services + ')'); + + what.disabled = false; + what.style.backgroundColor = '#ffffff'; + + if ( serviceArray.length == 0 ) { + + what.form.<% $field %>.value = ''; + + what.value = 'Service not found: ' + what.value; + what.style.color = '#ff0000'; + + what.style.display = ''; + service_select.style.display = 'none'; + + } else if ( serviceArray.length == 1 ) { + + //alert('one customer found: ' + customerArray[0]); + + what.form.<% $field %>.value = serviceArray[0][0]; + what.value = serviceArray[0][1]; + + what.style.display = ''; + service_select.style.display = 'none'; + + } else { + + //alert('multiple customers found, have to create select dropdown'); + + //blank the current list + for ( var i = service_select.length; i >= 0; i-- ) + service_select.options[i] = null; + + opt(service_select, '', 'Multiple services match "' + service + '" - select one', '#ff0000'); + + //add the multiple services + for ( var s = 0; s < serviceArray.length; s++ ) + opt(service_select, serviceArray[s][0], serviceArray[s][1], '#000000'); + + opt(service_select, 'cancel', '(Edit search string)', '#000000'); + + what.style.display = 'none'; + service_select.style.display = ''; + + } + + <% $field %>_search_active = false; + + } + + <% $field %>_search_active = true; + + smart_search( service, <% $field %>_search_update ); + + + } + + function select_<% $field %> (what) { + + var svcnum = what.options[what.selectedIndex].value; + var service = what.options[what.selectedIndex].text; + + var service_obj = document.getElementById('<% $field %>_search'); + + if ( svcnum == '' ) { + //what.style.color = '#ff0000'; + + } else if ( svcnum == 'cancel' ) { + + service_obj.style.color = '#000000'; + + what.style.display = 'none'; + service_obj.style.display = ''; + service_obj.focus(); + + } else { + + what.form.<% $field %>.value = svcnum; + + service_obj.value = service; + service_obj.style.color = '#000000'; + + what.style.display = 'none'; + service_obj.style.display = ''; + + } + + } + + function opt(what,value,text,color) { + var optionName = new Option(text, value, false, false); + optionName.style.color = color; + var length = what.length; + what.options[length] = optionName; + } + +</SCRIPT> +<%init> + +my( %opt ) = @_; + +my $field = $opt{'field'} || $opt{'field_name'} || 'svcnum'; + +my $value = $opt{'curr_value'} || $opt{'value'}; + +my $svc_broadband = ''; +if ( $value ) { + $svc_broadband = qsearchs({ + 'table' => 'svc_broadband', + 'hashref' => { 'svcnum' => $value }, + #have to join to cust_main for an agentnum 'extra_sql' => " AND ". $FS::CurrentUser::CurrentUser->agentnums_sql, + }); +} + +</%init> diff --git a/httemplate/elements/select-areacode.html b/httemplate/elements/select-areacode.html index a302befc2..f0f56d56d 100644 --- a/httemplate/elements/select-areacode.html +++ b/httemplate/elements/select-areacode.html @@ -17,7 +17,7 @@ what.form.<% $opt{'prefix'} %>areacode.disabled = 'disabled'; what.form.<% $opt{'prefix'} %>areacode.style.display = 'none'; var areacodewait = document.getElementById('<% $opt{'prefix'} %>areacodewait'); - areacodewait.style.display = ''; + areacodewait.style.display = 'inline'; var areacodeerror = document.getElementById('<% $opt{'prefix'} %>areacodeerror'); areacodeerror.style.display = 'none'; @@ -61,7 +61,7 @@ what.form.<% $opt{'prefix'} %>areacode.style.display = ''; } else { var areacodeerror = document.getElementById('<% $opt{'prefix'} %>areacodeerror'); - areacodeerror.style.display = ''; + areacodeerror.style.display = 'inline'; } //run the callback diff --git a/httemplate/elements/select-did.html b/httemplate/elements/select-did.html index 6e205d8ff..c39603156 100644 --- a/httemplate/elements/select-did.html +++ b/httemplate/elements/select-did.html @@ -18,6 +18,28 @@ Example: <TABLE> <TR> +% my( $phonenum_checked, $manual_checked ) = ( '', '' ); +% if ( $export->get_dids_can_manual ) { +% #not 100% perfect UI on error handling, but it'll do +% if ( $opt{'curr_value'} ) { +% $phonenum_checked = ''; +% $manual_checked = 'CHECKED'; +% } else { +% $phonenum_checked = 'CHECKED'; +% $manual_checked = ''; +% } + + <TD VALIGN="top"> + <INPUT TYPE = "radio" + NAME = "phonenum_which" + VALUE = "phonenum" + onChange = "phonenum_which_changed(this)" + onClick = "phonenum_which_changed(this)" + <% $phonenum_checked %> + > Inventory + </TD> +% } + % if ( $export->get_dids_npa_select ) { <TD VALIGN="top"> @@ -27,9 +49,10 @@ Example: 'svcpart' => $svcpart, 'disable_empty' => 0, 'empty_label' => 'Select state', + 'disabled' => ( $manual_checked ? 1 : 0 ), ) %> - <BR><FONT SIZE="-1">State</FONT> + <BR><FONT SIZE="-1" ID="phonenum_state_label" <% $manual_checked ? 'STYLE="color:#999999"' : '' %>>State</FONT> </TD> <TD VALIGN="top"> @@ -39,19 +62,24 @@ Example: 'empty' => 'Select area code', ) %> - <BR><FONT SIZE="-1">Area code</FONT> + <BR><FONT SIZE="-1" ID="areacode_label" <% $manual_checked ? 'STYLE="color:#999999"' : '' %>>Area code</FONT> </TD> <TD VALIGN="top"> <% include('/elements/select-exchange.html', - 'svcpart' => $svcpart, - 'empty' => 'Select exchange', + 'svcpart' => $svcpart, + 'empty' => 'Select exchange', ) %> - <BR><FONT SIZE="-1">City / Exchange</FONT> + <BR><FONT SIZE="-1" ID="exchange_label" <% $manual_checked ? 'STYLE="color:#999999"' : '' %>>City / Exchange</FONT> </TD> % } else { +% +% #this code path currently only being used by fibernetics +% # should change "Province" label to "State" or make it configurable +% # if/when other folks need an areacode-less DID selector that goes +% # directly from state to region <TD VALIGN="top"> <% include('/elements/select.html', @@ -60,9 +88,10 @@ Example: 'options' => [ '', @{ $export->get_dids } ], 'labels' => { '' => 'Select province' }, 'onchange' => 'phonenum_state_changed(this);', + 'disabled' => ( $manual_checked ? 1 : 0 ), ) %> - <BR><FONT SIZE="-1">Province</FONT> + <BR><FONT SIZE="-1" ID="phonenum_state_label" <% $manual_checked ? 'STYLE="color:#999999"' : '' %>>Province</FONT> </TD> <TD VALIGN="top"> @@ -72,7 +101,7 @@ Example: 'empty' => 'Select region', ) %> - <BR><FONT SIZE="-1">Region</FONT> + <BR><FONT SIZE="-1" ID="region_label" <% $manual_checked ? 'STYLE="color:#999999"' : '' %>>Region</FONT> </TD> % } @@ -86,10 +115,132 @@ Example: 'region' => ! $export->get_dids_npa_select, ) %> - <BR><FONT SIZE="-1">Phone number</FONT> + <BR><FONT SIZE="-1" ID="phonenum_phonenum_label" <% $manual_checked ? 'STYLE="color:#999999"' : '' %>>Phone number</FONT> </TD> </TR> + +% if ( $export->get_dids_can_manual ) { + <TR> + + <TD VALIGN="top"> + <INPUT TYPE = "radio" + NAME = "phonenum_which" + VALUE = "phonenum_manual" + onChange = "phonenum_which_changed(this)" + onClick = "phonenum_which_changed(this)" + <% $manual_checked %> + > Manual entry + </TD> + + <TD VALIGN="top" COLSPAN=4> + <& /elements/input-text.html, + %opt, + field => 'phonenum_manual', + id => 'phonenum_manual', + type => 'text', + disabled => ( $phonenum_checked ? 1 : 0 ), + &> + </TD> + </TR> + + <SCRIPT TYPE="text/javascript"> + function phonenum_which_changed(what) { + + if ( what.value == 'phonenum' && what.checked ) { + + what.form.phonenum_manual.disabled = true; + what.form.phonenum_manual.style.backgroundColor = '#dddddd'; + + what.form.phonenum_state.disabled = false; + + document.getElementById('phonenum_state_label').style.color = '#000000'; + if ( document.getElementById('areacode_label') ) { + document.getElementById('areacode_label').style.color = '#000000'; + } + if ( document.getElementById('exchange_label') ) { + document.getElementById('exchange_label').style.color = '#000000'; + } + if ( document.getElementById('region_label') ) { + document.getElementById('region_label').style.color = '#000000'; + } + document.getElementById('phonenum_phonenum_label').style.color = '#000000'; + + var value = what.form.phonenum_state.options[ what.form.phonenum_state.selectedIndex].value; + + if ( value != '' ) { + + if ( what.form.areacode ) { + what.form.areacode.disabled = false; + + var areacode_value = what.form.areacode.options[ what.form.areacode.selectedIndex].value; + + if ( areacode_value != '' ) { + what.form.exchange.disabled = false; + + var exchange_value = what.form.exchange.options[ what.form.exchange.selectedIndex].value; + + if ( exchange_value != '' ) { + what.form.phonenum.disabled = false; + } + + } + + } + if ( what.form.region ) { + what.form.region.disabled = false; + + var region_value = what.form.region.options[ what.form.region.selectedIndex].value; + + if ( region_value != '' ) { + what.form.phonenum.disabled = false; + } + + } + + } + + } + + if ( what.value == 'phonenum_manual' && what.checked ) { + + what.form.phonenum_manual.disabled = false; + what.form.phonenum_manual.style.backgroundColor = '#ffffff'; + + what.form.phonenum_state.disabled = true; + + document.getElementById('phonenum_state_label').style.color = '#999999'; + if ( document.getElementById('areacode_label') ) { + document.getElementById('areacode_label').style.color = '#999999'; + } + if ( document.getElementById('exchange_label') ) { + document.getElementById('exchange_label').style.color = '#999999'; + } + if ( document.getElementById('region_label') ) { + document.getElementById('region_label').style.color = '#999999'; + } + document.getElementById('phonenum_phonenum_label').style.color = '#999999'; + + if ( what.form.areacode ) { + what.form.areacode.disabled = true; + } + + if ( what.form.exchange ) { + what.form.exchange.disabled = true; + } + + if ( what.form.region ) { + what.form.region.disabled = true; + } + + what.form.phonenum.disabled = true; + } + + } + </SCRIPT> + +% } + </TABLE> % } diff --git a/httemplate/elements/select-exchange.html b/httemplate/elements/select-exchange.html index 9e4b5ce97..b9677094a 100644 --- a/httemplate/elements/select-exchange.html +++ b/httemplate/elements/select-exchange.html @@ -17,7 +17,7 @@ what.form.<% $opt{'prefix'} %>exchange.disabled = 'disabled'; what.form.<% $opt{'prefix'} %>exchange.style.display = 'none'; var exchangewait = document.getElementById('<% $opt{'prefix'} %>exchangewait'); - exchangewait.style.display = ''; + exchangewait.style.display = 'inline'; var exchangeerror = document.getElementById('<% $opt{'prefix'} %>exchangeerror'); exchangeerror.style.display = 'none'; @@ -56,7 +56,7 @@ what.form.<% $opt{'prefix'} %>exchange.style.display = ''; } else { var exchangeerror = document.getElementById('<% $opt{'prefix'} %>exchangeerror'); - exchangeerror.style.display = ''; + exchangeerror.style.display = 'inline'; } //run the callback diff --git a/httemplate/elements/select-mac.html b/httemplate/elements/select-mac.html index 8b1c71fea..4b406fce0 100644 --- a/httemplate/elements/select-mac.html +++ b/httemplate/elements/select-mac.html @@ -7,7 +7,7 @@ <% include( '/elements/input-text.html', %opt, 'type'=>'text' ) %> <SELECT ID="<% $opt{'prefix'} %>sel_mac_addr" NAME="<% $opt{'prefix'} %>sel_mac_addr" - notonChange="<% $opt{'prefix'} %>mac_addr_changed(this); <% $opt{'onchange'} %>" +%# notonChange="<% $opt{'prefix'} %>mac_addr_changed(this); <% $opt{'onchange'} %>" <% $opt{'disabled'} %> STYLE="display: none"> <OPTION VALUE="">Select MAC address</OPTION> </SELECT> diff --git a/httemplate/elements/select-part_svc.html b/httemplate/elements/select-part_svc.html index 72ab7f6b0..743b2852e 100644 --- a/httemplate/elements/select-part_svc.html +++ b/httemplate/elements/select-part_svc.html @@ -13,6 +13,9 @@ my( %opt ) = @_; $opt{'records'} = delete $opt{'part_svc'} if $opt{'part_svc'}; -$opt{'records'} ||= [ qsearch( 'part_svc', {} ) ]; # { disabled=>'' } ) +my %hash = (); +$hash{'svcdb'} = $opt{'svcdb'} if $opt{'svcdb'}; + +$opt{'records'} ||= [ qsearch( 'part_svc', \%hash ) ]; # { disabled=>'' } ) </%init> diff --git a/httemplate/elements/select-phonenum.html b/httemplate/elements/select-phonenum.html index 18abe3dea..a8d9a7c3e 100644 --- a/httemplate/elements/select-phonenum.html +++ b/httemplate/elements/select-phonenum.html @@ -17,7 +17,7 @@ what.form.<% $opt{'prefix'} %>phonenum.disabled = 'disabled'; what.form.<% $opt{'prefix'} %>phonenum.style.display = 'none'; var phonenumwait = document.getElementById('<% $opt{'prefix'} %>phonenumwait'); - phonenumwait.style.display = ''; + phonenumwait.style.display = 'inline'; var phonenumerror = document.getElementById('<% $opt{'prefix'} %>phonenumerror'); phonenumerror.style.display = 'none'; @@ -54,7 +54,7 @@ what.form.<% $opt{'prefix'} %>phonenum.style.display = ''; } else { var phonenumerror = document.getElementById('<% $opt{'prefix'} %>phonenumerror'); - phonenumerror.style.display = ''; + phonenumerror.style.display = 'inline'; } //run the callback diff --git a/httemplate/elements/select-region.html b/httemplate/elements/select-region.html index 9823290db..7ed959269 100644 --- a/httemplate/elements/select-region.html +++ b/httemplate/elements/select-region.html @@ -17,7 +17,7 @@ what.form.<% $opt{'prefix'} %>region.disabled = 'disabled'; what.form.<% $opt{'prefix'} %>region.style.display = 'none'; var regionwait = document.getElementById('<% $opt{'prefix'} %>regionwait'); - regionwait.style.display = ''; + regionwait.style.display = 'inline'; var regionerror = document.getElementById('<% $opt{'prefix'} %>regionerror'); regionerror.style.display = 'none'; @@ -56,7 +56,7 @@ what.form.<% $opt{'prefix'} %>region.style.display = ''; } else { var regionerror = document.getElementById('<% $opt{'prefix'} %>regionerror'); - regionerror.style.display = ''; + regionerror.style.display = 'inline'; } //run the callback diff --git a/httemplate/elements/select-table.html b/httemplate/elements/select-table.html index c0cd7a50b..b6c1573d1 100644 --- a/httemplate/elements/select-table.html +++ b/httemplate/elements/select-table.html @@ -8,7 +8,7 @@ Example: # required ## 'table' => 'table_name', - 'name_col' => 'name_column', + 'name_col' => 'name_column', #or method if you pass an order_by #strongly recommended (you want your forms to be "sticky" on errors, right?) 'curr_value' => 'current_value', @@ -111,6 +111,7 @@ Example: <% $opt{'label_callback'} ? &{ $opt{'label_callback'} }( $record ) : $record->$name_col() + |h %> % } diff --git a/httemplate/elements/select-tiered.html b/httemplate/elements/select-tiered.html index e332eeff8..3ff5471ae 100644 --- a/httemplate/elements/select-tiered.html +++ b/httemplate/elements/select-tiered.html @@ -124,13 +124,6 @@ my %opt = @_; my $pre = $opt{prefix} || ''; my $tiers = $opt{tiers} or die "no tiers defined"; -#my $json = JSON->new()->canonical(); #sort -# something super weird and broken going on with JSON's auto-loading, just -# using JSON alone errors out with -# Can't locate object method "new" via package "null" (perhaps you forgot to -# load "null"?) -# yes, "null", not "JSON". so instead, using JSON::XS explicity... -use JSON::XS; my $json = JSON::XS->new(); $json->canonical; diff --git a/httemplate/elements/selectlayers.html b/httemplate/elements/selectlayers.html index 01fd590ca..cb1d2d619 100644 --- a/httemplate/elements/selectlayers.html +++ b/httemplate/elements/selectlayers.html @@ -236,7 +236,7 @@ sub layer_callback { $date_noinit = 1; } else { - $include = "input-$include" if $include =~ /^(text|money)$/; + $include = "input-$include" if $include =~ /^(text|money|percentage)$/; $include = "tr-$include" unless $include eq 'hidden'; $html .= include( "/elements/$include.html", %$lf, diff --git a/httemplate/elements/standardize_locations.js b/httemplate/elements/standardize_locations.js index 15c5761a0..e98039d9d 100644 --- a/httemplate/elements/standardize_locations.js +++ b/httemplate/elements/standardize_locations.js @@ -7,8 +7,8 @@ function status_message(text, caption) { function form_address_info() { var cf = document.<% $formname %>; - var returnobj = { onlyship: <% $onlyship ? 1 : 0 %> }; -% if ( !$onlyship ) { + var returnobj = { billship: <% $billship %> }; +% if ( $billship ) { returnobj['same'] = cf.elements['same'].checked; % } % if ( $withfirm ) { @@ -59,16 +59,12 @@ function standardize_locations() { cf.elements['<% $pre %>coord_auto'].value = 'Y'; changed = true; } - -% } #foreach $pre - // standardize if the old address wasn't clean - if ( cf.elements['old_ship_addr_clean'].value == '' || - cf.elements['old_bill_addr_clean'].value == '' ) { - + if ( cf.elements['<% $pre %>addr_clean'].value == '' ) { changed = true; - } +% } #foreach $pre + // or if it was clean but has been changed for (var key in address_info) { var old_el = cf.elements['old_'+key]; @@ -81,7 +77,7 @@ function standardize_locations() { % # If address hasn't been changed, auto-confirm the existing value of % # censustract so that we don't ask the user to confirm it again. - if ( !changed ) { + if ( !changed && <% $withcensus %> ) { if ( address_info['same'] ) { cf.elements['bill_censustract'].value = address_info['bill_censustract']; @@ -195,12 +191,14 @@ function post_standardization() { % if ( $conf->exists('enable_taxproducts') ) { + var cf = document.<% $formname %>; + if ( new String(cf.elements['<% $taxpre %>zip'].value).length < 10 ) { var country_el = cf.elements['<% $taxpre %>country']; var country = country_el.options[ country_el.selectedIndex ].value; - var geocode = cf.elements['geocode'].value; + var geocode = cf.elements['bill_geocode'].value; if ( country == 'CA' || country == 'US' ) { @@ -222,14 +220,14 @@ function post_standardization() { } else { - cf.elements['geocode'].value = 'DEFAULT'; + cf.elements['bill_geocode'].value = 'DEFAULT'; <% $post_geocode %>; } } else { - cf.elements['geocode'].value = ''; + cf.elements['bill_geocode'].value = ''; <% $post_geocode %>; } @@ -254,14 +252,14 @@ function update_geocode() { cf.elements['<% $taxpre %>city'].value = argsHash['city']; setselect(cf.elements['<% $taxpre %>state'], argsHash['state']); cf.elements['<% $taxpre %>zip'].value = argsHash['zip']; - cf.elements['geocode'].value = argsHash['geocode']; + cf.elements['bill_geocode'].value = argsHash['geocode']; <% $post_geocode %>; } // popup a chooser - overlib( OLresponseAJAX, CAPTION, 'Select tax location', STICKY, AUTOSTATUSCAP, CLOSETEXT, '', MIDX, 0, MIDY, 0, DRAGGABLE, WIDTH, 576, HEIGHT, 268, BGCOLOR, '#333399', CGCOLOR, '#333399', TEXTSIZE, 3 ); + overlib( OLresponseAJAX, CAPTION, 'Select tax location', STICKY, AUTOSTATUSCAP, CLOSETEXT, '', MIDX, 0, MIDY, 0, WIDTH, 576, HEIGHT, 268, BGCOLOR, '#333399', CGCOLOR, '#333399', TEXTSIZE, 3 ); } @@ -279,21 +277,18 @@ function setselect(el, value) { my %opt = @_; my $conf = new FS::Conf; -my $withfirm = 1; -my $withcensus = 1; +my $withfirm = $opt{'with_firm'} ? 1 : 0; +my $withcensus = $opt{'with_census'} ? 1 : 0; + +my @prefixes = ''; +my $billship = $opt{'billship'} ? 1 : 0; # whether to have bill_ and ship_ prefixes +my $taxpre = ''; +if ($billship) { + @prefixes = qw(bill_ ship_); + $taxpre = $conf->exists('tax-ship_address') ? 'ship_' : 'bill_'; +} my $formname = $opt{form} || 'CustomerForm'; -my $onlyship = $opt{onlyship} || ''; -#my $main_prefix = $opt{main_prefix} || ''; -#my $ship_prefix = $opt{ship_prefix} || ($onlyship ? '' : 'ship_'); -# The prefixes are now 'ship_' and 'bill_'. -my $taxpre = 'bill_'; -$taxpre = 'ship_' if ( $conf->exists('tax-ship_address') || $onlyship ); my $post_geocode = $opt{callback} || 'post_geocode();'; -$withfirm = 0 if $opt{no_company}; -$withcensus = 0 if $opt{no_census}; - -my @prefixes = ('ship_'); -unshift @prefixes, 'bill_' unless $onlyship; </%init> diff --git a/httemplate/elements/tr-cust_svc.html b/httemplate/elements/tr-cust_svc.html index 1ca22f6d4..b66654f38 100644 --- a/httemplate/elements/tr-cust_svc.html +++ b/httemplate/elements/tr-cust_svc.html @@ -96,7 +96,8 @@ my $svc_unprovision_link = my $manage_link = $opt{'manage_link'}; my $manage_target = ''; if ( $part_svc->svcdb eq 'svc_broadband' and $manage_link ) { - my $ip_addr = $svc_x->ip_addr; #substitution for $manage_link + my $ip_addr = $svc_x->ip_addr; #substitution for $manage_link + my $mac_addr = $svc_x->mac_addr; # ditto $manage_link = eval(qq("$manage_link")); $opt{'manage_link_text'} ||= mt('Manage Device'); $opt{'manage_link_loc'} ||= 'bottom'; diff --git a/httemplate/elements/tr-input-beginning_ending.html b/httemplate/elements/tr-input-beginning_ending.html index 7481c9bb6..ffc903875 100644 --- a/httemplate/elements/tr-input-beginning_ending.html +++ b/httemplate/elements/tr-input-beginning_ending.html @@ -74,7 +74,7 @@ my( $input_time, $time_format, $time_hint ) = ( '', '', '' ); my( $size, $maxlength ) = ( 11, 10 ); if ( $opt{'input_time'} ) { $input_time = ', showsTime: true, timeFormat: "12"'; # http://www.dynarch.com/demos/jscalendar/doc/html/reference.html#node_sec_2.3 - $time_format = ' %k:%M:%S'; # http://www.dynarch.com/demos/jscalendar/doc/html/reference.html#node_sec_5.3.5 + $time_format = ' %H:%M:%S'; # http://www.dynarch.com/demos/jscalendar/doc/html/reference.html#node_sec_5.3.5 $time_hint = ' h:m:s'; $size = 21; $maxlength = 27; diff --git a/httemplate/elements/tr-search-svc_broadband.html b/httemplate/elements/tr-search-svc_broadband.html new file mode 100644 index 000000000..cd7c11500 --- /dev/null +++ b/httemplate/elements/tr-search-svc_broadband.html @@ -0,0 +1,15 @@ +<& tr-td-label.html, @_ &> + + <TD <% $colspan %> <% $cell_style %> ID="<% $opt{input_id} || $opt{id}.'_input0' %>"><& search-svc_broadband.html, @_ &></TD> + +</TR> + +<%init> + +my %opt = @_; + +my $cell_style = $opt{'cell_style'} ? 'STYLE="'. $opt{'cell_style'}. '"' : ''; + +my $colspan = $opt{'colspan'} ? 'COLSPAN="'.$opt{'colspan'}.'"' : ''; + +</%init> diff --git a/httemplate/elements/tr-select-contact.html b/httemplate/elements/tr-select-contact.html new file mode 100644 index 000000000..d6bc67f36 --- /dev/null +++ b/httemplate/elements/tr-select-contact.html @@ -0,0 +1,204 @@ +<%doc> + +Example: + + include('/elements/tr-select-contact.html', + 'cgi' => $cgi, + + 'cust_main' => $cust_main, + #or + 'prospect_main' => $prospect_main, + + #optional + 'empty_label' => '(default contact)', + ) + +</%doc> + +<SCRIPT TYPE="text/javascript"> + + function contact_disable(what) { +% for (@contact_fields) { + what.form.<%$_%>.disabled = true; + var ftype = what.form.<%$_%>.tagName; + if( ftype == 'SELECT') changeSelect(what.form.<%$_%>, ''); + else what.form.<%$_%>.value = ''; + if( ftype != 'SELECT') what.form.<%$_%>.style.backgroundColor = '#dddddd'; +% } + } + + function contact_clear(what) { +% for (@contact_fields) { + var ftype = what.form.<%$_%>.tagName; + if( ftype == 'INPUT' ) what.form.<%$_%>.value = ''; +% } + } + + function contact_enable(what) { +% for (@contact_fields) { + what.form.<%$_%>.disabled = false; + var ftype = what.form.<%$_%>.tagName; + if( ftype != 'SELECT') what.form.<%$_%>.style.backgroundColor = '#ffffff'; +% } + } + + function contactnum_changed(what) { + var contactnum = what.options[what.selectedIndex].value; + if ( contactnum == -1 ) { //Add new contact + contact_clear(what); + + contact_enable(what); + return; + } + +% if ( $editable ) { + if ( contactnum == 0 ) { +% } + +% #sleep/wait until dropdowns are updated? + contact_disable(what); + +% if ( $editable ) { + } else { + +% #sleep/wait until dropdowns are updated? + contact_enable(what); + + } +% } + + } + + function changeSelect(what, value) { + for ( var i=0; i<what.length; i++) { + if ( what.options[i].value == value ) { + what.selectedIndex = i; + } + } + } + +</SCRIPT> + +<TR> + <<%$th%> ALIGN="right" VALIGN="top"><% $opt{'label'} || emt('Service contact') %></<%$th%>> + <TD VALIGN="top" COLSPAN=7> + <SELECT NAME = "contactnum" + ID = "contactnum" + STYLE = "vertical-align:top;margin:3px" + onchange = "contactnum_changed(this);" + > +% if ( $cust_main ) { + <OPTION VALUE=""><% $opt{'empty_label'} || '(customer default)' |h %> +% } +% +% foreach my $contact ( @contact ) { + <OPTION VALUE="<% $contact->contactnum %>" + <% $contactnum == $contact->contactnum ? 'SELECTED' : '' %> + ><% $contact->line |h %> +% } +% if ( $addnew ) { + <OPTION VALUE="-1" + <% $contactnum == -1 ? 'SELECTED' : '' %> + >New contact +% } + </SELECT> + +<% include('/elements/contact.html', + 'object' => $contact, + #'onchange' ? probably not + 'disabled' => $disabled, + 'name_only' => 1, + ) +%> + + </TD> +</TR> + +<SCRIPT TYPE="text/javascript"> + contactnum_changed(document.getElementById('contactnum')); +</SCRIPT> +<%init> + +#based on / kinda false laziness w/tr-select-cust_contact.html + +my $conf = new FS::Conf; + +my %opt = @_; +my $cgi = $opt{'cgi'}; +my $cust_pkg = $opt{'cust_pkg'}; +my $cust_main = $opt{'cust_main'}; +my $prospect_main = $opt{'prospect_main'}; +die "cust_main or prospect_main required" unless $cust_main or $prospect_main; + +my $contactnum = ''; +if ( $cgi->param('error') ) { + $cgi->param('contactnum') =~ /^(\-?\d*)$/ or die "illegal contactnum"; + $contactnum = $1; +} else { + if ( length($opt{'curr_value'}) ) { + $contactnum = $opt{'curr_value'}; + } elsif ($prospect_main) { + my @cust_contact = $prospect_main->cust_contact; + $contactnum = $cust_contact[0]->contactnum if scalar(@cust_contact)==1; + } else { #$cust_main + $cgi->param('contactnum') =~ /^(\-?\d*)$/ or die "illegal contactnum"; + $contactnum = $1; + } +} + +##probably could use explicit controls +#my $editable = $cust_main ? 0 : 1; #could use explicit control +my $editable = 0; +my $addnew = $cust_main ? 1 : ( $contactnum>0 ? 0 : 1 ); + +my @contact_fields = map "contactnum_$_", qw( first last ); + +my $contact; #the one that shows by default in the contact edit space +if ( $contactnum && $contactnum > 0 ) { + $contact = qsearchs('contact', { 'contactnum' => $contactnum } ) + or die "unknown contactnum"; +} else { + $contact = new FS::contact; + if ( $contactnum == -1 ) { + $contact->$_( $cgi->param($_) ) foreach @contact_fields; #XXX + } elsif ( $cust_pkg && $cust_pkg->contactnum ) { + my $pkg_contact = $cust_pkg->contact_obj; + $contact->$_( $pkg_contact->$_ ) foreach @contact_fields; #XXX why are we making a new one gagain?? + $opt{'empty_label'} ||= 'package contact: '.$pkg_contact->line; + } elsif ( $cust_main ) { + $contact = new FS::contact; #I think + } +} + +my $contact_sort = sub { + lc($a->last) cmp lc($b->last) + or lc($a->first) cmp lc($b->first) +}; + +my @contact; +push @contact, $cust_main->cust_contact if $cust_main; +push @contact, $prospect_main->contact if $prospect_main; +push @contact, $contact + if !$cust_main && $contact && $contact->contactnum > 0 + && ! grep { $_->contactnum == $contact->contactnum } @contact; + +@contact = sort $contact_sort grep !$_->disabled, @contact; + +$contact = $contact[0] + if ( $prospect_main ) + && !$opt{'is_optional'} + && @contact; + +my $disabled = + ( $contactnum < 0 + || ( $editable && $contactnum ) + || ( $prospect_main + && !$opt{'is_optional'} && !@contact && $addnew + ) + ) + ? '' + : 'DISABLED'; + +my $th = $opt{'no_bold'} ? 'TD' : 'TH'; + +</%init> diff --git a/httemplate/elements/tr-select-cust_location.html b/httemplate/elements/tr-select-cust_location.html index 7ffbd6c14..780bf96ad 100644 --- a/httemplate/elements/tr-select-cust_location.html +++ b/httemplate/elements/tr-select-cust_location.html @@ -153,25 +153,16 @@ Example: } } + var location_fields = <% encode_json(\@location_fields) %>; function update_location( string ) { - var hash = eval('('+string+')'); - document.getElementById('address1').value = hash['address1']; - document.getElementById('city').value = hash['city']; - document.getElementById('zip').value = hash['zip']; - -% if ( $opt{'alt_format'} ) { - changeSelect( document.getElementById('location_kind'), hash['location_kind']); - changeSelect( document.getElementById('location_type'), hash['location_type']); - document.getElementById('location_number').value = hash['location_number']; -% } else { - document.getElementById('address2').value = hash['address2']; -% } - - var country_el = document.getElementById('country'); - - changeSelect( country_el, hash['country'] ); - - country_changed( country_el, + var hash = JSON.parse(string); + for(var i = 0; i < location_fields.length; i++) { + var f = location_fields[i]; + if (hash[f] && document.getElementById(f)) { + document.getElementById(f).value = hash[f]; + } + } + country_changed( document.getElementById('country'), fix_state_factory( hash['state'], hash['county'] ) @@ -185,7 +176,7 @@ Example: <TD COLSPAN=7> <SELECT NAME = "locationnum" ID = "locationnum" - onChange = "locationnum_changed(this);" + onchange = "locationnum_changed(this);" > % if ( $cust_main ) { <OPTION VALUE="<% $cust_main->ship_locationnum %>"><% $opt{'empty_label'} || '(default service address)' |h %> @@ -258,9 +249,7 @@ if ( $cgi->param('error') ) { my $editable = $cust_main ? 0 : 1; #could use explicit control my $addnew = $cust_main ? 1 : ( $locationnum>0 ? 0 : 1 ); -my @location_fields = qw( address1 address2 city county state zip country - latitude longitude - ); +my @location_fields = FS::cust_main->location_fields; if ( $opt{'alt_format'} ) { push @location_fields, qw( location_type location_number location_kind ); } diff --git a/httemplate/elements/tr-select-did.html b/httemplate/elements/tr-select-did.html index 987ade689..2aa712f79 100644 --- a/httemplate/elements/tr-select-did.html +++ b/httemplate/elements/tr-select-did.html @@ -1,6 +1,6 @@ <% include('tr-td-label.html', @_ ) %> -% if ( $opt{'curr_value'} ne '' && $use_selector ) { +% if ( $use_selector && $opt{'curr_value'} ne '' && ! $can_edit ) { <TD BGCOLOR="#dddddd" <% $cell_style %>><% $opt{'formatted_value'} || $opt{'curr_value'} || $opt{'value'} |h %></TD> @@ -38,4 +38,6 @@ if ( scalar(@exports) > 1 ) { my $use_selector = scalar(@exports) ? 1 : 0; +my $can_edit = scalar(@exports) && $exports[0]->get_dids_can_edit; + </%init> diff --git a/httemplate/elements/tr-select-discount_term.html b/httemplate/elements/tr-select-discount_term.html index e9faeb228..d4218f848 100644 --- a/httemplate/elements/tr-select-discount_term.html +++ b/httemplate/elements/tr-select-discount_term.html @@ -24,7 +24,9 @@ function change_discount_term(what) { id => 'discount_term', options => [ '', @discount_term ], labels => { '' => mt('1 month'), - map { $_ => mt('[_1] months', $_) } @discount_term }, + map { $_ => mt('[_1] months', sprintf('%.0f', $_)) } + @discount_term + }, curr_value => '', onchange => $amount_id ? 'change_discount_term(this)' : '', &> diff --git a/httemplate/elements/tr-select-from_to.html b/httemplate/elements/tr-select-from_to.html index a27412f99..ad9b40a6b 100644 --- a/httemplate/elements/tr-select-from_to.html +++ b/httemplate/elements/tr-select-from_to.html @@ -39,7 +39,7 @@ my %hash = ( 'show_month_abbr' => 1, 'start_year' => '1999', - 'end_year' => '2013', #haha, well... + 'end_year' => '2014', @_, ); </%init> diff --git a/httemplate/elements/tr-select-inventory_item.html b/httemplate/elements/tr-select-inventory_item.html new file mode 100644 index 000000000..669e85f27 --- /dev/null +++ b/httemplate/elements/tr-select-inventory_item.html @@ -0,0 +1,48 @@ +% if ( scalar(@classnums) == 0 ) { +<& tr-fixed.html, %opt &> +% } elsif ( scalar(@classnums) == 1 ) { +% $opt{'extra_sql'} .= ' AND '.$classnum_sql; +<& tr-select-table.html, + 'table' => 'inventory_item', + 'name_col' => 'item', + 'value_col' => 'item', + %opt +&> +% } else { +<& tr-td-label.html, %opt &> +<TD> +<& select-tiered.html, + 'prefix' => $opt{'field'}.'_', + 'tiers' => [ + { + field => $opt{'field'}.'_classnum', + table => 'inventory_class', + extra_sql => "WHERE $classnum_sql", + name_col => 'classname', + empty_label => '(all)', + }, + { + field => $opt{'field'}, + table => 'inventory_item', + name_col => 'item', + value_col => 'item', + link_col => 'classnum', + extra_sql => delete($opt{'extra_sql'}), + disable_empty => 1, + }, + ], + %opt, +&> +</TD> +</TR> +% } +<%init> +my %opt = @_; +my @classnums; +if (ref($opt{'classnum'})) { + @classnums = @{ $opt{'classnum'} }; +} else { + @classnums = split(',', $opt{'classnum'}); +} +my $classnum_sql = 'classnum IN('.join(',', @classnums).')'; +</%init> diff --git a/httemplate/elements/tr-select-part_svc.html b/httemplate/elements/tr-select-part_svc.html index af5148749..959ac8dd9 100644 --- a/httemplate/elements/tr-select-part_svc.html +++ b/httemplate/elements/tr-select-part_svc.html @@ -5,7 +5,7 @@ % } else { <TR> - <TD ALIGN="right"><% $opt{'label'} || 'Package definition' %></TD> + <TD ALIGN="right"><% $opt{'label'} || 'Service definition' %></TD> <TD> <% include( '/elements/select-part_svc.html', 'multiple' => 1, @@ -21,6 +21,9 @@ my( %opt ) = @_; -$opt{'part_svc'} ||= [ qsearch( 'part_svc', {} ) ]; # { disabled=>'' } ) +my %hash = (); +$hash{'svcdb'} = $opt{'svcdb'} if $opt{'svcdb'}; + +$opt{'part_svc'} ||= [ qsearch( 'part_svc', \%hash ) ]; # { disabled=>'' } ) </%init> diff --git a/httemplate/elements/tr-select-reason.html b/httemplate/elements/tr-select-reason.html index c1df10b94..9a670a26b 100755 --- a/httemplate/elements/tr-select-reason.html +++ b/httemplate/elements/tr-select-reason.html @@ -154,15 +154,12 @@ my $controlledbutton = $opt{'control_button'}; my $id = $opt{'id'} || $func_suffix; -my( $add_access_right, $access_right ); +my $add_access_right; if ($class eq 'C') { - $access_right = 'Cancel customer'; $add_access_right = 'Add on-the-fly cancel reason'; } elsif ($class eq 'S') { - $access_right = 'Suspend customer package'; $add_access_right = 'Add on-the-fly suspend reason'; } elsif ($class eq 'R') { - $access_right = 'Post credit'; $add_access_right = 'Add on-the-fly credit reason'; } else { die "illegal class: $class"; diff --git a/httemplate/elements/tr-select-voip_class.html b/httemplate/elements/tr-select-voip_class.html index dcc1487cc..afd3e1f8a 100644 --- a/httemplate/elements/tr-select-voip_class.html +++ b/httemplate/elements/tr-select-voip_class.html @@ -18,7 +18,8 @@ my @options = ( '' => '', 1 => 'VoIP without Broadband', 2 => 'VoIP with Broadband', - 3 => 'Wholesale VoIP' + 3 => 'Wholesale VoIP', + 4 => 'Local Exchange (non-VoIP)', ); </%init> diff --git a/httemplate/misc/areacodes.cgi b/httemplate/misc/areacodes.cgi index 9d32a3baf..4b31deb00 100644 --- a/httemplate/misc/areacodes.cgi +++ b/httemplate/misc/areacodes.cgi @@ -1,4 +1,4 @@ -<% objToJson(\@areacodes) %> +<% encode_json(\@areacodes) %>\ <%init> my( $state, $svcpart ) = $cgi->param('arg'); diff --git a/httemplate/misc/batch-cust_pay.html b/httemplate/misc/batch-cust_pay.html index 887b92489..0b2f1f18c 100644 --- a/httemplate/misc/batch-cust_pay.html +++ b/httemplate/misc/batch-cust_pay.html @@ -23,15 +23,21 @@ function add_row_callback(rownum, prefix) { function custnum_update_callback(rownum, prefix) { var custnum = document.getElementById('custnum'+rownum).value; - document.getElementById('enable_app'+rownum).disabled = ( - custnum == 0 || - num_open_invoices[rownum] < 2 - ); + // if there is a custnum and more than one open invoice, enable + // (and check) the box + var show_applications = (custnum > 0 && num_open_invoices[rownum] > 1); + var enable_app_checkbox = document.getElementById('enable_app'+rownum); + enable_app_checkbox.disabled = show_applications; + % if ( $use_discounts ) { select_discount_term(rownum, prefix); % } } +function invnum_update_callback(rownum, prefix) { + custnum_update_callback(rownum, prefix); +} + function select_discount_term(row, prefix) { var custnum_obj = document.getElementById('custnum'+prefix+row); var select_obj = document.getElementById('discount_term'+prefix+row); @@ -89,6 +95,17 @@ function toggle_application_row(ev, next) { next.call(this, rownum); } ); + } else { + var row = document.getElementById('row'+rownum); + var table_rows = row.parentNode.rows; + for (i = row.sectionRowIndex; i < table_rows.count; i++) { + if ( table_rows[i].id.indexof('row'+rownum+'.') > -1 ) { + table_rows.removeChild(table_rows[i]); + } else { + break; + } + } + lock_payment_row(rownum, false); } } @@ -198,7 +215,6 @@ function change_app_amount() { && amount_unapplied(rownum) > 0 ) { create_application_row(rownum, parseInt(appnum) + 1); - } } @@ -352,6 +368,7 @@ function preload() { footer_align => \@footer_align, onchange => \@onchange, custnum_update_callback => 'custnum_update_callback', + invnum_update_callback => 'invnum_update_callback', add_row_callback => 'add_row_callback', &> diff --git a/httemplate/misc/change_pkg.cgi b/httemplate/misc/change_pkg.cgi index 7b08f7b10..03e336cba 100755 --- a/httemplate/misc/change_pkg.cgi +++ b/httemplate/misc/change_pkg.cgi @@ -32,9 +32,6 @@ <& /elements/standardize_locations.html, 'form' => "OrderPkgForm", - 'onlyship' => 1, - 'no_company' => 1, - 'no_census' => 1, 'callback' => 'document.OrderPkgForm.submit();', &> diff --git a/httemplate/misc/change_pkg_contact.html b/httemplate/misc/change_pkg_contact.html new file mode 100755 index 000000000..d9da5beec --- /dev/null +++ b/httemplate/misc/change_pkg_contact.html @@ -0,0 +1,70 @@ +<& /elements/header-popup.html, mt("Change Package Contact") &> + +<& /elements/error.html &> + +<FORM ACTION="<% $p %>misc/process/change_pkg_contact.html" METHOD=POST> +<INPUT TYPE="hidden" NAME="pkgnum" VALUE="<% $pkgnum %>"> + +<% ntable('#cccccc') %> + + <TR> + <TH ALIGN="right"><% mt('Package') |h %></TH> + <TD COLSPAN=7> + <% $curuser->option('show_pkgnum') ? $cust_pkg->pkgnum.': ' : '' %><B><% $part_pkg->pkg |h %></B> - <% $part_pkg->comment |h %> + </TD> + </TR> + +% if ( $cust_pkg->contactnum ) { + <TR> + <TH ALIGN="right"><% mt('Current Contact') %></TH> + <TD COLSPAN=7> + <% $cust_pkg->contact_obj->line |h %> + </TD> + </TR> +% } + +<& /elements/tr-select-contact.html, + 'label' => mt('New Contact'), #XXX test + 'cgi' => $cgi, + 'cust_main' => $cust_pkg->cust_main, +&> + +</TABLE> + +<BR> +<INPUT TYPE = "submit" + VALUE = "<% $cust_pkg->contactnum ? mt("Change contact") : mt("Add contact") |h %>" +> + +</FORM> +</BODY> +</HTML> + +<%init> + +my $conf = new FS::Conf; + +my $curuser = $FS::CurrentUser::CurrentUser; + +die "access denied" + unless $curuser->access_right('Change customer package'); + +my $pkgnum = scalar($cgi->param('pkgnum')); +$pkgnum =~ /^(\d+)$/ or die "illegal pkgnum $pkgnum"; +$pkgnum = $1; + +my $cust_pkg = + qsearchs({ + 'table' => 'cust_pkg', + 'addl_from' => 'LEFT JOIN cust_main USING ( custnum )', + 'hashref' => { 'pkgnum' => $pkgnum }, + 'extra_sql' => ' AND '. $curuser->agentnums_sql, + }) or die "unknown pkgnum $pkgnum"; + +my $cust_main = $cust_pkg->cust_main + or die "can't get cust_main record for custnum ". $cust_pkg->custnum. + " ( pkgnum ". cust_pkg->pkgnum. ")"; + +my $part_pkg = $cust_pkg->part_pkg; + +</%init> diff --git a/httemplate/misc/choose_tax_location.html b/httemplate/misc/choose_tax_location.html index dce04c77d..23099c421 100644 --- a/httemplate/misc/choose_tax_location.html +++ b/httemplate/misc/choose_tax_location.html @@ -1,6 +1,5 @@ <FORM NAME="choosegeocodeform"> <CENTER><BR><B>Choose tax location</B><BR><BR> -<P>the geocode is:<% $header %></P> <P STYLE="<% $style %>"><% $header %></P> <SELECT NAME='geocodes' ID='geocodes' STYLE="<% $style %>"> @@ -12,7 +11,7 @@ % map { $value{$_} = $location{$_} } qw ( city state ) % if $location{country} eq 'CA'; % -% my $value = encode_entities(objToJson({ %value }) +% my $value = encode_entities(encode_json({ %value }) % ); % my $content = ''; % $content .= $location->$_. ' ' x ( $max{$_} - length($location->$_) ) diff --git a/httemplate/misc/confirm-address_standardize.html b/httemplate/misc/confirm-address_standardize.html index 57201ea5a..420e8ea1d 100644 --- a/httemplate/misc/confirm-address_standardize.html +++ b/httemplate/misc/confirm-address_standardize.html @@ -11,16 +11,14 @@ Confirm address standardization </B><BR><BR> <TABLE WIDTH="100%"> -% my @prefixes; -% if ( $old{onlyship} ) { -% @prefixes = ('ship_'); -% } elsif ( $old{same} ) { +% my @prefixes = (''); +% if ( $old{same} ) { % @prefixes = ('bill_'); -% } else { +% } elsif ( $old{billship} ) { % @prefixes = ('bill_', 'ship_'); % } % for my $pre (@prefixes) { -% my $name = $pre eq 'ship_' ? 'service' : 'billing'; +% my $name = $pre eq 'bill_' ? 'billing' : 'service'; % if ( $new{$pre.'addr_clean'} ) { <TR> <TH>Entered <%$name%> address</TH> @@ -128,6 +126,6 @@ my $q = decode_json($cgi->param('q')); my %old = %{ $q->{old} }; my %new = %{ $q->{new} }; -my $addresses = $old{onlyship} ? 'address' : 'addresses'; +my $addresses = $old{billship} ? 'addresses' : 'address'; </%init> diff --git a/httemplate/misc/confirm-cust_pkg-edit_dates.html b/httemplate/misc/confirm-cust_pkg-edit_dates.html new file mode 100755 index 000000000..8e548527a --- /dev/null +++ b/httemplate/misc/confirm-cust_pkg-edit_dates.html @@ -0,0 +1,289 @@ +<%init> +my $curuser = $FS::CurrentUser::CurrentUser; + +die "access denied" + unless $curuser->access_right('Edit customer package dates'); + +my %arg = $cgi->Vars; + +my $pkgnum = $arg{'pkgnum'}; +$pkgnum =~ /^\d+$/ or die "bad pkgnum '$pkgnum'"; +my $cust_pkg = qsearchs('cust_pkg',{'pkgnum'=>$pkgnum}); +my %hash = $cust_pkg->hash; +foreach (qw( start_date setup bill last_bill contract_end )) { + # adjourn, expire, resume not editable this way + if( $arg{$_} =~ /^\d+$/ ) { + $hash{$_} = $arg{$_}; + } elsif ( $arg{$_} ) { + $hash{$_} = parse_datetime($arg{$_}); + } else { + $hash{$_} = ''; + } +} + +my (@changes, @confirm, @errors); + +my $part_pkg = $cust_pkg->part_pkg; +my @supp_pkgs = $cust_pkg->supplemental_pkgs; +my $main_pkg = $cust_pkg->main_pkg; + +my $conf = FS::Conf->new; +my $date_format = $conf->config('date_format') || '%b %o, %Y'; +# Start date +if ( $hash{'start_date'} != $cust_pkg->get('start_date') and !$hash{'setup'} ) { + my $start = ''; + $start = time2str($date_format, $hash{'start_date'}) if $hash{'start_date'}; + my $text = 'Set this package'; + if ( @supp_pkgs ) { + $text .= ' and all its supplemental packages'; + } + $text .= ' to start billing'; + if ( $start ) { + $text .= ' on [_1].'; + push @changes, mt($text, $start); + } else { + $text .= ' immediately.'; + push @changes, mt($text); + } + push @confirm, ''; +} + +# Setup date changes +if ( $hash{'setup'} != $cust_pkg->get('setup') ) { + my $setup = time2str($date_format, $hash{'setup'}); + my $has_setup_fee = grep { $_->part_pkg->option('setup_fee',1) > 0 } + $cust_pkg, @supp_pkgs; + if ( !$hash{'setup'} ) { + my $text = 'Remove the setup date'; + $text .= ' from this and all its supplemental packages' if @supp_pkgs; + $text .= '.'; + push @changes, mt($text); + if ( $has_setup_fee ) { + push @confirm, mt('This will re-charge the customer for the setup fee.'); + } else { + push @confirm, ''; + } + } elsif ( $hash{'setup'} and !$cust_pkg->get('setup') ) { + my $text = 'Add a setup date of [_1]'; + $text .= ' to this and all its supplemental packages' if @supp_pkgs; + $text .= '.'; + push @changes, mt($text, $setup); + if ( $has_setup_fee ) { + push @confirm, mt('This will prevent charging the setup fee.'); + } else { + push @confirm, ''; + } + } else { + my $text = 'Set the setup date to [_1]'; + $text .= ' on this and all its supplemental packages' if @supp_pkgs; + $text .= '.'; + push @changes, mt($text, $setup); + push @confirm, ''; + } +} + +# Check for start date + setup date +if ( $hash{'start_date'} and $hash{'setup'} ) { + if ( $cust_pkg->get('setup') ) { + push @errors, mt('Since the package has already started billing, it '. + 'cannot have a start date.'); + } else { + push @errors, mt('You cannot set both a start date and a setup date on '. + 'the same package.'); + } +} + +# Last bill date change +if ( $hash{'last_bill'} != $cust_pkg->get('last_bill') ) { + my $last_bill = time2str($date_format, $hash{'last_bill'}); + my $name = 'last bill date'; + $name = 'last renewal date' if $part_pkg->is_prepaid; + if ( $hash{'last_bill'} ) { + push @changes, mt('Set the [_1] to [_2].', $name, $last_bill); + } else { + push @changes, mt('Remove the [_1].', $name); + } + push @confirm, ''; + # I don't think we want to adjust this on supplemental packages. +} + +# Bill date change +if ( $hash{'bill'} != $cust_pkg->get('bill') ) { + my $bill = time2str($date_format, $hash{'bill'}); + $bill = 'today' if !$hash{'bill'}; # or 'the end of today'?... + my $name = 'next bill date'; + $name = 'end of the prepaid period' if $part_pkg->is_prepaid; + push @changes, mt('Set the [_1] to [_2].', $name, $bill); + + if ( $hash{'bill'} < time and $hash{'bill'} ) { + push @confirm, + mt('The customer will be charged for the interval from [_1] until now.', + $bill); + } elsif ( !$hash{'bill'} and ($hash{'last_bill'} or $hash{'setup'}) ) { + my $last_bill = + time2str($date_format, $hash{'last_bill'} || $hash{'setup'}); + push @confirm, + mt('The customer will be charged for the interval from [_1] until now.', + $last_bill); + } else { + push @confirm, ''; + } + + if ( @supp_pkgs ) { + push @changes, ''; + if ( $cust_pkg->get('bill') and $hash{'bill'} ) { + # the package already has a bill date, so adjust the dates + # of supplementals by the same interval + my $diff = $hash{'bill'} - $cust_pkg->get('bill'); + my $sign = $diff < 0 ? -1 : 1; + $diff = $diff * $sign / 86400; + if ( $diff < 1 ) { + $diff = mt('[quant,_1,hour]', int($diff * 24)); + } else { + $diff = mt('[quant,_1,day]', int($diff)); + } + push @confirm, + mt('[_1] supplemental package will also be billed [_2] [_3].', + (@supp_pkgs > 1 ? 'Each' : 'The'), + $diff, + ($sign > 0 ? 'later' : 'earlier') + ); + } else { + # the package hasn't been billed yet, or you've set bill = null + push @confirm, + mt('[_1] supplemental package will also be billed on [_2].', + (@supp_pkgs > 1 ? 'Each' : 'The'), + $bill + ); + } + } #if @supp_pkgs + + if ( $main_pkg ) { + push @changes, ''; + push @confirm, + mt('This package is a supplemental package. The bill date of its '. + 'main package will not be adjusted.'); + } +} + +# Contract end change +if ( $hash{'contract_end'} != $cust_pkg->get('contract_end') ) { + if ( $hash{'contract_end'} ) { + my $contract_end = time2str($date_format, $hash{'contract_end'}); + push @changes, + mt('Set this package\'s contract end date to [_1]', $contract_end); + } else { + push @changes, mt('Remove this package\'s contract end date.'); + } + if ( @supp_pkgs ) { + my $text = 'This change will also apply to ' . + (@supp_pkgs > 1 ? + 'all supplemental packages.': + 'the supplemental package.'); + push @confirm, mt($text); + } else { + push @confirm, ''; + } +} + +my $title = ''; +if ( @errors ) { + $title = 'Error changing package dates'; +} else { + $title = 'Confirm date changes'; +} +</%init> +<& /elements/header-popup.html, { title => $title, etc => 'BGCOLOR=""' } &> +<STYLE TYPE="text/css"> +.error { + color: #ff0000; + font-weight: bold; + text-align: center; +} +.confirm { color: #ff0000 } +.button-container { + position: fixed; + bottom: 5px; + text-align: center; + width: 100% +} +</STYLE> +<DIV STYLE="text-align: center; padding:1em"> +<% emt('Package #') %><B><% $pkgnum %></B>: <B><% $cust_pkg->part_pkg->pkg %></B><BR> +% if ( @changes ) { + <% emt('The following changes will be made:') %> +% } else { + <% emt('No changes will be made.') %> +% } +</DIV> +<TABLE WIDTH="100%"> +% if ( @errors ) { +% foreach my $error ( @errors ) { +<TR> + <TD><IMG SRC="<%$p%>images/cross.png"></TD> + <TD CLASS="error"><% $error %></TD> +</TR> +% } +% } else { +% while (@changes, @confirm) { +% my $text = shift @changes; +% if (length $text) { +<TR> + <TD><IMG SRC="<%$p%>images/tick.png"></TD> + <TD><% $text %></TD> +</TR> +% } +% $text = shift @confirm; +% if (length $text) { +<TR> + <TD> + <INPUT TYPE="checkbox" NAME="areyousure" VALUE=1 onclick="submit_ready()"> + </TD> + <TD CLASS="confirm"><% $text %></TD> +</TR> +% } +% } +% } +</TABLE> +%# action buttons +<DIV CLASS="button-container"> + <BUTTON TYPE="button" STYLE="width:145px" ID="submit_cancel"\ + onclick="submit_cancel()"> + <IMG SRC="<%$p%>images/cross.png" ALT=""> Cancel + </BUTTON> +% if (!@errors ) { + <BUTTON TYPE="button" STYLE="width:145px" ID="submit_continue"\ + onclick="submit_continue()"> + <IMG SRC="<%$p%>images/tick.png" ALT=""> Continue + </BUTTON> +</DIV> +% } +<FORM NAME="DateEditForm" STYLE="display:none" TARGET="_parent" ACTION="<%$p%>edit/process/REAL_cust_pkg.cgi" METHOD="POST"> +% foreach (keys %hash) { +<INPUT TYPE="hidden" NAME="<%$_%>" VALUE="<% $hash{$_} |h%>"> +% } +</FORM> +<SCRIPT> +function submit_ready() { + var ready = true; + var checkboxes = document.getElementsByName('areyousure'); + var i; + for (i=0; i < checkboxes.length; i++) { + if (! checkboxes[i].checked ) { + ready = false; + } + } + document.getElementById('submit_continue').disabled = !ready; + return ready; +} +function submit_cancel() { + parent.nd(1); +} +function submit_continue() { + if ( submit_ready() ) { + document.forms.DateEditForm.submit(); + } +} +submit_ready(); +</SCRIPT> +<& /elements/footer.html &> diff --git a/httemplate/misc/cust-part_pkg.cgi b/httemplate/misc/cust-part_pkg.cgi index a277ba407..43b92297e 100644 --- a/httemplate/misc/cust-part_pkg.cgi +++ b/httemplate/misc/cust-part_pkg.cgi @@ -1,4 +1,4 @@ -<% objToJson( \@return ) %> +<% encode_json( \@return ) %>\ <%init> my( $custnum, $prospectnum, $classnum ) = $cgi->param('arg'); diff --git a/httemplate/misc/email-customers.html b/httemplate/misc/email-customers.html index d26e40298..fcd79d7f8 100644 --- a/httemplate/misc/email-customers.html +++ b/httemplate/misc/email-customers.html @@ -120,9 +120,11 @@ Template: <TR> <TD ALIGN="right" VALIGN="top" STYLE="padding-top:3px">Message: </TD> - <TD><& '/elements/htmlarea.html', - 'field' => 'html_body', - 'width' => 600 &></TD> + <TD><& /elements/htmlarea.html, + 'field' => 'html_body', + 'width' => 763, + &> + </TD> </TR> </TABLE> diff --git a/httemplate/misc/exchanges.cgi b/httemplate/misc/exchanges.cgi index 8a67f7bab..0de4ace25 100644 --- a/httemplate/misc/exchanges.cgi +++ b/httemplate/misc/exchanges.cgi @@ -1,4 +1,4 @@ -<% objToJson(\@exchanges) %> +<% encode_json(\@exchanges) %>\ <%init> my( $areacode, $svcpart ) = $cgi->param('arg'); diff --git a/httemplate/misc/location.cgi b/httemplate/misc/location.cgi index 188c5c3df..fab61dd01 100644 --- a/httemplate/misc/location.cgi +++ b/httemplate/misc/location.cgi @@ -1,4 +1,4 @@ -<% objToJson(\%hash) %> +<% encode_json(\%hash) %>\ <%init> my $locationnum = $cgi->param('arg'); @@ -24,8 +24,9 @@ my $cust_location = qsearchs({ my %hash = (); %hash = map { $_ => $cust_location->$_() } - qw( address1 address2 city county state zip country - location_kind location_type location_number ) + ( FS::cust_main->location_fields, + qw( location_kind location_type location_number ) + ) if $cust_location; </%init> diff --git a/httemplate/misc/macinventory.cgi b/httemplate/misc/macinventory.cgi index b07da9726..cec0e3121 100644 --- a/httemplate/misc/macinventory.cgi +++ b/httemplate/misc/macinventory.cgi @@ -1,4 +1,4 @@ -<% objToJson(\@macs) %> +<% encode_json(\@macs) %>\ <%init> # XXX: this should be agent-virtualized / limited @@ -13,13 +13,8 @@ die "unknown devicepart $devicepart" unless $part_device; my $inventory_class = $part_device->inventory_class; die "devicepart $devicepart has no inventory" unless $inventory_class; -my @inventory_item = +my @macs = + map $_->item, qsearch('inventory_item', { 'classnum' => $inventory_class->classnum } ); -my @macs; - -foreach my $inventory_item ( @inventory_item ) { - push @macs, $inventory_item->item; -} - </%init> diff --git a/httemplate/misc/maestro-customer_status.html b/httemplate/misc/maestro-customer_status.html index 8acae2b2a..a872d4997 100644 --- a/httemplate/misc/maestro-customer_status.html +++ b/httemplate/misc/maestro-customer_status.html @@ -1,4 +1,4 @@ -<% objToJson( $return ) %> +<% encode_json( $return ) %>\ <%init> my $return; diff --git a/httemplate/misc/manage_cust_email.html b/httemplate/misc/manage_cust_email.html new file mode 100644 index 000000000..3ece459bb --- /dev/null +++ b/httemplate/misc/manage_cust_email.html @@ -0,0 +1,106 @@ +<& /elements/header.html, 'Manage customer email settings' &> +<STYLE TYPE="text/css"> +.hidden { display: none } +</STYLE> +<& /elements/xmlhttp.html, + url => $p.'misc/xmlhttp-cust_main-email_search.html', + subs => ['email_search'] +&> +<SCRIPT TYPE="text/javascript"> + +function receive_search(result) { + var recs = JSON.parse(result); + var tbody = document.getElementById('tbody_results'); + var j = tbody.rows.length; + for(var i = 0; i < j; i++) { + tbody.deleteRow(tbody.rows[i]); + } + if (recs.length > 0) { + for(var i = 0; i < recs.length; i++) { + var rec = recs[i]; + var row = tbody.insertRow(i); + row.style.backgroundColor = (i % 2 ? '#eeeeee' : '#ffffff'); + + var cell = row.insertCell(0); // custnum + cell.appendChild( document.createTextNode(rec[0]) ); + cell = row.insertCell(1); // customer name + cell.appendChild( document.createTextNode(rec[1]) ); + cell = row.insertCell(2); // email + cell.appendChild( document.createTextNode(rec[2]) ); + + cell = row.insertCell(3); // invoice_email + var input = document.createElement('INPUT'); + input.type = 'hidden'; + input.name = 'custnum'; + input.value = rec[0]; + cell.appendChild(input); + + input = document.createElement('INPUT'); + input.type = 'checkbox'; + input.name = 'custnum' + rec[0] + '_invoice_email'; + input.value = 'Y'; + input.checked = (rec[3] != 'Y'); + cell.appendChild(input); + cell.style.textAlign = 'center'; + + cell = row.insertCell(4); // message_email + input = document.createElement('INPUT'); + input.type = 'checkbox'; + input.name = 'custnum' + rec[0] + '_message_email'; + input.value = 'Y'; + input.checked = (rec[4] != 'Y'); + cell.appendChild(input); + cell.style.textAlign = 'center'; + } + document.getElementById('div_found').style.display = ''; + } else { + document.getElementById('div_notfound').style.display = ''; + } +} + +function start_search() { + document.getElementById('div_found').style.display = 'none'; + document.getElementById('div_notfound').style.display = 'none'; + var email = document.getElementById('input_email').value; + email_search(email, receive_search); +} +% if ( $cgi->param('search') ) { +window.onload = start_search; +% } +</SCRIPT> +<FORM ACTION="<%$p%>misc/process/manage_cust_email.html" METHOD="POST"> +<DIV> +% if ( $cgi->param('done') ) { +<P STYLE="font-weight: bold; color: #00ff00">Changes saved.</P> +% } elsif ( $cgi->param('error') ) { +<P STYLE="font-weight: bold; color: #ff0000"><% $cgi->param('error') |h %></P> +% } + Email address: + <INPUT TYPE="text" ID="input_email" NAME="search"\ + VALUE="<% $cgi->param('search') |h %>"> + <INPUT TYPE="button" onclick="start_search()" VALUE="find"> +</DIV> +<DIV ID="div_notfound" STYLE="display: none; padding: 1em"> +No matching email addresses found. +</DIV> +<DIV ID="div_found" STYLE="display: none"> +<TABLE CLASS="grid" STYLE="border-spacing: 0px"> + <THEAD> + <TR STYLE="background-color: #dddddd"> + <TH>#</TH> + <TH>Customer</TH> + <TH>Email</TH> + <TH>Send invoices</TH> + <TH>Send other notices</TH> + </TR> + </THEAD> + <TBODY ID="tbody_results"></TBODY> +</TABLE> +<INPUT TYPE="submit" VALUE="Save changes"> +</FORM> +<& /elements/footer.html &> +<%init> +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Edit customer'); + +</%init> diff --git a/httemplate/misc/order_pkg.html b/httemplate/misc/order_pkg.html index bfc7b6903..e09ba986d 100644 --- a/httemplate/misc/order_pkg.html +++ b/httemplate/misc/order_pkg.html @@ -93,6 +93,12 @@ &> % } +<& /elements/tr-select-contact.html, + 'cgi' => $cgi, + 'cust_main' => $cust_main, + 'prospect_main' => $prospect_main, +&> + % if ( $cgi->param('lock_locationnum') ) { <INPUT TYPE = "hidden" @@ -129,9 +135,6 @@ <& /elements/standardize_locations.html, 'form' => "OrderPkgForm", - 'onlyship' => 1, - 'no_company' => 1, - 'no_census' => 1, 'callback' => 'document.OrderPkgForm.submit();', &> diff --git a/httemplate/misc/part_export/huawei_hlr-import_sim.html b/httemplate/misc/part_export/huawei_hlr-import_sim.html new file mode 100644 index 000000000..9b87b3d2a --- /dev/null +++ b/httemplate/misc/part_export/huawei_hlr-import_sim.html @@ -0,0 +1,52 @@ +<& /elements/header-popup.html, 'Import SIMs' &> +Import a file containing SIM card properties.<BR> +Each row should contain the following fields, separated by spaces:<BR> +IMSI, ICCID, PIN1, PUK1, PIN2, PUK2, ACC, Ki<BR> +<BR> +<& /elements/form-file_upload.html, + 'name' => 'ImportForm', + 'action' => 'process/huawei_hlr-import_sim.html', + 'num_files' => 1, + 'fields' => [ 'exportnum', 'classnum', 'agentnum', ], + 'message' => 'Inventory import successful', + 'onsubmit' => "document.ImportForm.submitButton.disabled=true;", +&> +<TABLE CLASS="inv" WIDTH="100%"> + <INPUT TYPE="hidden" NAME="exportnum" VALUE="<%$exportnum%>"> + <& /elements/file-upload.html, + 'field' => 'file', + 'label' => 'Filename', + &> + <& /elements/tr-select-agent.html, + 'disable_empty' => 1, + &> + <& /elements/tr-select-table.html, + 'table' => 'inventory_class', + 'name_col' => 'classname', + 'label' => 'Inventory class', + 'disable_empty' => 1, + &> + + <TR> + <TD COLSPAN=2 ALIGN="center" STYLE="padding-top:6px"> + <INPUT TYPE = "submit" + NAME = "submitButton" + ID = "submitButton" + VALUE = "Import file" + > + </TD> + </TR> + +</TABLE> + +</FORM> + +<%init> +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); + +my ($exportnum) = $cgi->keywords; +$exportnum =~ /^\d+$/ or die "bad exportnum '$exportnum'"; +my $part_export = FS::part_export->by_key($exportnum) + or die "export $exportnum not found"; +</%init> diff --git a/httemplate/misc/part_export/process/huawei_hlr-import_sim.html b/httemplate/misc/part_export/process/huawei_hlr-import_sim.html new file mode 100644 index 000000000..d46700d5f --- /dev/null +++ b/httemplate/misc/part_export/process/huawei_hlr-import_sim.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::part_export::huawei_hlr::process_import_sim', $cgi; + +</%init> diff --git a/httemplate/misc/part_svc-columns.cgi b/httemplate/misc/part_svc-columns.cgi index 060256154..a86164d06 100644 --- a/httemplate/misc/part_svc-columns.cgi +++ b/httemplate/misc/part_svc-columns.cgi @@ -1,4 +1,4 @@ -<% objToJson(\@output) %> +<% encode_json(\@output) %>\ <%init> my $conf = new FS::Conf; diff --git a/httemplate/misc/phonenums.cgi b/httemplate/misc/phonenums.cgi index 5084628eb..a048280bb 100644 --- a/httemplate/misc/phonenums.cgi +++ b/httemplate/misc/phonenums.cgi @@ -1,4 +1,4 @@ -<% objToJson(\@phonenums) %> +<% encode_json(\@phonenums) %>\ <%init> my( $exchangestring, $svcpart ) = $cgi->param('arg'); diff --git a/httemplate/misc/process/change-password.html b/httemplate/misc/process/change-password.html new file mode 100644 index 000000000..7cab9c4e3 --- /dev/null +++ b/httemplate/misc/process/change-password.html @@ -0,0 +1,26 @@ +<%init> +my $curuser = $FS::CurrentUser::CurrentUser; + +$cgi->param('svcnum') =~ /^(\d+)$/ or die "illegal svcnum"; +my $svcnum = $1; +my $svc_acct = FS::svc_acct->by_key($svcnum) + or die "svc_acct $svcnum not found"; +my $part_svc = $svc_acct->part_svc; +die "access denied" unless ( + $curuser->access_right('Provision customer service') or + ( $curuser->access_right('Edit password') and + ! $part_svc->restrict_edit_password ) + ); +my $error = $svc_acct->set_password($cgi->param('password')) + || $svc_acct->replace; + +# annoyingly specific to view/svc_acct.cgi, for now... +$cgi->delete('password'); +</%init> +% if ( $error ) { +% $cgi->param('svcnum', $svcnum); +% $cgi->param("changepw${svcnum}_error", $error); +% } else { +% $cgi->query_string($svcnum); +% } +<% $cgi->redirect($fsurl.'view/svc_acct.cgi?'.$cgi->query_string) %> diff --git a/httemplate/misc/process/change_pkg_contact.html b/httemplate/misc/process/change_pkg_contact.html new file mode 100644 index 000000000..2795c1197 --- /dev/null +++ b/httemplate/misc/process/change_pkg_contact.html @@ -0,0 +1,49 @@ +<% header(emt("Package contact $past_method")) %> + <SCRIPT TYPE="text/javascript"> + window.top.location.reload(); + </SCRIPT> + </BODY> +</HTML> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Change customer package'); + +#untaint pkgnum +my $pkgnum = $cgi->param('pkgnum'); +$pkgnum =~ /^(\d+)$/ or die "Illegal pkgnum"; +$pkgnum = $1; + +my $cust_pkg = qsearchs( 'cust_pkg', {'pkgnum'=>$pkgnum} ); #needs agent virt + +my $contactnum = $cgi->param('contactnum'); +$contactnum =~ /^(-?\d*)$/ or die "Illegal contactnum"; +$contactnum = $1; + +my $past_method = $cust_pkg->contactnum ? 'changed' : 'added'; + +my $error = ''; + +if ( $contactnum == -1 ) { + + #little false laziness w/edit/process/quick-cust_pkg.cgi, also the whole + # thing should be a single transaction + my $contact = new FS::contact { + 'custnum' => $cust_pkg->custnum, + map { $_ => scalar($cgi->param("contactnum_$_")) } qw( first last ) + }; + $error = $contact->insert; + $cust_pkg->contactnum( $contact->contactnum ); + +} else { + $cust_pkg->contactnum($contactnum); +} + +$error ||= $cust_pkg->replace; + +if ($error) { + $cgi->param('error', $error); + print $cgi->redirect(popurl(2). "change_pkg_contact.html?". $cgi->query_string ); +} + +</%init> diff --git a/httemplate/misc/process/manage_cust_email.html b/httemplate/misc/process/manage_cust_email.html new file mode 100644 index 000000000..5bf1470d1 --- /dev/null +++ b/httemplate/misc/process/manage_cust_email.html @@ -0,0 +1,32 @@ +<% $cgi->redirect($fsurl.'misc/manage_cust_email.html?' . + $cgi->query_string) %> +<%init> +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Edit customer'); + +my $error; +foreach my $custnum ($cgi->param('custnum')) { + my $cust = FS::cust_main->by_key($custnum) + or die "customer not found: $custnum\n"; + my $new_invoice_noemail = + $cgi->param('custnum'.$custnum.'_invoice_email') ? '' : 'Y'; + my $new_message_noemail = + $cgi->param('custnum'.$custnum.'_message_email') ? '' : 'Y'; + if ( $new_invoice_noemail ne $cust->invoice_noemail + or $new_message_noemail ne $cust->message_noemail ) { + + $cust->set('invoice_noemail', $new_invoice_noemail); + $cust->set('message_noemail', $new_message_noemail); + $error ||= $cust->replace; + + } + $cgi->delete('custnum'.$custnum.'_invoice_email'); + $cgi->delete('custnum'.$custnum.'_message_email'); +} +$cgi->delete('custnum'); +if ( $error ) { + $cgi->param('error' => $error); # probably unnecessary... +} else { + $cgi->param('done' => 1) unless $error; +} +</%init> diff --git a/httemplate/misc/process/payment.cgi b/httemplate/misc/process/payment.cgi index 506e26684..981614e76 100644 --- a/httemplate/misc/process/payment.cgi +++ b/httemplate/misc/process/payment.cgi @@ -210,7 +210,15 @@ if ( $cgi->param('save') ) { $new->set( 'paycvv' => ''); } - $new->set( $_ => $cgi->param($_) ) foreach @{$payby2fields{$payby}}; + if ( $payby eq 'CARD' ) { + my $bill_location = FS::cust_location->new; + $bill_location->set( $_ => $cgi->param($_) ) + foreach @{$payby2fields{$payby}}; + $new->set('bill_location' => $bill_location); + # will do nothing if the fields are all unchanged + } else { + $new->set( $_ => $cgi->param($_) ) foreach @{$payby2fields{$payby}}; + } my $error = $new->replace($cust_main); errorpage("payment processed successfully, but error saving info: $error") diff --git a/httemplate/misc/regions.cgi b/httemplate/misc/regions.cgi index 2450ea31a..31538b08e 100644 --- a/httemplate/misc/regions.cgi +++ b/httemplate/misc/regions.cgi @@ -1,4 +1,4 @@ -<% objToJson(\@regions) %> +<% encode_json(\@regions) %>\ <%init> my( $state, $svcpart ) = $cgi->param('arg'); diff --git a/httemplate/misc/xmlhttp-address_standardize.html b/httemplate/misc/xmlhttp-address_standardize.html index 988057163..618265364 100644 --- a/httemplate/misc/xmlhttp-address_standardize.html +++ b/httemplate/misc/xmlhttp-address_standardize.html @@ -1,4 +1,4 @@ -<% encode_json($return) %> +<% encode_json($return) %>\ <%init> local $SIG{__DIE__}; #disable Mason error trap @@ -16,12 +16,10 @@ my %old = %{ decode_json($cgi->param('arg')) } my %new; -my @prefixes; -if ($old{onlyship}) { - @prefixes = ('ship_'); -} elsif ( $old{same} ) { +my @prefixes = (''); +if ( $old{same} ) { @prefixes = ('bill_'); -} else { +} elsif ( $old{billship} ) { @prefixes = ('bill_', 'ship_'); } my $all_same = 1; @@ -44,6 +42,8 @@ foreach my $pre ( @prefixes ) { $all_same = 0 if ( $new{$pre.$_} ne $old{$pre.$_} ); last if !$all_same; } + + $all_same = 0 if $new{$pre.'error'}; } my $return = { old => \%old, new => \%new, all_same => $all_same }; diff --git a/httemplate/misc/xmlhttp-calculate_taxes.html b/httemplate/misc/xmlhttp-calculate_taxes.html index d3dc36acf..ed7bd0173 100644 --- a/httemplate/misc/xmlhttp-calculate_taxes.html +++ b/httemplate/misc/xmlhttp-calculate_taxes.html @@ -1,4 +1,4 @@ -<% objToJson($return) %> +<% encode_json($return) %>\ <%init> my $DEBUG = 0; diff --git a/httemplate/misc/xmlhttp-cust_bill-search.html b/httemplate/misc/xmlhttp-cust_bill-search.html index 46f15d1ab..c60a0b05c 100644 --- a/httemplate/misc/xmlhttp-cust_bill-search.html +++ b/httemplate/misc/xmlhttp-cust_bill-search.html @@ -1,4 +1,4 @@ -<% encode_json(\@return) %> +<% encode_json(\@return) %>\ <%init> my $curuser = $FS::CurrentUser::CurrentUser; @@ -6,13 +6,15 @@ die 'access denied' unless $curuser->access_right('View invoices'); my @return; if ( $cgi->param('sub') eq 'custnum_search_open' ) { my $custnum = $cgi->param('arg'); - #warn "searching invoices for $custnum\n"; - my $cust_main = FS::cust_main->by_key($custnum); - @return = map { - +{ $_->hash, - 'owed' => $_->owed } - } $cust_main->open_cust_bill - if $curuser->agentnums_href->{ $cust_main->agentnum }; + if ( $custnum =~ /^(\d+)$/ ) { +#warn "searching invoices for $custnum\n"; + my $cust_main = FS::cust_main->by_key($custnum); + @return = map { + +{ $_->hash, + 'owed' => $_->owed } + } $cust_main->open_cust_bill + if $curuser->agentnums_href->{ $cust_main->agentnum }; + } } </%init> diff --git a/httemplate/misc/xmlhttp-cust_bill_pkg-calculate_taxes.html b/httemplate/misc/xmlhttp-cust_bill_pkg-calculate_taxes.html index 993504619..c0db3e2c4 100644 --- a/httemplate/misc/xmlhttp-cust_bill_pkg-calculate_taxes.html +++ b/httemplate/misc/xmlhttp-cust_bill_pkg-calculate_taxes.html @@ -1,8 +1,8 @@ -<% to_json($return) %> +<% encode_json($return) %>\ <%init> my $curuser = $FS::CurrentUser::CurrentUser; -die "access denied" unless $curuser->access_right('Post credit'); +die "access denied" unless $curuser->access_right('Credit line items'); my $DEBUG = 0; diff --git a/httemplate/misc/xmlhttp-cust_main-censustract.html b/httemplate/misc/xmlhttp-cust_main-censustract.html index 4b00898da..4c708a4c4 100644 --- a/httemplate/misc/xmlhttp-cust_main-censustract.html +++ b/httemplate/misc/xmlhttp-cust_main-censustract.html @@ -1,4 +1,4 @@ -<% objToJson($return) %> +<% encode_json($return) %>\ <%init> my %arg = $cgi->param('arg'); diff --git a/httemplate/misc/xmlhttp-cust_main-discount_terms.cgi b/httemplate/misc/xmlhttp-cust_main-discount_terms.cgi index b524e69fc..36b18b455 100644 --- a/httemplate/misc/xmlhttp-cust_main-discount_terms.cgi +++ b/httemplate/misc/xmlhttp-cust_main-discount_terms.cgi @@ -16,7 +16,7 @@ % } % } % -<% objToJson($return) %> +<% encode_json($return) %>\ % } <%init> diff --git a/httemplate/misc/xmlhttp-cust_main-email_search.html b/httemplate/misc/xmlhttp-cust_main-email_search.html new file mode 100644 index 000000000..0d830826c --- /dev/null +++ b/httemplate/misc/xmlhttp-cust_main-email_search.html @@ -0,0 +1,29 @@ +<% encode_json(\@result) %>\ +<%init> +die 'access denied' + unless $FS::CurrentUser::CurrentUser->access_right('Edit customer'); + +my $sub = $cgi->param('sub'); +my $email = $cgi->param('arg'); +my @where = ( + "cust_main_invoice.dest != 'POST'", + "cust_main_invoice.dest LIKE ".dbh->quote('%'.$email.'%'), + $FS::CurrentUser::CurrentUser->agentnums_sql(table => 'cust_main'), +); +my @cust_main = qsearch({ + 'table' => 'cust_main', + 'select' => 'cust_main.*, cust_main_invoice.dest', + 'addl_from' => 'JOIN cust_main_invoice USING (custnum)', + 'extra_sql' => 'WHERE '.join(' AND ', @where), +}); + +my @result = map { + [ $_->custnum, + $_->name, + $_->dest, + $_->invoice_noemail, + $_->message_noemail, + ] +} @cust_main; + +</%init> diff --git a/httemplate/misc/xmlhttp-cust_main-search.cgi b/httemplate/misc/xmlhttp-cust_main-search.cgi index acf7e70e2..73c9ff8ec 100644 --- a/httemplate/misc/xmlhttp-cust_main-search.cgi +++ b/httemplate/misc/xmlhttp-cust_main-search.cgi @@ -5,7 +5,7 @@ % # cust_main-agent_custid-format') eq 'ww?d+' % $return = findbycustnum_or_agent_custid($1); % } -<% objToJson($return) %> +<% encode_json($return) %>\ % } elsif ( $sub eq 'smart_search' ) { % % my $string = $cgi->param('arg'); @@ -22,14 +22,14 @@ % @cust_main % ]; % -<% objToJson($return) %> +<% encode_json($return) %>\ % } elsif ( $sub eq 'invnum_search' ) { % % my $string = $cgi->param('arg'); % if ( $string =~ /^(\d+)$/ ) { % my $inv = qsearchs('cust_bill', { 'invnum' => $1 }); % my $return = $inv ? findbycustnum($inv->custnum) : []; -<% objToJson($return) %> +<% encode_json($return) %>\ % } else { #return nothing [] % } @@ -47,7 +47,7 @@ % city => $_->city, % }; % } -<% objToJson($return) %> +<% encode_json($return) %>\ % } <%init> diff --git a/httemplate/misc/xmlhttp-ping.html b/httemplate/misc/xmlhttp-ping.html index e99303207..01baa3f57 100644 --- a/httemplate/misc/xmlhttp-ping.html +++ b/httemplate/misc/xmlhttp-ping.html @@ -1,4 +1,4 @@ -<% objToJson($return) %> +<% encode_json($return) %>\ <%init> my $conf = new FS::Conf; diff --git a/httemplate/misc/xmlhttp-svc_broadband-search.cgi b/httemplate/misc/xmlhttp-svc_broadband-search.cgi new file mode 100644 index 000000000..578e6140e --- /dev/null +++ b/httemplate/misc/xmlhttp-svc_broadband-search.cgi @@ -0,0 +1,22 @@ +% if ( $sub eq 'smart_search' ) { +% +% my $string = $cgi->param('arg'); +% my @svc_broadband = FS::svc_broadband->smart_search( $string ); +% my $return = [ map { my $cust_pkg = $_->cust_svc->cust_pkg; +% [ $_->svcnum, +% $_->label. ( $cust_pkg +% ? ' ('. $cust_pkg->cust_main->name. ')' +% : '' +% ), +% ]; +% } +% @svc_broadband, +% ]; +% +<% encode_json($return) %>\ +% } +<%init> + +my $sub = $cgi->param('sub'); + +</%init> diff --git a/httemplate/pref/pref-process.html b/httemplate/pref/pref-process.html index c4fef0311..6b94f7175 100644 --- a/httemplate/pref/pref-process.html +++ b/httemplate/pref/pref-process.html @@ -49,6 +49,7 @@ unless ( $error ) { # if ($access_user) { #XXX autogen my @paramlist = qw( locale menu_position default_customer_view + history_order spreadsheet_format mobile_menu enable_fuzzy_on_exact disable_html_editor disable_enter_submit_onetimecharge @@ -57,7 +58,7 @@ unless ( $error ) { # if ($access_user) { vonage-fromnumber vonage-username vonage-password cust_pkg-display_times show_pkgnum show_confitem_counts export_getsettings - show_db_profile save_db_profile + show_db_profile save_db_profile save_tmp_typesetting height width availHeight availWidth colorDepth ); diff --git a/httemplate/pref/pref.html b/httemplate/pref/pref.html index 1e9671dcc..5babb0181 100644 --- a/httemplate/pref/pref.html +++ b/httemplate/pref/pref.html @@ -75,6 +75,21 @@ Interface </SELECT> </TD> </TR> + +% my $history_order = $curuser->option('history_order') || 'oldest'; + <TR> + <TH ALIGN="right">Customer history sort order: </TH> + <TD COLSPAN=2> + <& /elements/select.html, + field => 'history_order', + curr_value => $history_order, + options => [ 'oldest', 'newest' ], + labels => { 'oldest' => 'Oldest first', + 'newest' => 'Newest first', + }, + &> + </TD> + </TR> <TR> <TH ALIGN="right">Spreadsheet download format: </TH> @@ -92,7 +107,7 @@ Interface </TR> <TR> - <TH ALIGN="right" COLSPAN=1>Enable approximate customer searching even when an exact match is found: </TH> + <TH ALIGN="right" COLSPAN=1>Enable approximate customer searching <BR>even when an exact match is found: </TH> <TD ALIGN="left" COLSPAN=2> <INPUT TYPE="checkbox" NAME="enable_fuzzy_on_exact" VALUE="1" <% $curuser->option('enable_fuzzy_on_exact') ? 'CHECKED' : '' %>> </TD> @@ -157,6 +172,10 @@ Development <TH>Save database profiling logs (when available): </TH> <TD><INPUT TYPE="checkbox" NAME="save_db_profile" VALUE="1" <% $curuser->option('save_db_profile') ? 'CHECKED' : '' %>></TD> </TR> + <TR> + <TH>Save temporary invoice typesetting files: </TH> + <TD><INPUT TYPE="checkbox" NAME="save_tmp_typesetting" VALUE="1" <% $curuser->option('save_tmp_typesetting') ? 'CHECKED' : '' %>></TD> + </TR> </TABLE> <BR> diff --git a/httemplate/search/477.html b/httemplate/search/477.html index 6f5fcdf3b..eed3df946 100755 --- a/httemplate/search/477.html +++ b/httemplate/search/477.html @@ -3,6 +3,14 @@ <Form_477_submission xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://specialreports.fcc.gov/wcb/Form477/XMLSchema-instance/form_477_upload_Schema.xsd" > % } else { #html <& /elements/header.html, "FCC Form 477 Results - $state" &> +%# XXX when we stop supporting IE8, add this to freeside.css using :nth-child +%# selectors, and remove it from everywhere else +<STYLE TYPE="text/css"> +.grid TH { background-color: #cccccc; padding: 0px 3px 2px; text-align: right } +.row0 TD { background-color: #eeeeee; padding: 0px 3px 2px; text-align: right } +.row1 TD { background-color: #ffffff; padding: 0px 3px 2px; text-align: right } +</STYLE> + <TABLE WIDTH="100%"> <TR> <TD></TD> @@ -38,8 +46,11 @@ % if ( $type eq 'xml' ) { <<% 'Part_IA_'. chr(65 + $tech) %>> % } -<& "477part${part}_summary.html", 'tech_code' => $tech, 'url' => $url &> -<& "477part${part}_detail.html", 'tech_code' => $tech, 'url' => $url &> +<& "477part${part}.html", + 'tech_code' => $tech, + 'url' => $url, + 'type' => $type +&> % if ( $type eq 'xml' ) { </<% 'Part_IA_'. chr(65 + $tech) %>> % } @@ -97,6 +108,11 @@ for(my $i=0; $i < scalar(@part2b_row_option); $i++) { &FS::Report::FCC_477::save_fcc477map("part2b_row_option_$i",$part2b_row_option[$i]); } +my $part5_report_option = $cgi->param('part5_report_option'); +if ( $part5_report_option ) { + FS::Report::FCC_477::save_fcc477map('part5_report_option', $part5_report_option); +} + my $url_mangler = sub { my $part = shift; my $url = $cgi->url('-path_info' => 1, '-full' => 1); diff --git a/httemplate/search/477partIA.html b/httemplate/search/477partIA.html new file mode 100755 index 000000000..1cd0b70e0 --- /dev/null +++ b/httemplate/search/477partIA.html @@ -0,0 +1,165 @@ +% if ( $opt{'type'} eq 'xml' ) { +%# container element <Part_IA_$tech> is in 477.html +% my $col = 'a'; +% foreach ( @summary_row ) { +% my $el = $xml_prefix . $col . '1'; # PartIA_Aa1, PartIA_Ab1, etc. + <<% $el %>><% $_ %><<% "/$el" %>> +% $col++; +% } +% foreach my $col_data ( @data ) { +% my $row = 1; +% foreach my $cell ( @$col_data ) { +% my $el = $xml_prefix . $col . $row; # PartIA_Af1, PartIA_Af2... + <<% $el %>><% $cell->[0] %><<% "/$el" %>> +% if ( $percentages ) { +% $el = $xml_percent . $col . $row; # Part_p_IA_Af1, ... + <<% $el %>><% $cell->[1] %><<% "/$el" %>> +% } +% $row++; +% } # foreach $cell +% $col++; +% } # foreach $col_data +% } else { # not XML + +<H2><% $title %> totals</H2> +<& /elements/table-grid.html &> + <TR> +% foreach ( 'Total Connections', +% '% owned loop', +% '% billed to end users', +% '% residential', +% '% residential > 200 kbps') { + <TH WIDTH="20%"><% $_ |h %></TH> +% } + </TR> + <TR CLASS="row0"> +% foreach ( @summary_row ) { + <TD><% $_ %></TD> +% } + </TR> +</TABLE> +<H2><% $title %> breakdown by speed</H2> +<TABLE CLASS="grid" CELLSPACING=0> + <TR> + <TH WIDTH="12%"></TH> +% for (my $col = 0; $col < scalar(@download_option); $col++) { + <TH WIDTH="11%"> + <% $FS::Report::FCC_477::download[$col] |h %> + </TH> +% } + </TR> +% for (my $row = 0; $row < scalar(@upload_option); $row++) { + <TR CLASS="row<% $row % 2%>"> + <TD STYLE="text-align: left; font-weight: bold"> +% if ( $asymmetric ) { + <% $FS::Report::FCC_477::upload[$row] |h %> +% } + </TD> +% for (my $col = 0; $col < scalar(@download_option); $col++) { + <TD> + <% $data[$col][$row][0] %> +% if ( $percentages ) { + <BR><% $data[$col][$row][1] %> +% } + </TD> +% } # for $col + </TR> +% } # for $row +</TABLE> +% } +<%init> + +my $curuser = $FS::CurrentUser::CurrentUser; + +die "access denied" + unless $curuser->access_right('List packages'); + +my %opt = @_; +my %search_hash; + +for ( qw(agentnum state) ) { + $search_hash{$_} = $cgi->param($_) if $cgi->param($_); +} +$search_hash{'status'} = 'active'; +$search_hash{'country'} = 'US'; +$search_hash{'classnum'} = [ $cgi->param('classnum') ]; + +# arrays of report_option_ numbers, running parallel to +# the download and upload speed arrays +my @download_option = $cgi->param('part1_column_option'); +my @upload_option = $cgi->param('part1_row_option'); + +my @technology_option = &FS::Report::FCC_477::parse_technology_option($cgi); + +my $total_count = 0; +my $total_residential = 0; +my $above_200 = 0; +my $tech_code = $opt{tech_code}; +my $technology = $FS::Report::FCC_477::technology[$tech_code] || 'unknown'; +my $title = "Part IA $technology"; +my $xml_prefix = 'PartIA_'. chr(65 + $tech_code); +my $xml_percent = 'Part_p_IA_'. chr(65 + $tech_code); # yes, seriously + +# whether to show the results as a matrix (upload speeds in rows) or a single +# row +my $asymmetric = 1; +if ( $technology eq 'Symmetric xDSL' or $technology eq 'Other Wireline' ) { + $asymmetric = 0; + @upload_option = ( undef ); +} +# whether to show residential percentages in each cell of the matrix +my $percentages = ($technology eq 'Terrestrial Mobile Wireless'); + +my $query = FS::cust_pkg->search(\%search_hash); +my $count_query = $query->{'count_query'}; + +my $is_residential = " AND COALESCE(cust_main.company, '') = ''"; +my $has_option = sub { + my $optionnum = shift; + $optionnum =~ /^\d+$/ ? + " AND EXISTS( + SELECT 1 FROM part_pkg_option + WHERE part_pkg_option.pkgpart = part_pkg.pkgpart + AND optionname = 'report_option_$optionnum' + AND optionvalue = '1' + )" : ''; +}; + +# limit to those that have technology option $tech_code +$count_query .= $has_option->($technology_option[$tech_code]); + +my @data; +for ( my $row = 0; $row < scalar @upload_option; $row++ ) { + for ( my $col = 0; $col < scalar @download_option; $col++ ) { + + my $this_count_query = $count_query . + $has_option->($upload_option[$row]) . + $has_option->($download_option[$col]); + + my $count = FS::Record->scalar_sql($this_count_query); + my $residential = FS::Record->scalar_sql($this_count_query . $is_residential); + + my $percent = sprintf('%.2f', $count ? 100 * $residential / $count : 0); + $data[$col][$row] = [ $count, $percent ]; + + $total_count += $count; + $total_residential += $residential; + $above_200 += $residential if $row > 0 or !$asymmetric; + } +} + +my $total_percentage = + sprintf("%.2f", $total_count ? 100*$total_residential/$total_count : 0); + +my $above_200_percentage = + sprintf("%.2f", $total_count ? 100*$above_200/$total_count : 0); + +my @summary_row = ( + $total_count, + 100.00, # own local loop--consistent with previous practice, but probably wrong + 100.00, # billed to end user--also wrong + $total_percentage, # residential percentage + $above_200_percentage, +); + +</%init> diff --git a/httemplate/search/477partIA_detail.html b/httemplate/search/477partIA_detail.html deleted file mode 100755 index 66f3a8651..000000000 --- a/httemplate/search/477partIA_detail.html +++ /dev/null @@ -1,129 +0,0 @@ -<% include( 'elements/search.html', - 'html_init' => $html_init, - 'name' => 'lines', - 'query' => $query, - 'count_query' => $count_query, - 'really_disable_download' => 1, - 'disable_download' => 1, - 'nohtmlheader' => 1, - 'disable_total' => 1, - 'header' => [ '', @column_option_name ], - 'xml_elements' => [ @xml_elements ], - 'xml_omit_empty' => 1, - 'fields' => [ @fields ], - ) -%> -<%init> - -my $curuser = $FS::CurrentUser::CurrentUser; - -die "access denied" - unless $curuser->access_right('List packages'); - -my %opt = @_; -my %search_hash = (); - -for ( qw(agentnum magic state) ) { - $search_hash{$_} = $cgi->param($_) if $cgi->param($_); -} -$search_hash{'country'} = 'US'; - -$search_hash{'classnum'} = [ $cgi->param('classnum') ]; - -my @column_option = grep { /^\d+/ } $cgi->param('part1_column_option') - if $cgi->param('part1_column_option'); - -my @row_option = grep { /^\d+/ } $cgi->param('part1_row_option') - if $cgi->param('part1_row_option'); - -my @technology_option = &FS::Report::FCC_477::parse_technology_option($cgi); - -my @column_option_name = scalar(@column_option) - ? ( map { my $part_pkg_report_option = - qsearchs({ 'table' => 'part_pkg_report_option', - 'hashref' => { num => $_ }, - }); - $part_pkg_report_option ? $part_pkg_report_option->name - : 'no such report option'; - } @column_option - ) - : ( 'all packages' ); - -my $where = join(' OR ', map { "num = $_" } @row_option ); -my %row_option_name = $where ? - ( map { $_->num => $_->name } - qsearch({ 'table' => 'part_pkg_report_option', - 'hashref' => {}, - 'extra_sql' => "WHERE $where", - }) - ) : - (); - -my $tech_code = $opt{tech_code}; -my $technology = $FS::Report::FCC_477::technology[$tech_code] || 'unknown'; -my $html_init = "<H2>Part IA $technology breakdown by speeds</H2>"; -my $xml_prefix = 'PartIA_'. chr(65 + $tech_code); - -if ($cgi->param('_type') eq 'xml') { - #rotate data pi/2 - my @temp = @column_option; - @column_option = @row_option; - @row_option = @temp; -} - -my $query = 'SELECT '. join(' UNION ALL SELECT ',@row_option); -my $count_query = 'SELECT '. scalar(@row_option); - -my $xml_element = 'OOPS, I was never set'; -my $rowchar = 101; # 'e' -- rows are columns! (pi/2) - -my $value = sub { - my ($rowref, $column) = (shift, shift); - my $row = $rowref->[0]; - - if ($column eq 'name') { - return $row_option_name{$row} || 'no such report option'; - } elsif ( $column =~ /^(\d+)$/ ) { - my @report_option = ( $row || '', - $column_option[$column] || '', - $technology_option[$tech_code] || '', - ); - - my ( $count, $residential ) = FS::cust_pkg->fcc_477_count( - { %search_hash, 'report_option' => join(',', @report_option) } - ); - - my $percentage = sprintf('%.2f', $count ? 100 * $residential / $count : 0); - my $return = $count; - - if ($cgi->param('_type') eq 'xml') { - $rowchar++ if $column == 0; - $xml_element = $xml_prefix. chr($rowchar). ($column+1); - $return = '' if $count == 0 and $cgi->param('_type') eq 'xml'; - } else { - $return .= "<BR>$percentage% residential"; - } - - return $return; - } else { - return '<FONT SIZE="+1" COLOR="#ff0000">Bad call to column_value</FONT>'; - } -}; - -my @fields = map { my $ci = $_; sub { &{$value}(shift, $ci); } } - ( 'name', (0 .. $#column_option) ); -shift @fields if $cgi->param('_type') eq 'xml'; - -my @xml_elements = ( # -- columns are rows! (pi/2) - sub { return $xml_element; }, - sub { return $xml_element; }, - sub { return $xml_element; }, - sub { return $xml_element; }, - sub { return $xml_element; }, - sub { return $xml_element; }, - sub { return $xml_element; }, - sub { return $xml_element; }, - sub { return $xml_element; }, -); - -</%init> diff --git a/httemplate/search/477partIA_summary.html b/httemplate/search/477partIA_summary.html deleted file mode 100755 index f5c2bc251..000000000 --- a/httemplate/search/477partIA_summary.html +++ /dev/null @@ -1,89 +0,0 @@ -<% include( 'elements/search.html', - 'html_init' => $html_init, - 'name' => 'lines', - 'query' => 'SELECT 1', - 'count_query' => 'SELECT 1', - 'really_disable_download' => 1, - 'disable_download' => 1, - 'nohtmlheader' => 1, - 'disable_total' => 1, - 'header' => [ - 'Total Connections', - '% owned loop', - '% billed to end users', - '% residential', - '% residential > 200kbps', - ], - 'xml_elements' => [ - $xml_prefix. 'a1', - $xml_prefix. 'b1', - $xml_prefix. 'c1', - $xml_prefix. 'd1', - $xml_prefix. 'e1', - ], - 'fields' => [ - sub { $total_count }, - sub { '100.00' }, - sub { '100.00' }, - sub { $total_percentage }, - sub { $above_200_percentage }, - ], - ) -%> -<%init> - -my $curuser = $FS::CurrentUser::CurrentUser; - -die "access denied" - unless $curuser->access_right('List packages'); - -my %opt = @_; -my %search_hash = (); - -for ( qw(agentnum magic state) ) { - $search_hash{$_} = $cgi->param($_) if $cgi->param($_); -} -$search_hash{'country'} = 'US'; -$search_hash{'classnum'} = [ $cgi->param('classnum') ]; - -my @column_option = grep { /^\d+$/ } $cgi->param('part1_column_option') - if $cgi->param('part1_column_option'); - -my @row_option = grep { /^\d+$/ } $cgi->param('part1_row_option') - if $cgi->param('part1_row_option'); - -my @technology_option = &FS::Report::FCC_477::parse_technology_option($cgi); - -my $total_count = 0; -my $total_residential = 0; -my $above_200 = 0; -my $tech_code = $opt{tech_code}; -my $technology = $FS::Report::FCC_477::technology[$tech_code] || 'unknown'; -my $html_init = "<H2>Part IA $technology totals</H2>"; -my $xml_prefix = 'PartIA_'. chr(65 + $tech_code); - -my $not_first_row = 0; # ugh; -foreach my $row ( @row_option ) { - foreach my $column ( @column_option ) { - - my @report_option = ( $row || '-1', $column || '-1', $technology_option[$tech_code] ); - - my ( $count, $residential ) = FS::cust_pkg->fcc_477_count( - { %search_hash, 'report_option' => join(',', @report_option) } - ); - - $total_count += $count; - $total_residential += $residential; - $above_200 += $residential if $not_first_row; - } - $not_first_row++; -} - -my $total_percentage = - sprintf("%.2f", $total_count ? 100*$total_residential/$total_count : 0); - -my $above_200_percentage = - sprintf("%.2f", $total_count ? 100*$above_200/$total_count : 0); - - -</%init> diff --git a/httemplate/search/477partIIA.html b/httemplate/search/477partIIA.html index d2cc8c3e9..95c00a3e0 100755 --- a/httemplate/search/477partIIA.html +++ b/httemplate/search/477partIIA.html @@ -1,17 +1,44 @@ -<% include( 'elements/search.html', - 'html_init' => $html_init, - 'name' => 'lines', - 'query' => $query, - 'count_query' => 'SELECT 11', - 'really_disable_download' => 1, - 'disable_download' => 1, - 'nohtmlheader' => 1, - 'disable_total' => 1, - 'header' => [ @headers ], - 'xml_elements' => [ @xml_elements ], - 'fields' => [ @fields ], - ) -%> +% if ( $cgi->param('_type') eq 'xml' ) { +% my @cols = qw(a b c d); +% for ( my $row = 0; $row < scalar(@rows); $row++ ) { +% for my $col (0..3) { +% if ( exists($data[$col][$row]) and $data[$col][$row] > 0 ) { +<PartII_<% $row + 1 %><% $cols[$col] %>>\ +<% $data[$col][$row] %>\ +</PartII_<% $row + 1 %><% $cols[$col] %>> +% } +% } #for $col +% } #for $row +% } else { # HTML mode +% # fake up the search-html.html header +<H2>Part IIA</H2> +<TABLE> + <TR><TD VALIGN="bottom"><BR></TD></TR> + <TR><TD COLSPAN=2> + <TABLE CLASS="grid" CELLSPACING=0> + <TR> +% foreach (@row1_headers) { + <TH><% $_ %></TH> +% } + </TR> +% my $row = 0; +% foreach my $rowhead (@rows) { + <TR CLASS="row<%$row % 2%>"> + <TD STYLE="text-align: left; font-weight: bold"><% $rowhead %></TD> +% for my $col (0..3) { + <TD> +% if ( exists($data[$col][$row]) ) { + <% $data[$col][$row] %> +% } + </TD> +% } # for $col + </TR> +% $row++; +% } #for $rowhead + </TABLE> + </TD></TR> +</TABLE> +% } #XML/HTML <%init> my $curuser = $FS::CurrentUser::CurrentUser; @@ -19,83 +46,76 @@ my $curuser = $FS::CurrentUser::CurrentUser; die "access denied" unless $curuser->access_right('List packages'); -my $html_init = '<H2>Part IIA</H2>'; my %search_hash = (); - -for ( qw(agentnum magic state) ) { - $search_hash{$_} = $cgi->param($_) if $cgi->param($_); -} -$search_hash{'country'} = 'US'; -$search_hash{'classnum'} = [ $cgi->param('classnum') ]; - -my @row_option = grep { /^\d+$/ } $cgi->param('part2a_row_option') - if $cgi->param('part2a_row_option'); - -# fudge in two rows of LD carrier -unshift @row_option, $row_option[0]; - -# fudge in the first pair of rows -unshift @row_option, ''; -unshift @row_option, ''; - -my $query = 'SELECT '. join(' UNION SELECT ', 1..11); -my $total_count = 0; -my $column_value = sub { - my $row = shift; - - my @report_option = ( $row_option[$row - 1] || '' ); - - my $sql_query = FS::cust_pkg->search( - { %search_hash, 'report_option' => join(',', @report_option) } - ); - - my $count_sql = delete($sql_query->{'count_query'}); - if ( $row == 2 || $row == 4 ) { - $count_sql =~ s/COUNT\(\*\) FROM/sum(COALESCE(CASE WHEN cust_main.company IS NULL OR cust_main.company = '' THEN CASE WHEN part_pkg.fcc_ds0s IS NOT NULL AND part_pkg.fcc_ds0s > 0 THEN part_pkg.fcc_ds0s WHEN pkg_class.fcc_ds0s IS NOT NULL AND pkg_class.fcc_ds0s > 0 THEN pkg_class.fcc_ds0s ELSE 0 END ELSE 0 END, 0) ) FROM/ - or die "couldn't parse count_sql"; - } else { - $count_sql =~ s/COUNT\(\*\) FROM/sum(COALESCE(CASE WHEN part_pkg.fcc_ds0s IS NOT NULL AND part_pkg.fcc_ds0s > 0 THEN part_pkg.fcc_ds0s WHEN pkg_class.fcc_ds0s IS NOT NULL AND pkg_class.fcc_ds0s > 0 THEN pkg_class.fcc_ds0s ELSE 0 END, 0)) FROM/ - or die "couldn't parse count_sql"; - } - - my $count_sth = dbh->prepare($count_sql) - or die "Error preparing $count_sql: ". dbh->errstr; - $count_sth->execute - or die "Error executing $count_sql: ". $count_sth->errstr; - my $count_arrayref = $count_sth->fetchrow_arrayref; - my $count = $count_arrayref->[0]; +$search_hash{'agentnum'} = $cgi->param('agentnum'); +$search_hash{'state'} = $cgi->param('state'); +$search_hash{'classnum'} = [ $cgi->param('classnum') ]; +$search_hash{'status'} = 'active'; - $total_count = $count if $row == 1; - $count = sprintf('%.2f', $total_count ? 100*$count/$total_count : 0) - if $row != 1; +my @row_option; +foreach ($cgi->param('part2a_row_option')) { + push @row_option, (/^\d+$/ ? $_ : undef); +} - return "$count"; +my $is_residential = "AND COALESCE(cust_main.company, '') = ''"; +my $has_report_option = sub { + map { + defined($row_option[$_]) ? + " AND EXISTS( + SELECT 1 FROM part_pkg_option + WHERE part_pkg_option.pkgpart = part_pkg.pkgpart + AND optionname = 'report_option_" . $row_option[$_]."' + AND optionvalue = '1' + )" : ' AND FALSE' + } @_ }; -my @headers = ( - '', - 'End user lines', - 'UNE-P replacement', - 'UNE (unswitched)', - 'UNE-P', +# an arrayref for each column +my @data; +# get the skeleton of the query +my $sql_query = FS::cust_pkg->search(\%search_hash); +my $from_where = $sql_query->{'count_query'}; +$from_where =~ s/^SELECT COUNT\(\*\) //; + +# for row 1 +my $query_ds0 = "SELECT SUM(COALESCE(part_pkg.fcc_ds0s, pkg_class.fcc_ds0s, 0)) + $from_where AND fcc_voip_class = '4'"; # 4 = Local Exchange + +my $total_lines = FS::Record->scalar_sql($query_ds0); +# always return zero for the number of resold lines, until an actual ILEC +# starts using this report + +@data = ( + [ $total_lines ], + [ 0 ], + [ 0 ], + [ 0 ], ); -my @xml_elements = ( - sub { my $row = shift; my $rownum = $row->[0] + 1; "PartII_${rownum}a" }, - sub { my $row = shift; my $rownum = $row->[0] + 1; "PartII_${rownum}b" }, - sub { my $row = shift; my $rownum = $row->[0] + 1; "PartII_${rownum}c" }, - sub { my $row = shift; my $rownum = $row->[0] + 1; "PartII_${rownum}d" }, +my @row_conds = ( + $is_residential, + $has_report_option->(0), # LD carrier + ($has_report_option->(0))[0] . $is_residential, + $has_report_option->(1..7), ); +if ( $total_lines > 0 ) { + foreach (@row_conds) { + my $sql = $query_ds0 . $_; + my $lines = FS::Record->scalar_sql($sql); + my $percent = sprintf('%.2f', 100 * $lines / $total_lines); + push @{ $data[0] }, $percent; + } +} my @rows = ( 'lines', '% residential', '% LD carrier', - '% residential and LD carrier', - '% own loops', - '% obtained unswitched UNE loops', + '% residential and LD', + '% owned loops', + '% unswitched UNE', '% UNE-P', '% UNE-P replacement', '% FTTP', @@ -103,13 +123,12 @@ my @rows = ( '% wireless', ); -my @fields = ( - sub { my $row = shift; $rows[$row->[0] - 1]; }, - sub { my $row = shift; &{$column_value}($row->[0]); }, - sub { 0; }, - sub { 0; }, - sub { 0; }, +my @row1_headers = ( + '', + 'End user lines', + 'UNE-P replacement', + 'unswitched UNE', + 'UNE-P', ); -shift @fields if $cgi->param('_type') eq 'xml'; </%init> diff --git a/httemplate/search/477partIIB.html b/httemplate/search/477partIIB.html index c58310d36..5b9b30769 100755 --- a/httemplate/search/477partIIB.html +++ b/httemplate/search/477partIIB.html @@ -3,9 +3,10 @@ % for ( my $row = 0; $row < scalar(@rows); $row++ ) { % for my $col (0..2) { % if ( exists($data[$col][$row]) ) { -<PartII_<% $row %><% $cols[$col] %>> +<PartII_<% $row + 1 %><% $cols[$col] %>>\ +<% $data[$col][$row] %>\ +</PartII_<% $row + 1 %><% $cols[$col] %>> % } -</PartII_<% $row %><% $cols[$col] %>> % } #for $col % } #for $row % } else { # HTML mode @@ -14,19 +15,18 @@ <TABLE> <TR><TD VALIGN="bottom"><BR></TD></TR> <TR><TD COLSPAN=2> - <TABLE CLASS="grid" CELLSPACING=0 STYLE="border: 1px solid #cccccc;" BGCOLOR="#cccccc"> + <TABLE CLASS="grid" CELLSPACING=0> <TR> % foreach (@headers) { - <TH class="grid"><% $_ %></TH> + <TH><% $_ %></TH> % } </TR> -% my @bgcolor = ('eeeeee','ffffff'); % my $row = 0; % foreach my $rowhead (@rows) { - <TR> - <TD CLASS="grid" BGCOLOR="#<% $bgcolor[$row % 2] %>"><% $rowhead %></TD> + <TR CLASS="row<% $row % 2 %>"> + <TD STYLE="text-align: left; font-weight: bold"><% $rowhead %></TD> % for my $col (0..2) { - <TD CLASS="grid" BGCOLOR="#<% $bgcolor[$row % 2] %>"> + <TD> % if ( exists($data[$col][$row]) ) { <% $data[$col][$row] %> % } diff --git a/httemplate/search/477partV.html b/httemplate/search/477partV.html index 2fd5119d1..b2dd9ca95 100755 --- a/httemplate/search/477partV.html +++ b/httemplate/search/477partV.html @@ -1,4 +1,7 @@ -<% include( 'elements/search.html', +% if ( $cgi->param('_type') =~ /^xml$/ ) { +<zip_code> +% } +<& elements/search.html, 'html_init' => $html_init, 'name' => 'zip code', 'query' => $sql_query, @@ -12,8 +15,11 @@ 'url' => $opt{url} || '', 'really_disable_download' => 1, - ) -%> + +&> +% if ( $cgi->param('_type') =~ /^xml$/ ) { +</zip_code> +% } <%init> my $curuser = $FS::CurrentUser::CurrentUser; @@ -32,8 +38,8 @@ for ( qw(agentnum magic state) ) { } $search_hash{'country'} = 'US'; $search_hash{'classnum'} = [ $cgi->param('classnum') ]; -$search_hash{report_option} = $cgi->param('partv_report_option') - if $cgi->param('partv_report_option'); +$search_hash{report_option} = $cgi->param('part5_report_option') + if $cgi->param('part5_report_option'); my $sql_query = FS::cust_pkg->search( { %search_hash, 'fcc_line' => 1, diff --git a/httemplate/search/477partVI_census.html b/httemplate/search/477partVI_census.html index 8425c4b48..59a6fb50d 100755 --- a/httemplate/search/477partVI_census.html +++ b/httemplate/search/477partVI_census.html @@ -1,4 +1,4 @@ -<% include( 'elements/search.html', +<& elements/search.html, 'html_init' => '<H2>Part VI</H2>', 'html_foot' => $html_foot, 'name' => 'regions', @@ -24,8 +24,8 @@ 'url' => $opt{url} || '', 'xml_row_element' => 'Datarow', 'really_disable_download' => 1, - ) -%> + +&> <%init> my $curuser = $FS::CurrentUser::CurrentUser; diff --git a/httemplate/search/agent_commission.html b/httemplate/search/agent_commission.html new file mode 100644 index 000000000..b94ae9f6e --- /dev/null +++ b/httemplate/search/agent_commission.html @@ -0,0 +1,197 @@ +%# still not a good way to do rows grouped by some field in a search.html +%# report +% if ( $type eq 'xls' ) { +<% $data %>\ +% } else { +<& /elements/header.html, $title &> +<P ALIGN="right" CLASS="noprint"> +Download full results<BR> +as <A HREF="<% $cgi->self_url %>;_type=xls">Excel spreadsheet</A></P> +<BR> +<STYLE TYPE="text/css"> +td.cust_head { + border-left: none; + border-right: none; + padding-top: 0.5em; + font-weight: bold; + background-color: #ffffff; +} +td.money { text-align: right; } +td.money:before { content: '<% $money_char %>'; } +.row0 { background-color: #eeeeee; } +.row1 { background-color: #ffffff; } +</STYLE> +<& /elements/table-grid.html &> + <TR STYLE="background-color: #cccccc"> + <TH CLASS="grid">Package</TH> + <TH CLASS="grid">Sales</TH> + <TH CLASS="grid">Percentage</TH> + <TH CLASS="grid">Commission</TH> + </TR> +% my ($custnum, $sales, $commission, $row, $bgcolor) = (0, 0, 0, 0); +% foreach my $cust_pkg ( @cust_pkg ) { +% if ( $custnum ne $cust_pkg->custnum ) { +% # start of a new customer section +% my $cust_main = $cust_pkg->cust_main; +% my $label = $cust_main->custnum . ': '. $cust_main->name; +% $bgcolor = 0; + <TR> + <TD COLSPAN=4 CLASS="cust_head"> + <A HREF="<%$p%>view/cust_main.cgi?<%$cust_main->custnum%>"><% $label %></A> + </TD> + </TR> +% } + <TR CLASS="row<% $bgcolor %>"> + <TD CLASS="grid"><% $cust_pkg->pkg_label %></TD> + <TD CLASS="money"><% sprintf('%.2f', $cust_pkg->sum_charged) %></TD> + <TD ALIGN="right"><% $cust_pkg->percent %>%</TD> + <TD CLASS="money"><% sprintf('%.2f', + $cust_pkg->sum_charged * $cust_pkg->percent / 100) %></TD> + </TR> +% $sales += $cust_pkg->sum_charged; +% $commission += $cust_pkg->sum_charged * $cust_pkg->percent / 100; +% $row++; +% $bgcolor = 1-$bgcolor; +% $custnum = $cust_pkg->custnum; +% } + <TR STYLE="background-color: #f5f6be"> + <TD CLASS="grid"> + <% emt('[quant,_1,package] with commission', $row) %> + </TD> + <TD CLASS="money"><% sprintf('%.2f', $sales) %></TD> + <TD></TD> + <TD CLASS="money"><% sprintf('%.2f', $commission) %></TD> + </TR> +</TABLE> +<& /elements/footer.html &> +% } +<%init> +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Financial reports'); + +my ($begin, $end) = FS::UI::Web::parse_beginning_ending($cgi); +$cgi->param('agentnum') =~ /^(\d+)$/ or die "bad agentnum"; +my $agentnum = $1; +my $agent = FS::agent->by_key($agentnum); + +my $title = $agent->agent . ' commissions'; + +my $sum_charged = + '(SELECT SUM(setup + recur) FROM cust_bill_pkg JOIN cust_bill USING (invnum)'. + 'WHERE cust_bill_pkg.pkgnum = cust_pkg.pkgnum AND '. + "cust_bill._date >= $begin AND cust_bill._date < $end)"; + +my @select = ( + 'cust_pkg.*', + 'agent_pkg_class.commission_percent AS percent', + "$sum_charged AS sum_charged", +); + +my $query = { + 'table' => 'cust_pkg', + 'select' => join(',', @select), + 'addl_from' => 'JOIN cust_main USING (custnum) '. + 'JOIN part_pkg USING (pkgpart) '. + 'JOIN agent_pkg_class ON ( '. + 'cust_main.agentnum = agent_pkg_class.agentnum AND '. + '( agent_pkg_class.classnum = part_pkg.classnum OR '. + '(agent_pkg_class IS NULL AND part_pkg.classnum IS NULL)'. + ' ) ) ', + 'extra_sql' => "WHERE cust_main.agentnum = $agentnum AND ". + 'agent_pkg_class.commission_percent > 0 AND '. + "$sum_charged > 0", + 'order_by' => 'ORDER BY cust_pkg.custnum ASC', +}; + +my @cust_pkg = qsearch($query); + +my $money_char = FS::Conf->new->config('money_char') || '$'; + +my $data = ''; +my $type = $cgi->param('_type'); +if ( $type eq 'xls') { + # some false laziness with the above... + my $format = $FS::CurrentUser::CurrentUser->spreadsheet_format; + my $filename = 'agent_commission' . $format->{extension}; + http_header('Content-Type' => $format->{mime_type}); + http_header('Content-Disposition' => qq!attachment;filename="$filename"!); + my $XLS = IO::Scalar->new(\$data); + my $workbook = $format->{class}->new($XLS); + my $worksheet = $workbook->add_worksheet(substr($title, 0, 31)); + + my $cust_head_format = $workbook->add_format( + bold => 1, + underline => 1, + text_wrap => 0, + bg_color => 'white', + ); + + my $col_head_format = $workbook->add_format( + bold => 1, + align => 'center', + bg_color => 'silver' + ); + + my @format; + foreach (0, 1) { + my %bg = (bg_color => $_ ? 'white' : 'silver'); + $format[$_] = { + 'text' => $workbook->add_format(%bg), + 'money' => $workbook->add_format(%bg, num_format => $money_char.'#0.00'), + 'percent' => $workbook->add_format(%bg, num_format => '0.00%'), + }; + } + my $total_format = $workbook->add_format( + bg_color => 'yellow', + num_format => $money_char.'#0.00', + top => 1 + ); + + my ($r, $c) = (0, 0); + foreach (qw(Package Sales Percentage Commission)) { + $worksheet->write($r, $c++, $_, $col_head_format); + } + $r++; + + my ($custnum, $sales, $commission, $row, $bgcolor) = (0, 0, 0, 0); + my $label_length = 0; + foreach my $cust_pkg ( @cust_pkg ) { + if ( $custnum ne $cust_pkg->custnum ) { + # start of a new customer section + my $cust_main = $cust_pkg->cust_main; + my $label = $cust_main->custnum . ': '. $cust_main->name; + $bgcolor = 0; + $worksheet->set_row($r, 20); + $worksheet->merge_range($r, 0, $r, 3, $label, $cust_head_format); + $r++; + } + $c = 0; + my $percent = $cust_pkg->percent / 100; + $worksheet->write($r, $c++, $cust_pkg->pkg_label, $format[$bgcolor]{text}); + $worksheet->write($r, $c++, $cust_pkg->sum_charged, $format[$bgcolor]{money}); + $worksheet->write($r, $c++, $percent, $format[$bgcolor]{percent}); + $worksheet->write($r, $c++, ($cust_pkg->sum_charged * $percent), + $format[$bgcolor]{money}); + + $label_length = max($label_length, length($cust_pkg->pkg_label)); + $sales += $cust_pkg->sum_charged; + $commission += $cust_pkg->sum_charged * $cust_pkg->percent / 100; + $row++; + $bgcolor = 1-$bgcolor; + $custnum = $cust_pkg->custnum; + $r++; + } + + $c = 0; + $label_length = max($label_length, 20); + $worksheet->set_column($c, $c, $label_length); + $worksheet->write($r, $c++, mt('[quant,_1,package] with commission', $row), + $total_format); + $worksheet->set_column($c, $c + 2, 11); + $worksheet->write($r, $c++, $sales, $total_format); + $worksheet->write($r, $c++, '', $total_format); + $worksheet->write($r, $c++, $commission, $total_format); + + $workbook->close; +} +</%init> diff --git a/httemplate/search/agent_inventory.html b/httemplate/search/agent_inventory.html index ac65371ca..015aca46b 100644 --- a/httemplate/search/agent_inventory.html +++ b/httemplate/search/agent_inventory.html @@ -1,4 +1,4 @@ -<% include('elements/search.html', +<& elements/search.html, 'title' => 'Inventory summary per agent', 'name_singular' => 'agent', 'query' => { 'table' => 'agent', @@ -10,8 +10,7 @@ " AND $agentnums_sql", 'header' => \@header, 'fields' => \@fields, - ) -%> +&> <%init> die "access denied" diff --git a/httemplate/search/bill_batch.cgi b/httemplate/search/bill_batch.cgi index b6676f261..b740bdc68 100755 --- a/httemplate/search/bill_batch.cgi +++ b/httemplate/search/bill_batch.cgi @@ -26,7 +26,7 @@ function start() { % -expires => '-1d', % ); % $r->headers_out->add( 'Set-Cookie' => $cookie->as_string ); -<% include( 'elements/search.html', +<& elements/search.html, 'title' => 'Invoice Batches', 'name_singular' => 'batch', 'query' => { 'table' => 'bill_batch', @@ -67,9 +67,7 @@ function start() { 'agent_pos' => 1, 'html_foot' => include('.foot'), - ) - -%> +&> %} <%def .foot> <SCRIPT type="text/javascript"> diff --git a/httemplate/search/cdr.html b/httemplate/search/cdr.html index d0d7292d1..ca303d386 100644 --- a/httemplate/search/cdr.html +++ b/httemplate/search/cdr.html @@ -1,4 +1,4 @@ -<% include( 'elements/search.html', +<& elements/search.html, 'title' => $title, 'name' => 'call detail records', 'query' => $query, @@ -9,27 +9,8 @@ 'fields' => \@fields, 'links' => \@links, 'html_form' => qq!<FORM NAME="cdrForm" ACTION="$p/misc/cdr.cgi" METHOD="POST">!, - #false laziness w/queue.html - 'html_foot' => sub { - if ( $areboxes ) { - '<BR><INPUT TYPE="button" VALUE="select all" onClick="setAll(true)">'. - '<INPUT TYPE="button" VALUE="unselect all" onClick="setAll(false)">'. - qq!<BR><INPUT TYPE="submit" NAME="action" VALUE="reprocess selected" onClick="return confirm('Are you sure you want to reprocess the selected CDRs?')">!. - qq!<INPUT TYPE="submit" NAME="action" VALUE="delete selected" onClick="return confirm('Are you sure you want to delete the selected CDRs?')"><BR>!. - '<SCRIPT TYPE="text/javascript">'. - ' function setAll(setTo) { '. - ' theForm = document.cdrForm;'. - ' for (i=0,n=theForm.elements.length;i<n;i++)'. - ' if (theForm.elements[i].name.indexOf("acctid") != -1)'. - ' theForm.elements[i].checked = setTo;'. - ' }'. - '</SCRIPT>'; - } else { - ''; - } - }, - ) -%> + 'html_foot' => $html_foot, +&> <%init> die "access denied" @@ -44,8 +25,6 @@ my $totalminutes_sub = sub { my $conf = new FS::Conf; -my $areboxes = 0; - my $title = 'Call Detail Records'; my $hashref = {}; @@ -355,7 +334,6 @@ my %links = ( @fields = map { exists($fields{$_}) ? $fields{$_} : $_ } @fields; unshift @fields, sub { return '' unless $edit_data; - $areboxes = 1; my $cdr = shift; my $acctid = $cdr->acctid; qq!<INPUT NAME="acctid$acctid" TYPE="checkbox" VALUE="1">!; @@ -409,4 +387,14 @@ if ( $topmode ) { $nototalminutes = 1; } +my $html_foot = include('/search/elements/checkbox-foot.html', + actions => [ + { submit => "reprocess selected", + name => "action", + confirm => "Are you sure you want to reprocess the selected CDRs?" }, + { submit => "delete selected", + name => "action", + confirm => "Are you sure you want to delete the selected CDRs?" }, + ] +); </%init> diff --git a/httemplate/search/cust_bill.html b/httemplate/search/cust_bill.html index 3c0530e4f..473aed311 100755 --- a/httemplate/search/cust_bill.html +++ b/httemplate/search/cust_bill.html @@ -62,7 +62,7 @@ die "access denied" unless $FS::CurrentUser::CurrentUser->access_right('List invoices'); -my $join_cust_main = 'LEFT JOIN cust_main USING ( custnum )'; +my $join_cust_main = FS::UI::Web::join_cust_main('cust_bill'); #here is the agent virtualization my $agentnums_sql = $FS::CurrentUser::CurrentUser->agentnums_sql; @@ -97,7 +97,7 @@ if ( $cgi->param('invnum') =~ /^\s*(FS-)?(\d+)\s*$/ ) { $search{'refnum'} = $1; } - if ( $cgi->param('cust_classnum') ) { +if ( grep { $_ eq 'cust_classnum' } $cgi->param ) { $search{'cust_classnum'} = [ $cgi->param('cust_classnum') ]; } @@ -198,7 +198,6 @@ if ( $cgi->param('invnum') =~ /^\s*(FS-)?(\d+)\s*$/ ) { }; } - my $link = [ "${p}view/cust_bill.cgi?", 'invnum', ]; my $clink = sub { my $cust_bill = shift; diff --git a/httemplate/search/cust_bill_event.cgi b/httemplate/search/cust_bill_event.cgi index 90c89139c..9fb533a5f 100644 --- a/httemplate/search/cust_bill_event.cgi +++ b/httemplate/search/cust_bill_event.cgi @@ -1,4 +1,4 @@ -<% include( 'elements/search.html', +<& elements/search.html, 'title' => $title, 'html_init' => $html_init, 'menubar' => $menubar, @@ -60,8 +60,8 @@ '', FS::UI::Web::cust_styles(), ], - ) -%> + +&> <%init> my $curuser = $FS::CurrentUser::CurrentUser; @@ -100,7 +100,7 @@ my $where = 'WHERE '. FS::cust_bill_event->search_sql_where( \%search ); my $join = 'LEFT JOIN part_bill_event USING ( eventpart ) '. 'LEFT JOIN cust_bill USING ( invnum ) '. - 'LEFT JOIN cust_main USING ( custnum ) '; + FS::UI::Web::join_cust_main('cust_bill'); my $sql_query = { 'table' => 'cust_bill_event', diff --git a/httemplate/search/cust_bill_pay.html b/httemplate/search/cust_bill_pay.html index 79de74985..ff20458d8 100644 --- a/httemplate/search/cust_bill_pay.html +++ b/httemplate/search/cust_bill_pay.html @@ -1,4 +1,4 @@ -<% include( 'elements/search.html', +<& elements/search.html, 'title' => $title, 'name' => 'net payments', 'query' => $sql_query, @@ -71,8 +71,8 @@ '', FS::UI::Web::cust_styles(), ], - ) -%> + +&> <%init> die "access denied" @@ -99,9 +99,12 @@ if ( $cgi->param('refnum') && $cgi->param('refnum') =~ /^(\d+)$/ ) { $title = $part_referral->referral. " $title"; } -if ( $cgi->param('cust_classnum') ) { - my @classnums = grep /^\d+$/, $cgi->param('cust_classnum'); - push @search, 'cust_main.classnum IN('.join(',',@classnums).')' +# cust_classnum (false laziness w/ elements/cust_main_dayranges.html, prepaid_income.html, cust_bill_pkg.html, cust_bill_pkg_referral.html, unearned_detail.html, cust_credit.html, cust_credit_refund.html, cust_main::Search::search_sql) +if ( grep { $_ eq 'cust_classnum' } $cgi->param ) { + my @classnums = grep /^\d*$/, $cgi->param('cust_classnum'); + push @search, 'COALESCE( cust_main.classnum, 0) IN ( '. + join(',', map { $_ || '0' } @classnums ). + ' )' if @classnums; } @@ -117,8 +120,8 @@ my $where = 'WHERE '. join(' AND ', @search); # my $count_query = 'SELECT COUNT(*), SUM(amount) FROM cust_bill_pay - LEFT JOIN cust_bill USING ( invnum ) - LEFT JOIN cust_main USING ( custnum ) '. + LEFT JOIN cust_bill USING ( invnum ) '. + FS::UI::Web::join_cust_main('cust_bill') . $where; my $sql_query = { @@ -137,8 +140,8 @@ my $sql_query = { 'hashref' => {}, 'extra_sql' => $where, 'addl_from' => 'LEFT JOIN cust_bill USING ( invnum ) - LEFT JOIN cust_pay USING ( paynum ) - LEFT JOIN cust_main ON ( cust_bill.custnum = cust_main.custnum )', + LEFT JOIN cust_pay USING ( paynum ) '. + FS::UI::Web::join_cust_main('cust_bill') }; my $cust_bill_link = sub { diff --git a/httemplate/search/cust_bill_pkg.cgi b/httemplate/search/cust_bill_pkg.cgi index 1e67e9320..3a3b0feb9 100644 --- a/httemplate/search/cust_bill_pkg.cgi +++ b/httemplate/search/cust_bill_pkg.cgi @@ -222,9 +222,9 @@ if ( $conf->exists('enable_taxclasses') ) { # valid in both the tax and non-tax cases my $join_cust = - " LEFT JOIN cust_bill USING (invnum) - LEFT JOIN cust_main USING (custnum) - "; + " LEFT JOIN cust_bill USING (invnum)". + # use cust_pkg.locationnum if it exists + FS::UI::Web::join_cust_main('cust_bill', 'cust_pkg'); #agent virtualization my $agentnums_sql = @@ -260,13 +260,16 @@ if ( $cgi->param('refnum') =~ /^(\d+)$/ ) { push @where, "cust_main.refnum = $1"; } -# cust_classnum -if ( $cgi->param('cust_classnum') ) { - my @classnums = grep /^\d+$/, $cgi->param('cust_classnum'); - push @where, 'cust_main.classnum IN('.join(',',@classnums).')' +# cust_classnum (false laziness w/ elements/cust_main_dayranges.html, elements/cust_pay_or_refund.html, prepaid_income.html, cust_bill_pay.html, cust_bill_pkg_referral.html, unearned_detail.html, cust_credit.html, cust_credit_refund.html, cust_main::Search::search_sql) +if ( grep { $_ eq 'cust_classnum' } $cgi->param ) { + my @classnums = grep /^\d*$/, $cgi->param('cust_classnum'); + push @where, 'COALESCE( cust_main.classnum, 0) IN ( '. + join(',', map { $_ || '0' } @classnums ). + ' )' if @classnums; } + # custnum if ( $cgi->param('custnum') =~ /^(\d+)$/ ) { push @where, "cust_main.custnum = $1"; @@ -278,11 +281,11 @@ my $join_pkg = LEFT JOIN part_pkg USING (pkgpart)'; my $part_pkg = 'part_pkg'; -if ( $cgi->param('use_override') ) { +if ( $cgi->param('use_override') ) { #"Separate sub-packages from parents" # still need the real part_pkg for tax applicability, # so alias this one $join_pkg .= " LEFT JOIN part_pkg AS override ON ( - COALESCE(cust_bill_pkg.pkgpart_override, cust_pkg.pkgpart, 0) = part_pkg.pkgpart + COALESCE(cust_bill_pkg.pkgpart_override, cust_pkg.pkgpart, 0) = override.pkgpart )"; $part_pkg = 'override'; } @@ -559,12 +562,11 @@ if ( $cgi->param('nottax') ) { #total payments -my $pay_sub = "SELECT SUM(cust_bill_pay_pkg.amount) AS pay_amount, - billpkgnum - FROM cust_bill_pay_pkg - GROUP BY billpkgnum"; -$join_pkg .= " LEFT JOIN ($pay_sub) AS item_pay USING (billpkgnum)"; -push @select, 'item_pay.pay_amount'; +my $pay_sub = "SELECT SUM(cust_bill_pay_pkg.amount) + FROM cust_bill_pay_pkg + WHERE cust_bill_pkg.billpkgnum = cust_bill_pay_pkg.billpkgnum + "; +push @select, "($pay_sub) AS pay_amount"; # credit @@ -630,13 +632,12 @@ if ( $cgi->param('credit') ) { #still want a credit total column - my $credit_sub = "SELECT SUM(cust_credit_bill_pkg.amount) AS credit_amount, - billpkgnum - FROM cust_credit_bill_pkg - GROUP BY billpkgnum"; - $join_pkg .= " LEFT JOIN ($credit_sub) AS item_credit USING (billpkgnum)"; - - push @select, 'item_credit.credit_amount'; + my $credit_sub = " + SELECT SUM(cust_credit_bill_pkg.amount) + FROM cust_credit_bill_pkg + WHERE cust_bill_pkg.billpkgnum = cust_credit_bill_pkg.billpkgnum + "; + push @select, "($credit_sub) AS credit_amount"; } @@ -647,7 +648,7 @@ $where &&= "WHERE $where"; my $query = { 'table' => 'cust_bill_pkg', - 'addl_from' => "$join_cust $join_pkg", + 'addl_from' => "$join_pkg $join_cust", 'hashref' => {}, 'select' => join(",\n", @select ), 'extra_sql' => $where, @@ -656,7 +657,7 @@ my $query = { my $count_query = 'SELECT ' . join(',', @total) . - " FROM cust_bill_pkg $join_cust $join_pkg + " FROM cust_bill_pkg $join_pkg $join_cust $where"; @peritem_desc = map {emt($_)} @peritem_desc; diff --git a/httemplate/search/cust_bill_pkg_discount.html b/httemplate/search/cust_bill_pkg_discount.html index bb8038a44..f598341a0 100644 --- a/httemplate/search/cust_bill_pkg_discount.html +++ b/httemplate/search/cust_bill_pkg_discount.html @@ -1,4 +1,4 @@ -<% include( 'elements/search.html', +<& elements/search.html, 'title' => 'Discounts', 'name' => 'discounts', 'query' => $query, @@ -68,8 +68,8 @@ '', FS::UI::Web::cust_styles(), ], - ) -%> + +&> <%init> #a little false laziness below w/cust_bill_pkg.cgi @@ -127,12 +127,12 @@ my $join_cust_pkg_discount = 'LEFT JOIN cust_pkg_discount USING (pkgdiscountnum)'; my $join_cust = - ' JOIN cust_bill_pkg USING ( billpkgnum ) - JOIN cust_bill USING ( invnum ) - LEFT JOIN cust_main USING ( custnum ) '; + ' JOIN cust_bill USING ( invnum ) '. + FS::UI::Web::join_cust_main('cust_bill', 'cust_pkg'); my $join_pkg = - ' LEFT JOIN cust_pkg ON ( cust_bill_pkg.pkgnum = cust_pkg.pkgnum ) + ' JOIN cust_bill_pkg USING ( billpkgnum ) + LEFT JOIN cust_pkg ON ( cust_bill_pkg.pkgnum = cust_pkg.pkgnum ) LEFT JOIN part_pkg USING ( pkgpart ) '; #LEFT JOIN part_pkg AS override # ON pkgpart_override = override.pkgpart '; @@ -140,7 +140,7 @@ my $join_pkg = my $where = ' WHERE '. join(' AND ', @where); $count_query .= - " FROM cust_bill_pkg_discount $join_cust_pkg_discount $join_cust $join_pkg ". + " FROM cust_bill_pkg_discount $join_cust_pkg_discount $join_pkg $join_cust ". $where; my @select = ( @@ -155,7 +155,7 @@ push @select, 'cust_main.custnum', my $query = { 'table' => 'cust_bill_pkg_discount', - 'addl_from' => "$join_cust_pkg_discount $join_cust $join_pkg", + 'addl_from' => "$join_cust_pkg_discount $join_pkg $join_cust", 'hashref' => {}, 'select' => join(', ', @select ), 'extra_sql' => $where, diff --git a/httemplate/search/cust_bill_pkg_referral.html b/httemplate/search/cust_bill_pkg_referral.html index 1289ff7ee..c4dde32a0 100644 --- a/httemplate/search/cust_bill_pkg_referral.html +++ b/httemplate/search/cust_bill_pkg_referral.html @@ -156,9 +156,13 @@ if ( @refnum ) { push @where, 'cust_main.refnum IN ('.join(',', @refnum).')'; } -my @cust_classnums = grep /^\d+$/, $cgi->param('cust_classnum'); -if ( @cust_classnums ) { - push @where, 'cust_main.classnum IN ('.join(',', @cust_classnums).')'; +# cust_classnum (false laziness w/ elements/cust_main_dayranges.html, elements/cust_pay_or_refund.html, prepaid_income.html, cust_bill_pay.html, cust_bill_pkg.html, unearned_detail.html, cust_credit.html, cust_credit_refund.html, cust_main::Search::search_sql) +if ( grep { $_ eq 'cust_classnum' } $cgi->param ) { + my @classnums = grep /^\d*$/, $cgi->param('cust_classnum'); + push @where, 'COALESCE( cust_main.classnum, 0) IN ( '. + join(',', map { $_ || '0' } @classnums ). + ' )' + if @classnums; } if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) { diff --git a/httemplate/search/cust_credit.html b/httemplate/search/cust_credit.html index f5d8fa19f..cabf8c002 100755 --- a/httemplate/search/cust_credit.html +++ b/httemplate/search/cust_credit.html @@ -103,9 +103,13 @@ if ( $cgi->param('refnum') && $cgi->param('refnum') =~ /^(\d+)$/ ) { $title = $part_referral->referral. " $title"; } -if ( $cgi->param('cust_classnum') ) { - my @classnums = grep /^\d+$/, $cgi->param('cust_classnum'); - push @search, 'cust_main.classnum IN('.join(',',@classnums).')' + +# cust_classnum (false laziness w/ elements/cust_main_dayranges.html, elements/cust_pay_or_refund.html, prepaid_income.html, cust_bill_pay.html, cust_bill_pkg.html, cust_bill_pkg_referral.html, unearned_detail.html, cust_credit_refund.html, cust_main::Search::search_sql) +if ( grep { $_ eq 'cust_classnum' } $cgi->param ) { + my @classnums = grep /^\d*$/, $cgi->param('cust_classnum'); + push @search, 'COALESCE( cust_main.classnum, 0) IN ( '. + join(',', map { $_ || '0' } @classnums ). + ' )' if @classnums; } @@ -137,7 +141,7 @@ my $where = 'WHERE '. join(' AND ', @search); my $count_query = 'SELECT COUNT(*), SUM(amount) '; $count_query .= ', SUM(' . FS::cust_credit->unapplied_sql . ') ' if $unapplied; -$count_query .= 'FROM cust_credit LEFT JOIN cust_main USING ( custnum ) '. +$count_query .= 'FROM cust_credit'. FS::UI::Web::join_cust_main('cust_credit'). $where; my @count_addl = ( $money_char.'%.2f total credited (gross)' ); @@ -148,7 +152,7 @@ my $sql_query = { 'select' => join(', ',@select), 'hashref' => {}, 'extra_sql' => $where, - 'addl_from' => 'LEFT JOIN cust_main USING ( custnum )', + 'addl_from' => FS::UI::Web::join_cust_main('cust_credit') }; </%init> diff --git a/httemplate/search/cust_credit_bill.html b/httemplate/search/cust_credit_bill.html index 9fd6a987a..88f897d70 100644 --- a/httemplate/search/cust_credit_bill.html +++ b/httemplate/search/cust_credit_bill.html @@ -1,4 +1,4 @@ -<% include( 'elements/search.html', +<& elements/search.html, 'title' => $title, 'name' => 'net credits', 'query' => $sql_query, @@ -64,8 +64,8 @@ '', FS::UI::Web::cust_styles(), ], - ) -%> + +&> <%init> die "access denied" @@ -103,8 +103,8 @@ my $where = 'WHERE '. join(' AND ', @search); # my $count_query = 'SELECT COUNT(*), SUM(amount) FROM cust_credit_bill - LEFT JOIN cust_bill USING ( invnum ) - LEFT JOIN cust_main USING ( custnum ) '. + LEFT JOIN cust_bill USING ( invnum ) '. + FS::UI::Web::join_cust_main('cust_bill') . $where; my $sql_query = { @@ -121,8 +121,8 @@ my $sql_query = { 'hashref' => {}, 'extra_sql' => $where, 'addl_from' => 'LEFT JOIN cust_bill USING ( invnum ) - LEFT JOIN cust_credit USING ( crednum ) - LEFT JOIN cust_main ON ( cust_bill.custnum = cust_main.custnum )', + LEFT JOIN cust_credit USING ( crednum )'. + FS::UI::Web::join_cust_main('cust_bill') }; my $cust_bill_link = sub { diff --git a/httemplate/search/cust_credit_bill_pkg.html b/httemplate/search/cust_credit_bill_pkg.html index 06fd881a8..63d70c27e 100644 --- a/httemplate/search/cust_credit_bill_pkg.html +++ b/httemplate/search/cust_credit_bill_pkg.html @@ -1,4 +1,4 @@ -<% include( 'elements/search.html', +<& elements/search.html, 'title' => 'Credit application detail', #to line item 'name_singular' => 'credit application', 'query' => $query, @@ -16,6 +16,7 @@ # line item 'Description', + 'Location', @post_desc_header, #invoice @@ -35,6 +36,7 @@ ? $_[0]->get('pkg') # possibly use override.pkg : $_[0]->get('itemdesc') # but i think this correct }, + $location_sub, @post_desc, 'invnum', sub { time2str('%b %d %Y', shift->_date ) }, @@ -46,6 +48,7 @@ '', #'otaker', '', #reason '', #line item description + '', #location @post_desc_null, 'invnum', '_date', @@ -57,6 +60,7 @@ '', '', '', + '', @post_desc_null, $ilink, $ilink, @@ -64,7 +68,7 @@ FS::UI::Web::cust_header() ), ], - 'align' => 'rrlll'. + 'align' => 'rrllll'. $post_desc_align. 'rr'. FS::UI::Web::cust_aligns(), @@ -74,6 +78,7 @@ '', '', '', + '', @post_desc_null, '', '', @@ -85,13 +90,14 @@ '', '', '', + '', @post_desc_null, '', '', FS::UI::Web::cust_styles(), ], - ) -%> + +&> <%init> #LOTS of false laziness below w/cust_bill_pkg.cgi @@ -377,8 +383,8 @@ my $count_query = "SELECT COUNT(DISTINCT creditbillpkgnum), SUM(cust_credit_bill_pkg.amount)"; my $join_cust = - ' JOIN cust_bill ON ( cust_bill_pkg.invnum = cust_bill.invnum ) - LEFT JOIN cust_main ON ( cust_bill.custnum = cust_main.custnum ) '; + ' JOIN cust_bill ON ( cust_bill_pkg.invnum = cust_bill.invnum )'. + FS::UI::Web::join_cust_main('cust_bill', 'cust_pkg'); my $join_pkg; @@ -423,10 +429,9 @@ if ( $cgi->param('nottax') ) { s/cust_pkg\.locationnum/cust_bill_pkg_tax_location.locationnum/g for @where; } -} else { +} else { - #die? - warn "neiether nottax nor istax parameters specified"; + #warn "neither nottax nor istax parameters specified"; #same as before? $join_pkg = ' LEFT JOIN cust_pkg USING ( pkgnum ) LEFT JOIN part_pkg USING ( pkgpart ) '; @@ -459,7 +464,7 @@ my @post_desc_header = (); my @post_desc = (); my @post_desc_null = (); my $post_desc_align = ''; -if ( $conf->exists('enable_taxclasses') ) { +if ( $conf->exists('enable_taxclasses') && ! $cgi->param('istax') ) { push @post_desc_header, 'Tax class'; push @post_desc, 'taxclass'; push @post_desc_null, ''; @@ -485,4 +490,57 @@ my $clink = [ "${p}view/cust_main.cgi?", 'custnum' ]; my $conf = new FS::Conf; my $money_char = $conf->config('money_char') || '$'; +my $tax_pkg_address = $conf->exists('tax-pkg_address'); +my $tax_ship_address = $conf->exists('tax-ship_address'); + +my $location_sub = sub { + #my $cust_credit_bill_pkg = shift; + my $self = shift; + my $tax_Xlocation = $self->cust_bill_pkg_tax_Xlocation; + if ( defined($tax_Xlocation) && $tax_Xlocation ) { + + if ( ref($tax_Xlocation) eq 'FS::cust_bill_pkg_tax_location' ) { + + if ( $tax_Xlocation->taxtype eq 'FS::cust_main_county' ) { + my $cust_main_county = $tax_Xlocation->cust_main_county; + if ( $cust_main_county ) { + $cust_main_county->label; + } else { + ''; #cust_main_county record is gone... history? yuck. + } + } else { + '(CCH tax_rate)'; #XXX FS::tax_rate.. vendor taxes not yet handled here + } + + } elsif ( ref($tax_Xlocation) eq 'FS::cust_bill_pkg_tax_rate_location' ) { + '(CCH)'; #XXX vendor taxes not yet handled here + } else { + 'unknown tax_Xlocation '. ref($tax_Xlocation); + } + + } else { + + my $cust_bill_pkg = $self->cust_bill_pkg; + if ( $cust_bill_pkg->pkgnum > 0 ) { + my $cust_pkg = $cust_bill_pkg->cust_pkg; + if ( $tax_pkg_address && (my $cust_location = $cust_pkg->cust_location) ){ + $cust_location->county_state_country; + } else { + my $cust_main = $cust_pkg->cust_main; + if ( $tax_ship_address && $cust_main->has_ship_address ) { + $cust_main->county_state_country('ship_'); + } else { + $cust_main->county_state_country; + } + } + + } else { + #tax? we shouldn't have wound up here then... + ''; #return customer ship or bill address? (depending on tax-ship_address) + } + + } + +}; + </%init> diff --git a/httemplate/search/cust_credit_refund.html b/httemplate/search/cust_credit_refund.html index 75138e99d..817420054 100644 --- a/httemplate/search/cust_credit_refund.html +++ b/httemplate/search/cust_credit_refund.html @@ -1,4 +1,4 @@ -<% include( 'elements/search.html', +<& elements/search.html, 'title' => $title, 'name' => 'net refunds', 'query' => $sql_query, @@ -57,8 +57,8 @@ '', FS::UI::Web::cust_styles(), ], - ) -%> + +&> <%init> die "access denied" @@ -85,9 +85,12 @@ if ( $cgi->param('refnum') && $cgi->param('refnum') =~ /^(\d+)$/ ) { $title = $part_referral->referral. " $title"; } -if ( $cgi->param('cust_classnum') ) { - my @classnums = grep /^\d+$/, $cgi->param('cust_classnum'); - push @search, 'cust_main.classnum IN('.join(',',@classnums).')' +# cust_classnum (false laziness w/ elements/cust_main_dayranges.html, elements/cust_pay_or_refund.html, prepaid_income.html, cust_bill_pay.html, cust_bill_pkg.html, cust_bill_pkg_referral.html, unearned_detail.html, cust_credit.html, cust_main::Search::search_sql) +if ( grep { $_ eq 'cust_classnum' } $cgi->param ) { + my @classnums = grep /^\d*$/, $cgi->param('cust_classnum'); + push @search, 'COALESCE( cust_main.classnum, 0) IN ( '. + join(',', map { $_ || '0' } @classnums ). + ' )' if @classnums; } @@ -103,8 +106,8 @@ my $where = 'WHERE '. join(' AND ', @search); # my $count_query = 'SELECT COUNT(*), SUM(cust_credit_refund.amount) FROM cust_credit_refund - LEFT JOIN cust_credit USING ( crednum ) - LEFT JOIN cust_main USING ( custnum ) '. + LEFT JOIN cust_credit USING ( crednum ) '. + FS::UI::Web::join_cust_main('cust_credit') . $where; my $sql_query = { @@ -121,8 +124,8 @@ my $sql_query = { 'hashref' => {}, 'extra_sql' => $where, 'addl_from' => 'LEFT JOIN cust_credit USING ( crednum ) - LEFT JOIN cust_refund USING ( refundnum ) - LEFT JOIN cust_main ON ( cust_credit.custnum = cust_main.custnum )', + LEFT JOIN cust_refund USING ( refundnum )'. + FS::UI::Web::join_cust_main('cust_credit') }; #my $cust_credit_link = sub { diff --git a/httemplate/search/cust_event.html b/httemplate/search/cust_event.html index deb34b9e5..bfc5f43e8 100644 --- a/httemplate/search/cust_event.html +++ b/httemplate/search/cust_event.html @@ -1,4 +1,4 @@ -<% include( 'elements/search.html', +<& elements/search.html, 'title' => $title, 'html_init' => $html_init, 'menubar' => $menubar, @@ -62,8 +62,7 @@ #'', FS::UI::Web::cust_styles(), ], - ) -%> +&> <%once> my $status_sub = sub { @@ -175,7 +174,13 @@ $search{'ending'} = $ending; my $where = ' WHERE '. FS::cust_event->search_sql_where( \%search ); -my $join = FS::cust_event->join_sql(); +my $join = FS::cust_event->join_sql() . + '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. my $sql_query = { 'table' => 'cust_event', diff --git a/httemplate/search/cust_main-zip.html b/httemplate/search/cust_main-zip.html index 08800d431..f5f8c8f3c 100644 --- a/httemplate/search/cust_main-zip.html +++ b/httemplate/search/cust_main-zip.html @@ -1,4 +1,4 @@ -<% include( 'elements/search.html', +<& elements/search.html, 'title' => 'Zip code Search Results', 'name' => 'zip codes', 'query' => $sql_query, @@ -6,8 +6,7 @@ 'header' => [ 'Zip code', 'Customers', ], 'fields' => [ 0, 1 ], 'links' => [ '', $link ], - ) -%> +&> <%init> die "access denied" diff --git a/httemplate/search/cust_main.cgi b/httemplate/search/cust_main.cgi index 8e3c8133e..2c09c692c 100755 --- a/httemplate/search/cust_main.cgi +++ b/httemplate/search/cust_main.cgi @@ -244,7 +244,7 @@ % my $pkg_rowspan = shift @pkg_rowspans; <% $n1 %><TD CLASS="grid" BGCOLOR="<% $bgcolor %>" ROWSPAN="<% $pkg_rowspan%>"> - <A HREF="<% $pkgview %>"><FONT SIZE=-1><% $pkg_comment %></FONT></A> + <A HREF="<% $pkgview %>"><FONT SIZE=-1><% $pkg_comment |h %></FONT></A> </TD> % my $n2 = ''; diff --git a/httemplate/search/cust_main.html b/httemplate/search/cust_main.html index 8b39ea962..24348ff8a 100755 --- a/httemplate/search/cust_main.html +++ b/httemplate/search/cust_main.html @@ -42,10 +42,11 @@ my %search_hash = (); #scalars my @scalars = qw ( agentnum status address zip paydate_year paydate_month invoice_terms - no_censustract with_geocode with_email no_POST + no_censustract with_geocode with_email POST no_POST custbatch usernum cancelled_pkgs cust_fields flattened_pkgs + all_tags ); for my $param ( @scalars ) { diff --git a/httemplate/search/cust_pay_batch.cgi b/httemplate/search/cust_pay_batch.cgi index 800df8702..9f9eb30ce 100755 --- a/httemplate/search/cust_pay_batch.cgi +++ b/httemplate/search/cust_pay_batch.cgi @@ -1,4 +1,4 @@ -<% include('elements/search.html', +<& elements/search.html, 'title' => 'Batch payment details', 'name' => 'batch details', 'query' => $sql_query, @@ -7,55 +7,41 @@ 'disable_download' => 1, 'header' => [ '#', 'Inv #', - 'Customer', + 'Cust #', 'Customer', 'Card Name', 'Card', 'Exp', 'Amount', 'Status', + '', # error_message ], - 'fields' => [ sub { - shift->[0]; - }, - sub { - shift->[1]; - }, - sub { - shift->[2]; - }, - sub { - my $cpb = shift; - $cpb->[3] . ', ' . $cpb->[4]; - }, - sub { - shift->[5]; - }, - sub { - my $cardnum = shift->[6]; - 'x'x(length($cardnum)-4). substr($cardnum,(length($cardnum)-4)); - }, - sub { - shift->[7] =~ /^\d{2}(\d{2})[\/\-](\d+)[\/\-]\d+$/; - my( $mon, $year ) = ( $2, $1 ); - $mon = "0$mon" if length($mon) == 1; - "$mon/$year"; - }, - sub { - shift->[8]; - }, - sub { - shift->[9]; - }, - ], - 'align' => 'lllllllrl', - 'links' => [ ['', sub{'#';}], - ["${p}view/cust_bill.cgi?", sub{shift->[1];},], - ["${p}view/cust_main.cgi?", sub{shift->[2];},], - ["${p}view/cust_main.cgi?", sub{shift->[2];},], + 'fields' => [ 'paybatchnum', + 'invnum', + 'custnum', + sub { $_[0]->cust_main->name_short }, + 'payname', + 'mask_payinfo', + sub { + return('') if $_[0]->payby ne 'CARD'; + $_[0]->get('exp') =~ /^\d\d(\d\d)-(\d\d)/; + sprintf('%02d/%02d',$1,$2); + }, + sub { + sprintf('%.02f', $_[0]->amount) + }, + 'status', + 'error_message', + ], + 'align' => 'rrrlllcrll', + 'links' => [ '', + ["${p}view/cust_bill.cgi?", 'invnum'], + (["${p}view/cust_main.cgi?", 'custnum']) x 2, ], - ) -%> + 'link_onclicks' => [ ('') x 8, + $sub_receipt + ], +&> <%init> my $conf = new FS::Conf; @@ -101,7 +87,7 @@ if ( $cgi->param('payby') ) { } if ( not $cgi->param('dcln') ) { - push @search, "cpb.status IS DISTINCT FROM 'Approved'"; + push @search, "cust_pay_batch.status IS DISTINCT FROM 'Approved'"; } my ($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi); @@ -119,18 +105,30 @@ push @search, $curuser->agentnums_sql({ table => 'pay_batch', my $search = ' WHERE ' . join(' AND ', @search); -$count_query = 'SELECT COUNT(*) FROM cust_pay_batch AS cpb ' . +$count_query = 'SELECT COUNT(*) FROM cust_pay_batch ' . 'LEFT JOIN cust_main USING ( custnum ) ' . 'LEFT JOIN pay_batch USING ( batchnum )' . $search; -#grr -$sql_query = "SELECT paybatchnum,invnum,custnum,cpb.last,cpb.first," . - "cpb.payname,cpb.payinfo,cpb.exp,amount,cpb.status " . - "FROM cust_pay_batch AS cpb " . - 'LEFT JOIN cust_main USING ( custnum ) ' . - 'LEFT JOIN pay_batch USING ( batchnum ) ' . - "$search ORDER BY $orderby"; +$sql_query = { + 'table' => 'cust_pay_batch', + 'select' => 'cust_pay_batch.*, cust_main.*, cust_pay.paynum', + 'hashref' => {}, + 'addl_from' => 'LEFT JOIN pay_batch USING ( batchnum ) '. + 'LEFT JOIN cust_main USING ( custnum ) '. + + 'LEFT JOIN cust_pay USING ( batchnum, custnum ) ', + 'extra_sql' => $search, + 'order_by' => "ORDER BY $orderby", +}; + +my $sub_receipt = sub { + my $paynum = shift->paynum or return ''; + include('/elements/popup_link_onclick.html', + 'action' => $p.'view/cust_pay.html?link=popup;paynum='.$paynum, + 'actionlabel' => emt('Payment Receipt'), + ); +}; my $html_init = ''; if ( $pay_batch ) { diff --git a/httemplate/search/cust_pkg.cgi b/httemplate/search/cust_pkg.cgi index 887ec6039..110da91ae 100755 --- a/httemplate/search/cust_pkg.cgi +++ b/httemplate/search/cust_pkg.cgi @@ -9,6 +9,7 @@ emt('Package'), emt('Class'), emt('Status'), + emt('Ordered by'), emt('Setup'), emt('Base Recur'), emt('Freq.'), @@ -34,6 +35,7 @@ sub { $_[0]->pkg; }, 'classname', sub { ucfirst(shift->status); }, + 'otaker', sub { sprintf( $money_char.'%.2f', shift->part_pkg->option('setup_fee'), ); @@ -96,13 +98,14 @@ '', '', '', + '', FS::UI::Web::cust_colors(), '', ], - 'style' => [ '', '', '', '', 'b', '', '', '', '', '', '', '', '', '', '', '', '', '', '', + 'style' => [ '', '', '', '', 'b', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', FS::UI::Web::cust_styles() ], 'size' => [ '', '', '', '', '-1' ], - 'align' => 'rrlccrrlrrrrrrrrrrl'. FS::UI::Web::cust_aligns(). 'r', + 'align' => 'rrlcccrrlrrrrrrrrrrl'. FS::UI::Web::cust_aligns(). 'r', 'links' => [ $link, $link, @@ -119,6 +122,7 @@ '', '', '', + '', '', # link to changed-from package? '', '', diff --git a/httemplate/search/cust_pkg_discount.html b/httemplate/search/cust_pkg_discount.html index d70c3116f..23af1802e 100644 --- a/httemplate/search/cust_pkg_discount.html +++ b/httemplate/search/cust_pkg_discount.html @@ -1,4 +1,4 @@ -<% include( 'elements/search.html', +<& elements/search.html, 'title' => 'Package discounts', 'name' => 'discounts', 'query' => $query, @@ -50,8 +50,8 @@ '', FS::UI::Web::cust_styles(), ], - ) -%> + +&> <%init> die "access denied" @@ -92,8 +92,8 @@ my $count_query = "SELECT COUNT(*), SUM(amount)"; my $join = ' LEFT JOIN discount USING ( discountnum ) LEFT JOIN cust_pkg USING ( pkgnum ) - LEFT JOIN part_pkg USING ( pkgpart ) - LEFT JOIN cust_main USING ( custnum ) '; + LEFT JOIN part_pkg USING ( pkgpart ) '. + FS::UI::Web::join_cust_main('cust_pkg', 'cust_pkg'); my $where = ' WHERE '. join(' AND ', @where); diff --git a/httemplate/search/cust_pkg_svc.html b/httemplate/search/cust_pkg_svc.html index 9c5b32fc7..cdc70351a 100644 --- a/httemplate/search/cust_pkg_svc.html +++ b/httemplate/search/cust_pkg_svc.html @@ -1,4 +1,4 @@ -<% include( 'elements/search.html', +<& elements/search.html, 'title' => $part_svc->svc.' services in package #'.$pkgnum, 'name' => 'services', 'html_form' => $html_form, @@ -30,8 +30,8 @@ ('')x4, ], 'html_foot' => sub { $areboxes ? $html_foot : '' } - ) -%> + +&> <%init> die "access denied" diff --git a/httemplate/search/cust_svc.html b/httemplate/search/cust_svc.html index 2adcbd76f..e2a83b7de 100644 --- a/httemplate/search/cust_svc.html +++ b/httemplate/search/cust_svc.html @@ -62,7 +62,7 @@ if ( length( $cgi->param('search_svc') ) ) { my $addl_from = ' LEFT JOIN part_svc USING ( svcpart ) '. ' LEFT JOIN cust_pkg USING ( pkgnum ) '. - ' LEFT JOIN cust_main USING ( custnum ) '; + FS::UI::Web::join_cust_main('cust_pkg', 'cust_pkg'); my @extra_sql = (); diff --git a/httemplate/search/cust_tax_adjustment.html b/httemplate/search/cust_tax_adjustment.html index 925476516..6125a1c04 100644 --- a/httemplate/search/cust_tax_adjustment.html +++ b/httemplate/search/cust_tax_adjustment.html @@ -1,4 +1,4 @@ -<% include( 'elements/search.html', +<& elements/search.html, 'title' => $title, 'name_singular' => 'tax adjustment', 'query' => $query, @@ -12,9 +12,8 @@ }, ], 'links' => [ '', '', '', $ilink ], - ) -%> - + +&> <%init> die "access denied" diff --git a/httemplate/search/cust_tax_exempt.cgi b/httemplate/search/cust_tax_exempt.cgi index 3704b208a..005d77c33 100644 --- a/httemplate/search/cust_tax_exempt.cgi +++ b/httemplate/search/cust_tax_exempt.cgi @@ -1,4 +1,4 @@ -<% include( 'elements/search.html', +<& elements/search.html, 'title' => 'Legacy tax exemptions', 'name' => 'legacy tax exemptions', 'query' => $query, @@ -46,13 +46,11 @@ '', FS::UI::Web::cust_styles(), ], - ) -%> + +&> <%init> -my $join_cust = " - LEFT JOIN cust_main USING ( custnum ) -"; +my $join_cust = FS::UI::Web::join_cust_main('cust_tax_exempt'); die "access denied" unless $FS::CurrentUser::CurrentUser->access_right('View customer tax exemptions'); diff --git a/httemplate/search/cust_tax_exempt_pkg.cgi b/httemplate/search/cust_tax_exempt_pkg.cgi index 1b767f846..40b9ed78f 100644 --- a/httemplate/search/cust_tax_exempt_pkg.cgi +++ b/httemplate/search/cust_tax_exempt_pkg.cgi @@ -1,4 +1,4 @@ -<% include( 'elements/search.html', +<& elements/search.html, 'title' => 'Tax exemptions', 'name' => 'tax exemptions', 'query' => $query, @@ -77,14 +77,12 @@ '', FS::UI::Web::cust_styles(), ], - ) -%> +&> <%once> my $join_cust = " - JOIN cust_bill USING ( invnum ) - LEFT JOIN cust_main USING ( custnum ) -"; + JOIN cust_bill USING ( invnum )" . + FS::UI::Web::join_cust_main('cust_bill', 'cust_pkg'); my $join_pkg = " LEFT JOIN cust_pkg USING ( pkgnum ) @@ -93,8 +91,8 @@ my $join_pkg = " my $join = " JOIN cust_bill_pkg USING ( billpkgnum ) - $join_cust $join_pkg + $join_cust "; </%once> diff --git a/httemplate/search/customer_accounting_summary.html b/httemplate/search/customer_accounting_summary.html index 12c896276..b48ff21e3 100644 --- a/httemplate/search/customer_accounting_summary.html +++ b/httemplate/search/customer_accounting_summary.html @@ -142,8 +142,6 @@ $title .= $sel_part_referral->referral.' ' $title .= 'Customer Accounting Summary Report'; -my @cust_classnums = grep /^\d+$/, $cgi->param('cust_classnum'); - my @items = ('netsales', 'cashflow'); my @params = ( [], [] ); my $setuprecur = ''; @@ -173,7 +171,7 @@ foreach (qw(agentnum refnum status)) { } } $search_hash{'classnum'} = [ $cgi->param('cust_classnum') ] - if $cgi->param('cust_classnum'); + if grep { $_ eq 'cust_classnum' } $cgi->param; my $query = FS::cust_main::Search->search(\%search_hash); my @custs = qsearch($query); diff --git a/httemplate/search/elements/checkbox-foot.html b/httemplate/search/elements/checkbox-foot.html new file mode 100644 index 000000000..be1caab91 --- /dev/null +++ b/httemplate/search/elements/checkbox-foot.html @@ -0,0 +1,86 @@ +<%doc> +<& /elements/search.html, + # options... + html_foot => include('elements/checkbox-foot.html', + actions => [ + { label => 'Edit selected packages', + action => 'popup_package_edit()', + }, + { submit => 'Delete selected packages', + confirm => 'Really delete these packages?' + }, + ], + filter => '.name = "pkgpart"', # see below + ), +&> + +This creates a footer for a search page containing a column of checkboxes. +Typically this is used to select several items from the search result and +apply some change to all of them at once. The footer always provides +"select all" and "unselect all" buttons. + +"actions" is an arrayref of action buttons to show. Each element of the +array is a hashref of either: + +- "submit" and, optionally, "confirm". Creates a submit button. The value +of "submit" becomes the "value" property of the button (and thus its label). +If "confirm" is specified, the button will have an onclick handler that +displays the value of "confirm" in a popup message box and asks the user to +confirm the choice. + +- "onclick" and "label". Creates a non-submit button that executes the +Javascript code in "onclick". "label" is used as the text of the button. + +If you want only a single action, you can forget the arrayref-of-hashrefs +business and just put "submit" and "confirm" (or "onclick" and "label") +elements in the argument list. + +"filter" is a javascript expression to limit which checkboxes are included in +the "select/unselect all" actions. By default, any input with type="checkbox" +will be included. If this option is given, it will be evaluated with the +HTML node in a variable named "obj". The expression should return true or +false. + +</%doc> +<DIV ID="checkbox_footer" STYLE="display:block"> +<INPUT TYPE="button" VALUE="<% emt('select all') %>" onclick="setAll(true)"> +<INPUT TYPE="button" VALUE="<% emt('unselect all') %>" onclick="setAll(false)"> +<BR> +% foreach my $action (@$actions) { +% if ( $action->{onclick} ) { +<INPUT TYPE="button" <% $action->{name} %> onclick="<% $opt{onclick} %>"\ + VALUE="<% $action->{label} |h%>"> +% } elsif ( $action->{submit} ) { +<INPUT TYPE="submit" <% $action->{name} %> <% $action->{confirm} %>\ + VALUE="<% $action->{submit} |h%>"> +% } # else do nothing +% } #foreach +</DIV> +<SCRIPT> +var checkboxes = []; +var inputs = document.getElementsByTagName('input'); +for (var i = 0; i < inputs.length; i++) { + var obj = inputs[i]; + if ( obj.type == "checkbox" && <% $filter %> ) { + checkboxes.push(obj); + } +} +%# avoid the need for "$areboxes" late-evaluation hackery +if ( checkboxes.length == 0 ) { + document.getElementById('checkbox_footer').style.display = 'none'; +} +function setAll(setTo) { + for (var i = 0; i < checkboxes.length; i++) { + checkboxes[i].checked = setTo; + } +} +</SCRIPT> +<%init> +my %opt = @_; +my $actions = $opt{'actions'} || [ \%opt ]; +foreach (@$actions) { + $_->{confirm} &&= qq!onclick="return confirm('! . $_->{confirm} . qq!')"!; + $_->{name} &&= qq!NAME="! . $_->{name} . qq!"!; +} +my $filter = $opt{filter} || 'true'; +</%init> diff --git a/httemplate/search/elements/cust_main_dayranges.html b/httemplate/search/elements/cust_main_dayranges.html index eb7566494..cf2d495b1 100644 --- a/httemplate/search/elements/cust_main_dayranges.html +++ b/httemplate/search/elements/cust_main_dayranges.html @@ -162,6 +162,15 @@ if ( grep { $cgi->param('status') eq $_ } FS::cust_main->statuses() ) { push @where, FS::cust_main->$method(); } +# cust_classnum (false laziness w/prepaid_income.html, elements/cust_pay_or_refund.html, cust_bill_pay.html, cust_bill_pkg.html, cust_bill_pkg_referral.html, unearned_detail.html, cust_credit.html, cust_credit_refund.html, cust_main::Search::search_sql) +if ( grep { $_ eq 'cust_classnum' } $cgi->param ) { + my @classnums = grep /^\d*$/, $cgi->param('cust_classnum'); + push @where, 'COALESCE( cust_main.classnum, 0) IN ( '. + join(',', map { $_ || '0' } @classnums ). + ' )' + if @classnums; +} + #here is the agent virtualization push @where, $FS::CurrentUser::CurrentUser->agentnums_sql; @@ -172,10 +181,11 @@ my $count_sql = "select count(*) from cust_main $where"; my $sql_query = { 'table' => 'cust_main', + 'addl_from' => FS::UI::Web::join_cust_main('cust_main'), 'hashref' => {}, 'select' => join(',', #'cust_main.*', - 'custnum', + 'cust_main.custnum', $range_cols, $packages_cols, FS::UI::Web::cust_sql_fields(), diff --git a/httemplate/search/elements/cust_pay_batch_top.html b/httemplate/search/elements/cust_pay_batch_top.html index 1dcc37ac1..bf3047769 100644 --- a/httemplate/search/elements/cust_pay_batch_top.html +++ b/httemplate/search/elements/cust_pay_batch_top.html @@ -120,6 +120,7 @@ my $fixed = $conf->config("batch-fixed_format-$payby"); tie my %download_formats, 'Tie::IxHash', ( '' => 'Default batch mode', +'NACHA' => '94 byte NACHA', 'csv-td_canada_trust-merchant_pc_batch' => 'CSV file for TD Canada Trust Merchant PC Batch', 'csv-chase_canada-E-xactBatch' => diff --git a/httemplate/search/elements/cust_pay_or_refund.html b/httemplate/search/elements/cust_pay_or_refund.html index eeef0c0e1..7b2a17058 100755 --- a/httemplate/search/elements/cust_pay_or_refund.html +++ b/httemplate/search/elements/cust_pay_or_refund.html @@ -51,6 +51,7 @@ Examples: 'sort_fields' => \@sort_fields, 'align' => $align, 'links' => \@links, + 'link_onclicks' => \@link_onclicks, 'color' => \@color, 'style' => \@style, &> @@ -134,11 +135,12 @@ if ( $cgi->param('tax_names') ) { } } -my @header = (); -my @fields = (); -my @sort_fields = (); +my @header; +my @fields; +my @sort_fields; my $align = ''; -my @links = (); +my @links; +my @link_onclicks; if ( $opt{'pre_header'} ) { push @header, @{ $opt{'pre_header'} }; $align .= 'c' x scalar(@{ $opt{'pre_header'} }); @@ -147,6 +149,16 @@ if ( $opt{'pre_header'} ) { push @sort_fields, @{ $opt{'pre_fields'} }; } +my $sub_receipt = sub { + my $obj = shift; + my $objnum = $obj->primary_key . '=' . $obj->get($obj->primary_key); + + include('/elements/popup_link_onclick.html', + 'action' => $p.'view/cust_pay.html?link=popup;'.$objnum, + 'actionlabel' => emt('Payment Receipt'), + ); +}; + push @header, "\u$name_singular", 'Amount', ; @@ -155,6 +167,7 @@ push @links, '', ''; push @fields, 'payby_payinfo_pretty', sub { sprintf('$%.2f', shift->$amount_field() ) }, ; +push @link_onclicks, $sub_receipt, '', push @sort_fields, '', $amount_field; if ( $unapplied ) { @@ -239,9 +252,12 @@ if ( $cgi->param('magic') ) { $title = $part_referral->referral. " $title"; } - if ( $cgi->param('cust_classnum') ) { - my @classnums = grep /^\d+$/, $cgi->param('cust_classnum'); - push @search, 'cust_main.classnum IN('.join(',',@classnums).')' + # cust_classnum (false laziness w/ elements/cust_main_dayranges.html, prepaid_income.html, cust_bill_pay.html, cust_bill_pkg.html cust_bill_pkg_referral.html, unearned_detail.html, cust_credit.html, cust_credit_refund.html, cust_main::Search::search_sql) + if ( grep { $_ eq 'cust_classnum' } $cgi->param ) { + my @classnums = grep /^\d*$/, $cgi->param('cust_classnum'); + push @search, 'COALESCE( cust_main.classnum, 0) IN ( '. + join(',', map { $_ || '0' } @classnums ). + ' )' if @classnums; } @@ -250,78 +266,121 @@ if ( $cgi->param('magic') ) { } if ( $cgi->param('payby') ) { - $cgi->param('payby') =~ - /^(CARD|CHEK|BILL|PREP|CASH|WEST|MCRD)(-(VisaMC|Amex|Discover|Maestro))?$/ - or die "illegal payby ". $cgi->param('payby'); - push @search, "$table.payby = '$1'"; - if ( $3 ) { - - my $cardtype = $3; - - 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 processed as Visa/MC inside US - " ) "; - } 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' ) { - $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 ) = '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]' ". - " ) "; - } else { - die "unknown card type $cardtype"; - } - my $masksearch = $search; - $masksearch =~ s/$table\.payinfo/$table.paymask/gi; + my @all_payby_search = (); + foreach my $payby ( $cgi->param('payby') ) { + + $payby =~ + /^(CARD|CHEK|BILL|PREP|CASH|WEST|MCRD)(-(VisaMC|Amex|Discover|Maestro))?$/ + or die "illegal payby $payby"; + + my $payby_search = "$table.payby = '$1'"; + + if ( $3 ) { + + my $cardtype = $3; + + 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 $conf = new FS::Conf; + 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]' ". + " ) "; + } 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 ) ) )"; - push @search, - "( $search OR ( $table.paymask IS NOT NULL AND $masksearch ) )"; + } + + push @all_payby_search, $payby_search; } + + push @search, ' ( '. join(' OR ', @all_payby_search). ' ) ' if @all_payby_search; + } if ( $cgi->param('payinfo') ) { @@ -350,6 +409,7 @@ if ( $cgi->param('magic') ) { } my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi); + push @search, "_date >= $beginning ", "_date <= $ending"; @@ -411,7 +471,7 @@ if ( $cgi->param('magic') ) { #here is the agent virtualization push @search, $curuser->agentnums_sql; - my $addl_from = ' LEFT JOIN cust_main USING ( custnum ) '; + my $addl_from = FS::UI::Web::join_cust_main($table); my $group_by = ''; if ( $cgi->param('tax_names') ) { diff --git a/httemplate/search/elements/report_cust_pay_or_refund.html b/httemplate/search/elements/report_cust_pay_or_refund.html index 0e04ab0dd..0462f1cd9 100644 --- a/httemplate/search/elements/report_cust_pay_or_refund.html +++ b/httemplate/search/elements/report_cust_pay_or_refund.html @@ -30,68 +30,33 @@ Examples: <TR> <TD ALIGN="right"><% ucfirst(PL($name_singular)) %> of type: </TD> <TD> - <SELECT NAME="payby" onChange="payby_changed(this)"> - <OPTION VALUE=""><% mt('all') |h %></OPTION> - <OPTION VALUE="CARD"><% mt('credit card (all)') |h %></OPTION> - <OPTION VALUE="CARD-VisaMC"><% mt('credit card (Visa/MasterCard)') |h %></OPTION> - <OPTION VALUE="CARD-Amex"><% mt('credit card (American Express)') |h %></OPTION> - <OPTION VALUE="CARD-Discover"><% mt('credit card (Discover)') |h %></OPTION> - <OPTION VALUE="CARD-Maestro"><% mt('credit card (Maestro/Switch/Solo)') |h %></OPTION> - <OPTION VALUE="CHEK"><% mt('electronic check / ACH') |h %></OPTION> - <OPTION VALUE="BILL"><% mt('check') |h %></OPTION> - <OPTION VALUE="PREP"><% mt('prepaid card') |h %></OPTION> - <OPTION VALUE="CASH"><% mt('cash') |h %></OPTION> - <OPTION VALUE="WEST"><% mt('Western Union') |h %></OPTION> - <OPTION VALUE="MCRD"><% mt('manual credit card') |h %></OPTION> + <SELECT NAME="payby" SIZE=10 MULTIPLE> +%# <OPTION VALUE=""><% mt('all') |h %></OPTION> +%# <OPTION VALUE="CARD"><% mt('credit card (all)') |h %></OPTION> + <OPTION VALUE="CARD-VisaMC" SELECTED><% mt('credit card (Visa/MasterCard)') |h %></OPTION> + <OPTION VALUE="CARD-Amex" SELECTED><% mt('credit card (American Express)') |h %></OPTION> + <OPTION VALUE="CARD-Discover" SELECTED><% mt('credit card (Discover)') |h %></OPTION> + <OPTION VALUE="CARD-Maestro" SELECTED><% mt('credit card (Maestro/Switch/Solo)') |h %></OPTION> + <OPTION VALUE="CHEK" SELECTED><% mt('electronic check / ACH') |h %></OPTION> + <OPTION VALUE="BILL" SELECTED><% mt('check') |h %></OPTION> + <OPTION VALUE="PREP" SELECTED><% mt('prepaid card') |h %></OPTION> + <OPTION VALUE="CASH" SELECTED><% mt('cash') |h %></OPTION> + <OPTION VALUE="WEST" SELECTED><% mt('Western Union') |h %></OPTION> + <OPTION VALUE="MCRD" SELECTED><% mt('manual credit card') |h %></OPTION> </SELECT> </TD> </TR> - <SCRIPT TYPE="text/javascript"> - - function payby_changed(what) { - if ( what.value == 'BILL' ) { - show('payinfo'); - hide('ccpay'); - } else if ( what.value.match(/^CARD|CHEK/) ) { - hide('payinfo'); - show('ccpay'); - } else { - hide('payinfo'); - hide('ccpay'); - } - } - - function show(what) { - document.getElementById(what+'_caption').style.color = '#000000'; - document.getElementById(what).disabled = false; - document.getElementById(what).style.backgroundColor = '#ffffff'; - } - - function hide(what) { - document.getElementById(what+'_caption').style.color = '#bbbbbb'; - document.getElementById(what).disabled = true; - document.getElementById(what).style.backgroundColor = '#dddddd'; - } - - - - </SCRIPT> - <TR> - <TD ALIGN="right"><FONT ID="payinfo_caption" COLOR="#bbbbbb"><% mt('Check #:') |h %> </FONT></TD> + <TD ALIGN="right"><% mt('Check #:') |h %> </TD> <TD> - <INPUT TYPE="text" ID="payinfo" NAME="payinfo" DISABLED STYLE="background-color: #dddddd"> + <INPUT TYPE="text" ID="payinfo" NAME="payinfo"> </TD> </TR> <TR> - <TD ALIGN="right"> - <FONT ID="ccpay_caption" COLOR="#bbbbbb"> - <% mt('Transaction #') |h %> - </FONT> - </TD> + <TD ALIGN="right"><% mt('Transaction #:') |h %> </TD> <TD> - <INPUT TYPE="text" ID="ccpay" NAME="ccpay" DISABLED STYLE="background-color: #dddddd"> + <INPUT TYPE="text" ID="ccpay" NAME="ccpay"> </TD> </TR> @@ -108,7 +73,8 @@ Examples: <TD> <TABLE> <& /elements/tr-input-beginning_ending.html, - layout => 'horiz', + layout => 'horiz', + input_time => $conf->exists('report-cust_pay-select_time'), &> </TABLE> </TD> @@ -158,6 +124,8 @@ my $name_singular = $opt{'name_singular'}; die "access denied" unless $FS::CurrentUser::CurrentUser->access_right('Financial reports'); +my $conf = new FS::Conf; + my $void = $cgi->param('void') ? 1 : 0; my $unapplied = $cgi->param('unapplied') ? 1 : 0; diff --git a/httemplate/search/elements/report_svc_Common.html b/httemplate/search/elements/report_svc_Common.html new file mode 100644 index 000000000..434197078 --- /dev/null +++ b/httemplate/search/elements/report_svc_Common.html @@ -0,0 +1,122 @@ +<%doc> + +Example: + + <& elements/report_svc_Common.html, + + #required + 'table' => 'svc_something', + 'title' => 'Page title', + + #optional + 'action' => 'svc_tablename.html', #defaults to svc_tablename.html + + &> + +</%doc> +<& /elements/header.html, $title &> + +<FORM ACTION="<% $opt{'action'} || $opt{'table'}. '.html' %>" METHOD="GET"> +<INPUT TYPE="hidden" NAME="magic" VALUE="advanced"> +<INPUT TYPE="hidden" NAME="custnum" VALUE="<% $custnum %>"> + + <TABLE BGCOLOR="#cccccc" CELLSPACING=0> + + <TR> + <TH CLASS="background" COLSPAN=2 ALIGN="left"><FONT SIZE="+1"><% mt('Search options') |h %></FONT></TH> + </TR> + +% unless ( $custnum ) { + + <& /elements/tr-select-agent.html, + curr_value => scalar( $cgi->param('agentnum') ), + disable_empty => 0, + &> + + <& /elements/tr-select-cust_main-status.html, + label => 'Customer Status', + field => 'cust_status', + &> + + <& /elements/tr-select-payby.html, + label => emt('Payment method:'), + payby_type => 'cust', + multiple => 1, + all_selected => 1, + &> + + <& /elements/tr-input-money.html, + label => 'Balance over', + field => 'balance', + &> + + <& /elements/tr-input-text.html, + label => 'Balance age (days)', + field => 'balance_days', + size => 4, + &> + +% } + +% # just this customer's domains? +%# <& /elements/tr-select-domain.html, +%# 'element_name' => 'domsvc', +%# 'curr_value' => scalar( $cgi->param('domsvc') ), +%# 'disable_empty' => 0, +%# &> + + <& /elements/tr-selectmultiple-part_pkg.html &> + + <& /elements/tr-select-part_svc.html, + 'svcdb' => $svcdb, + 'label' => 'Services', + &> + + <TR> + <TH CLASS="background" COLSPAN=2> </TH> + </TR> + + <TR> + <TH CLASS="background" COLSPAN=2 ALIGN="left"><FONT SIZE="+1"><% mt('Display options') |h %></FONT></TH> + </TR> + +% #"package fields" ala advanced svc_acct search? +% #move to /elements/tr-select-cust_pkg-fields and use it from there if so... + + <& /elements/tr-select-cust-fields.html &> + + </TABLE> + +<BR> +<INPUT TYPE="submit" VALUE="<% mt('Get Report') |h %>"> + +</FORM> + +<& /elements/footer.html &> +<%init> + +my(%opt) = @_; + +my $svcdb = $opt{'table'}; + +my $name = "FS::$svcdb"->table_info->{'name_plural'} + || PL( "FS::$svcdb"->table_info->{'name'} ); + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right("Services: $name: Advanced search"); + +my $title = $opt{'title'}; + +#false laziness w/report_cust_pkg.html +my( $custnum, $cust_main) = ('', ''); +if ( $cgi->param('custnum') =~ /^(\d+)$/ ) { + $custnum = $1; + my $cust_main = qsearchs({ + 'table' => 'cust_main', + 'hashref' => { 'custnum' => $custnum }, + 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql, + }) or die "unknown custnum $custnum"; + $title = mt("$title: [_1]", $cust_main->name); +} + +</%init> diff --git a/httemplate/search/elements/search-html.html b/httemplate/search/elements/search-html.html index 7ccf356ea..e760bc546 100644 --- a/httemplate/search/elements/search-html.html +++ b/httemplate/search/elements/search-html.html @@ -253,7 +253,17 @@ % $bgcolor = $bgcolor1; % } - <TR> +% my $trid = ''; +% if ( $opt{'link_field' } ) { +% my $link_field = $opt{'link_field'}; +% if ( ref($link_field) eq 'CODE' ) { +% $trid = &{$link_field}($row); +% } else { +% $trid = $row->$link_field(); +% } +% } + <TR ID="<%$trid |h%>"> + % if ( $opt{'fields'} ) { % diff --git a/httemplate/search/elements/search.html b/httemplate/search/elements/search.html index 5a16a22fe..d44b45465 100644 --- a/httemplate/search/elements/search.html +++ b/httemplate/search/elements/search.html @@ -167,6 +167,11 @@ Example: # miscellany 'download_label' => 'Download this report', # defaults to 'Download full results' + 'link_field' => 'pkgpart' + # will create internal links for each row, + # with the value of this field as the NAME attribute + # If this is a coderef, will evaluate it, passing the + # row as an argument, and use the result as the NAME. &> </%doc> @@ -348,7 +353,7 @@ if ( $opt{'disableable'} ) { my $limit = ''; my($confmax, $maxrecords, $offset ); -unless ( $type =~ /^(csv|\w*.xls)$/) { +unless ( $type =~ /^(csv|xml|\w*.xls)$/) { # html mode unless (exists($opt{count_query}) && length($opt{count_query})) { ( $opt{count_query} = $opt{query} ) =~ diff --git a/httemplate/search/elements/svc_Common.html b/httemplate/search/elements/svc_Common.html new file mode 100644 index 000000000..56c75bba3 --- /dev/null +++ b/httemplate/search/elements/svc_Common.html @@ -0,0 +1,48 @@ +<& search.html, %opt &> +<%doc> +Currently does nothing but insert the classnames for fields chosen from an +inventory class. +</%doc> +<%init> +my %opt = @_; +my $query = $opt{query}; +my $svcdb = $query->{'table'}; + +# to avoid looking up the inventory class of every service in the database, +# keep as much of the base query as possible. +my $item_query = { %$query }; +$item_query->{'table'} = 'inventory_item'; +$item_query->{'addl_from'} = + " JOIN ( $svcdb ". $query->{'addl_from'} . + ") ON inventory_item.svcnum = $svcdb.svcnum ". + " JOIN inventory_class ON (inventory_item.classnum = inventory_class.classnum)"; +# avoid conflict with inventory_item.agentnum +$item_query->{'extra_sql'} =~ s/ agentnum/ cust_main.agentnum/g; +$item_query->{'select'} = 'inventory_item.svcnum, '. + 'inventory_item.svc_field, '. + 'inventory_class.classname'; +my @items = qsearch($item_query); +my %item_fields; +foreach my $i (@items) { + $item_fields{ $i->svc_field } ||= {}; + $item_fields{ $i->svc_field }{ $i->svcnum } = $i->classname; +} + +$opt{'sort_fields'} ||= []; +for ( my $i = 0; $i < @{ $opt{'fields'} }; $i++ ) { + my $f = $opt{'fields'}[$i]; + next if ref($f); # it's not a plain table column + $opt{'sort_fields'}[$i] ||= $f; + my $classnames = $item_fields{$f}; # hashref of svcnum -> classname + next if !$classnames; # there are no inventory items in this column + $opt{'fields'}[$i] = sub { + my $svc = $_[0]; + if ( exists($classnames->{$svc->svcnum}) ) { + return $svc->$f . '<BR><I>('. $classnames->{$svc->svcnum} . ')</I>'; + } else { + return $svc->$f; + } + }; #sub +} + +</%init> diff --git a/httemplate/search/employee_audit.html b/httemplate/search/employee_audit.html index 753c7bff3..2bc6ff46e 100644 --- a/httemplate/search/employee_audit.html +++ b/httemplate/search/employee_audit.html @@ -7,7 +7,7 @@ <%init> die "access denied" - unless $FS::CurrentUser::CurrentUser->access_right('Financial reports'); + unless $FS::CurrentUser::CurrentUser->access_right('Employees: Audit Report'); my %tables = ( cust_pay => 'Payments', diff --git a/httemplate/search/inventory_item.html b/httemplate/search/inventory_item.html index 086c8e92d..0e4251f74 100644 --- a/httemplate/search/inventory_item.html +++ b/httemplate/search/inventory_item.html @@ -1,4 +1,4 @@ -<% include( 'elements/search.html', +<& elements/search.html, 'title' => $title, 'menubar' => [ 'View inventory classes' => @@ -87,8 +87,8 @@ <INPUT TYPE="hidden" NAME="classnum" VALUE="$classnum"> <INPUT TYPE="hidden" NAME="avail" VALUE="! .$cgi->param('avail') . '">', #' 'html_foot' => $sub_foot, - ) -%> + +&> <%init> my $curuser = $FS::CurrentUser::CurrentUser; @@ -157,7 +157,7 @@ my $link_cust = sub { my $addl_from = ' LEFT JOIN cust_svc USING ( svcnum ) '. ' LEFT JOIN part_svc USING ( svcpart ) '. ' LEFT JOIN cust_pkg USING ( pkgnum ) '. - ' LEFT JOIN cust_main USING ( custnum ) '; + FS::UI::Web::join_cust_main('cust_pkg', 'cust_pkg'); my $areboxes = 0; my $sub_checkbox = sub { diff --git a/httemplate/search/mailinglistmember.html b/httemplate/search/mailinglistmember.html index ee395f416..a678d45ed 100644 --- a/httemplate/search/mailinglistmember.html +++ b/httemplate/search/mailinglistmember.html @@ -1,4 +1,4 @@ -<% include('elements/search.html', +<& elements/search.html, 'title' => $title, 'name_singular' => 'member', 'query' => $query, @@ -6,8 +6,7 @@ 'header' => [ 'Email address' ], 'fields' => [ $email_sub, ], #just this one for now 'html_init' => $html_init, - ) -%> +&> <%init> #XXX ACL: diff --git a/httemplate/search/part_pkg.html b/httemplate/search/part_pkg.html index 57da9d459..a90f13c95 100644 --- a/httemplate/search/part_pkg.html +++ b/httemplate/search/part_pkg.html @@ -1,4 +1,4 @@ -<% include( 'elements/search.html', +<& elements/search.html, 'title' => $title, 'name_singular' => $name, 'header' => \@header, @@ -14,8 +14,8 @@ 'links' => \@links, 'align' => $align, 'sort_fields' => [], - ) -%> + +&> <%init> #this is about reports about packages definitions (starting w/commission ones) @@ -23,7 +23,7 @@ my $curuser = $FS::CurrentUser::CurrentUser; die "access denied" - unless $curuser->access_right('Financial reports'); + unless $curuser->access_right('Employees: Commission Report'); #that's all this does so far my $conf = new FS::Conf; my $money_char = $conf->config('money_char') || '$'; diff --git a/httemplate/search/pay_batch.cgi b/httemplate/search/pay_batch.cgi index aeaa012f4..620996abd 100755 --- a/httemplate/search/pay_batch.cgi +++ b/httemplate/search/pay_batch.cgi @@ -1,4 +1,4 @@ -<% include( 'elements/search.html', +<& elements/search.html, 'title' => 'Payment Batches', 'name_singular' => 'batch', 'query' => { 'table' => 'pay_batch', @@ -101,8 +101,7 @@ ], 'html_init' => $html_init, 'html_foot' => include('.upload_incoming'), - ) -%> +&> <%def .upload_incoming> % if ( FS::payment_gateway->count("gateway_namespace = 'Business::BatchPayment' AND disabled IS NULL") > 0 ) { <& /elements/form-file_upload.html, @@ -149,16 +148,10 @@ my $count_query = 'SELECT COUNT(*) FROM pay_batch'; my($begin, $end) = ( '', '' ); my @where; -if ( $cgi->param('beginning') - && $cgi->param('beginning') =~ /^([ 0-9\-\/]{0,10})$/ ) { - $begin = parse_datetime($1); - push @where, "download >= $begin"; -} -if ( $cgi->param('ending') - && $cgi->param('ending') =~ /^([ 0-9\-\/]{0,10})$/ ) { - $end = parse_datetime($1) + 86399; - push @where, "download < $end"; -} + +my($beginning,$ending) = FS::UI::Web::parse_beginning_ending($cgi); +push @where, "( (download >= $beginning AND download <= $ending) ". + ' OR download IS NULL )'; my @status; if ( $cgi->param('open') ) { diff --git a/httemplate/search/phone_avail.html b/httemplate/search/phone_avail.html index 1335379ae..faf354420 100644 --- a/httemplate/search/phone_avail.html +++ b/httemplate/search/phone_avail.html @@ -1,4 +1,4 @@ -<% include( 'elements/search.html', +<& elements/search.html, 'title' => 'Phone Number (DID) Search Results', 'name_singular' => 'phone number', 'query' => { @@ -81,8 +81,8 @@ FS::UI::Web::cust_styles(), '', ], - ) -%> + +&> <%init> die "access denied" @@ -125,9 +125,11 @@ my $search = scalar(@search) my $addl_from = ' LEFT JOIN cust_svc USING ( svcnum ) '. #' LEFT JOIN part_svc USING ( svcpart ) '. ' LEFT JOIN cust_pkg USING ( pkgnum ) '. - ' LEFT JOIN cust_main USING ( custnum ) '; + FS::UI::Web::join_cust_main('cust_pkg', 'cust_pkg'); my $count_query = "SELECT COUNT(*) FROM phone_avail $search"; #$addl_from? +# All of these relationships are left joined in the many-to-one direction, +# so including $addl_from won't affect the count. Logic! my $hashref = {}; $hashref->{'ordernum'} = $1 if $cgi->param('ordernum') =~ /^(\d+)$/; diff --git a/httemplate/search/phone_inventory_provisioned.html b/httemplate/search/phone_inventory_provisioned.html index 03d21547d..b3efdbd77 100644 --- a/httemplate/search/phone_inventory_provisioned.html +++ b/httemplate/search/phone_inventory_provisioned.html @@ -1,4 +1,4 @@ -<% include( 'elements/search.html', +<& elements/search.html, 'title' => 'LATA Search Results', 'name_singular' => 'LATA', 'query' => { @@ -72,8 +72,8 @@ '', '', ], - ) -%> + +&> <%init> die "access denied" diff --git a/httemplate/search/prepaid_income.html b/httemplate/search/prepaid_income.html index 03d121d70..cb58a666d 100644 --- a/httemplate/search/prepaid_income.html +++ b/httemplate/search/prepaid_income.html @@ -129,10 +129,13 @@ if ( $cgi->param('status') =~ /^([a-z]+)$/ ) { push @where, FS::cust_main->cust_status_sql . " = '$status'"; } -if ( $cgi->param('cust_classnum') ) { - my @classnums = grep /^\d+$/, $cgi->param('cust_classnum'); +# cust_classnum (false laziness w/ elements/cust_main_dayranges.html, elements/cust_pay_or_refund.html, cust_bill_pay.html, cust_bill_pkg.html, cust_bill_pkg_referral.html, unearned_detail.html, cust_credit.html, cust_credit_refund.html, cust_main::Search::search_sql) +if ( grep { $_ eq 'cust_classnum' } $cgi->param ) { + my @classnums = grep /^\d*$/, $cgi->param('cust_classnum'); $link .= ";cust_classnum=$_" foreach @classnums; - push @where, 'cust_main.classnum IN('.join(',',@classnums).')' + push @where, 'COALESCE( cust_main.classnum, 0) IN ( '. + join(',', map { $_ || '0' } @classnums ). + ' )' if @classnums; } diff --git a/httemplate/search/prepay_credit.html b/httemplate/search/prepay_credit.html index 36403511b..7566e657e 100644 --- a/httemplate/search/prepay_credit.html +++ b/httemplate/search/prepay_credit.html @@ -1,4 +1,4 @@ -<% include( 'elements/search.html', +<& elements/search.html, 'title' => 'Unused Prepaid Cards'. ($agent ? ' for '. $agent->agent : ''), 'menubar' => [ @@ -47,8 +47,8 @@ $agent ? [ "${p}edit/agent.cgi?", 'agentnum' ] : ''; }, ], - ) -%> + +&> <%init> die "access denied" diff --git a/httemplate/search/prospect_main.html b/httemplate/search/prospect_main.html index 328d1202f..ab37b9089 100644 --- a/httemplate/search/prospect_main.html +++ b/httemplate/search/prospect_main.html @@ -1,4 +1,4 @@ -<% include('elements/search.html', +<& elements/search.html, 'title' => 'Prospect Search Results', 'name_singular' => 'prospect', 'query' => $query, @@ -23,8 +23,7 @@ '', #link to contact edit??? ], 'agent_virt' => 1, - ) -%> +&> <%init> die "access denied" diff --git a/httemplate/search/qual.cgi b/httemplate/search/qual.cgi index 7133ef056..7b718e498 100755 --- a/httemplate/search/qual.cgi +++ b/httemplate/search/qual.cgi @@ -1,4 +1,4 @@ -<% include( 'elements/search.html', +<& elements/search.html, 'title' => 'Qualifications', 'name_singular' => 'qualification', 'query' => { 'table' => 'qual', @@ -51,8 +51,8 @@ '', '', ], - ) -%> + +&> <%init> die "access denied" diff --git a/httemplate/search/queue.html b/httemplate/search/queue.html index 1c124706c..141c535da 100644 --- a/httemplate/search/queue.html +++ b/httemplate/search/queue.html @@ -1,4 +1,4 @@ -<% include( 'elements/search.html', +<& elements/search.html, 'title' => 'Job Queue', 'name' => 'jobs', 'html_form' => qq!<FORM NAME="jobForm" ACTION="$p/misc/queue.cgi" METHOD="POST">!, @@ -120,9 +120,8 @@ ''; } }, - ) - -%> + +&> <%init> die "access denied" diff --git a/httemplate/search/quotation.html b/httemplate/search/quotation.html index 259c85c22..fbc35bea1 100755 --- a/httemplate/search/quotation.html +++ b/httemplate/search/quotation.html @@ -72,7 +72,7 @@ die "access denied" unless $curuser->access_right('List quotations'); my $join_prospect_main = 'LEFT JOIN prospect_main USING ( prospectnum )'; -my $join_cust_main = 'LEFT JOIN cust_main ON ( quotation.custnum = cust_main.custnum )'; +my $join_cust_main = FS::UI::Web::join_cust_main('quotation'); #here is the agent virtualization my $agentnums_sql = ' ( '. $curuser->agentnums_sql( table=>'prospect_main' ). diff --git a/httemplate/search/reg_code.html b/httemplate/search/reg_code.html index f7d6d2061..42211e571 100644 --- a/httemplate/search/reg_code.html +++ b/httemplate/search/reg_code.html @@ -1,4 +1,4 @@ -<% include( 'elements/search.html', +<& elements/search.html, 'title' => 'Unused Registration Codes for '. $agent->agent, 'name' => 'registration codes', @@ -23,8 +23,8 @@ #$plink, '', ], - ) -%> + +&> <%init> die "access denied" diff --git a/httemplate/search/report_477.html b/httemplate/search/report_477.html index f593a94d8..b842b1f3f 100755 --- a/httemplate/search/report_477.html +++ b/httemplate/search/report_477.html @@ -231,7 +231,9 @@ 'table' => 'part_pkg_report_option', 'name_col' => 'name', 'hashref' => { 'disabled' => '' }, - 'element_name' => 'partv_report_option', + 'element_name' => 'part5_report_option', + 'curr_value' => + FS::Report::FCC_477::restore_fcc477map("part5_report_option"), ) %> </TD> diff --git a/httemplate/search/report_agent_commission.html b/httemplate/search/report_agent_commission.html new file mode 100644 index 000000000..79f94c52e --- /dev/null +++ b/httemplate/search/report_agent_commission.html @@ -0,0 +1,22 @@ +<% include('/elements/header.html', 'Agent commission report' ) %> + +<FORM ACTION="agent_commission.html"> + +<TABLE BGCOLOR="#cccccc" CELLSPACING=0> + +<% include( '/elements/tr-select-agent.html', disable_empty => 1 ) %> + +<% include( '/elements/tr-input-beginning_ending.html', ) %> + +</TABLE> + +<BR> +<INPUT TYPE="submit" VALUE="Get Report"> + +<% 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.html b/httemplate/search/report_cust_bill.html index 51618fb24..b339c80e0 100644 --- a/httemplate/search/report_cust_bill.html +++ b/httemplate/search/report_cust_bill.html @@ -4,7 +4,7 @@ <INPUT TYPE="hidden" NAME="magic" VALUE="_date"> <INPUT TYPE="hidden" NAME="custnum" VALUE="<% $custnum %>"> -<TABLE BGCOLOR="#cccccc" CELLSPACING=0 +<TABLE BGCOLOR="#cccccc" CELLSPACING=0> % unless ( $custnum ) { <& /elements/tr-select-agent.html, diff --git a/httemplate/search/report_cust_main.html b/httemplate/search/report_cust_main.html index acc49aec6..bac4346cf 100755 --- a/httemplate/search/report_cust_main.html +++ b/httemplate/search/report_cust_main.html @@ -96,11 +96,21 @@ </TR> % } - <& /elements/tr-select-cust_tag.html, - 'cgi' => $cgi, - 'is_report' => 1, - 'multiple' => 1, - &> + <TR> + <TD ALIGN="right">Tags</TD> + <TD> + <& /elements/select-cust_tag.html, + 'cgi' => $cgi, + 'is_report' => 1, + 'multiple' => 1, + &> + <DIV STYLE="display:inline-block; vertical-align:baseline"> + <INPUT TYPE="radio" NAME="all_tags" VALUE="0" CHECKED> Any of these + <BR> + <INPUT TYPE="radio" NAME="all_tags" VALUE="1"> All of these + </DIV> + </TD> + </TR> <& /elements/tr-select-payby.html, 'payby_type' => 'cust', @@ -174,10 +184,28 @@ </TR> <TR> + <TD ALIGN="right" VALIGN="center"><% mt('With postal mail invoices') |h %></TD> + <TD><INPUT TYPE="checkbox" NAME="POST" ID="POST" onClick="POST_changed();"></TD> + </TR> + + <TR> <TD ALIGN="right" VALIGN="center"><% mt('Without postal mail invoices') |h %></TD> - <TD><INPUT TYPE="checkbox" NAME="no_POST"></TD> + <TD><INPUT TYPE="checkbox" NAME="no_POST" ID="no_POST" onClick="no_POST_changed();"></TD> </TR> + <SCRIPT TYPE="text/javascript"> + function POST_changed() { + if ( document.getElementById('POST').checked == true ) { + document.getElementById('no_POST').checked = false; + } + } + function no_POST_changed() { + if ( document.getElementById('no_POST').checked == true ) { + document.getElementById('POST').checked = false; + } + } + </SCRIPT> + <TR> <TH CLASS="background" COLSPAN=2> </TH> </TR> diff --git a/httemplate/search/report_employee_audit.html b/httemplate/search/report_employee_audit.html index 757b8232f..461849b76 100644 --- a/httemplate/search/report_employee_audit.html +++ b/httemplate/search/report_employee_audit.html @@ -23,7 +23,7 @@ <%init> die "access denied" - unless $FS::CurrentUser::CurrentUser->access_right('Financial reports'); + unless $FS::CurrentUser::CurrentUser->access_right('Employees: Audit Report'); my %tables = ( cust_pay => 'Payments', diff --git a/httemplate/search/report_employee_commission.html b/httemplate/search/report_employee_commission.html index 51afad3b5..ebfcae82d 100644 --- a/httemplate/search/report_employee_commission.html +++ b/httemplate/search/report_employee_commission.html @@ -25,6 +25,6 @@ <%init> die "access denied" - unless $FS::CurrentUser::CurrentUser->access_right('Financial reports'); + unless $FS::CurrentUser::CurrentUser->access_right('Employees: Commission Report'); </%init> diff --git a/httemplate/search/report_receivables.html b/httemplate/search/report_receivables.html index 5cff0f4fc..854b24a00 100755 --- a/httemplate/search/report_receivables.html +++ b/httemplate/search/report_receivables.html @@ -15,7 +15,15 @@ <& /elements/tr-select-cust_main-status.html, 'label' => emt('Customer Status'), &> - + + <& /elements/tr-select-cust_class.html, + 'label' => emt('Customer class'), + 'field' => 'cust_classnum', + 'multiple' => 1, + 'pre_options' => [ '' => emt('(none)') ], + 'all_selected' => 1, + &> + <TR> <TD ALIGN="right"><% mt('Customers') |h %></TD> <TD> diff --git a/httemplate/search/report_sqlradius_usage.html b/httemplate/search/report_sqlradius_usage.html index 01215e834..7e54465d3 100644 --- a/httemplate/search/report_sqlradius_usage.html +++ b/httemplate/search/report_sqlradius_usage.html @@ -8,13 +8,18 @@ 'empty_label' => 'all', &> -% my @exporttypes = map { "'$_'" } qw(sqlradius broadband_sqlradius); +%#more future-proof to actually ask all exports if they ->can('usage_sessions') +% my @exporttypes = qw( sqlradius sqlradius_withdomain broadband_sqlradius +% phone_sqlradius radiator +% ); <& /elements/tr-select-table.html, 'label' => 'Export', 'table' => 'part_export', 'name_col' => 'label', 'hashref' => {}, - 'extra_sql' => ' WHERE exporttype IN('.join(',', @exporttypes).')', + 'extra_sql' => ' WHERE exporttype IN ( '. + join(',', map "'$_'", @exporttypes). + ')', 'disable_empty' => 1, 'order_by' => 'ORDER BY exportnum', &> diff --git a/httemplate/search/report_svc_acct.html b/httemplate/search/report_svc_acct.html index 74bf5538e..e47f72726 100755 --- a/httemplate/search/report_svc_acct.html +++ b/httemplate/search/report_svc_acct.html @@ -116,7 +116,7 @@ die "access denied" unless $FS::CurrentUser::CurrentUser->access_right('Services: Accounts: Advanced search'); #? -my $title = emt('Account Report'); +my $title = mt('Account Report'); #false laziness w/report_cust_pkg.html my $custnum = ''; @@ -127,7 +127,7 @@ if ( $cgi->param('custnum') =~ /^(\d+)$/ ) { 'hashref' => { 'custnum' => $custnum }, 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql, }) or die "unknown custnum $custnum"; - $title = emt("Account Report: [_1]", $cust_main->name); + $title = mt("Account Report: [_1]", $cust_main->name); } </%init> diff --git a/httemplate/search/report_svc_phone.html b/httemplate/search/report_svc_phone.html index 9f1042608..63ca03e16 100644 --- a/httemplate/search/report_svc_phone.html +++ b/httemplate/search/report_svc_phone.html @@ -1,32 +1,6 @@ -<% include('/elements/header.html', 'Phone number total usage' ) %> +<& elements/report_svc_Common.html, + 'table' => 'svc_phone', + 'title' => 'Phone number report', -<FORM ACTION="svc_phone.cgi" METHOD="GET"> - -<INPUT TYPE="hidden" NAME="magic" VALUE="all"> -<INPUT TYPE="hidden" NAME="usage_total" VALUE="1"> - -<TABLE BGCOLOR="#cccccc" CELLSPACING=0> - -%# <TR> -%# <TH CLASS="background" COLSPAN=2 ALIGN="left"> -%# <FONT SIZE="+1">Search options</FONT> -%# </TH> -%# </TR> - - <% include ( '/elements/tr-input-beginning_ending.html', prefix=>'usage' ) %> - -</TABLE> - -<BR> -<INPUT TYPE="submit" VALUE="Search phone numbers"> - -</FORM> - -<% include('/elements/footer.html') %> -<%init> - -#? 'List services' ? something new? -die "access denied" - unless $FS::CurrentUser::CurrentUser->access_right('List rating data'); - -</%init> + 'action' => 'svc_phone.cgi', +&> diff --git a/httemplate/search/report_svc_phone_usage.html b/httemplate/search/report_svc_phone_usage.html new file mode 100644 index 000000000..9f1042608 --- /dev/null +++ b/httemplate/search/report_svc_phone_usage.html @@ -0,0 +1,32 @@ +<% include('/elements/header.html', 'Phone number total usage' ) %> + +<FORM ACTION="svc_phone.cgi" METHOD="GET"> + +<INPUT TYPE="hidden" NAME="magic" VALUE="all"> +<INPUT TYPE="hidden" NAME="usage_total" VALUE="1"> + +<TABLE BGCOLOR="#cccccc" CELLSPACING=0> + +%# <TR> +%# <TH CLASS="background" COLSPAN=2 ALIGN="left"> +%# <FONT SIZE="+1">Search options</FONT> +%# </TH> +%# </TR> + + <% include ( '/elements/tr-input-beginning_ending.html', prefix=>'usage' ) %> + +</TABLE> + +<BR> +<INPUT TYPE="submit" VALUE="Search phone numbers"> + +</FORM> + +<% include('/elements/footer.html') %> +<%init> + +#? 'List services' ? something new? +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('List rating data'); + +</%init> diff --git a/httemplate/search/report_tax.cgi b/httemplate/search/report_tax.cgi index 42a52d154..479b99044 100755 --- a/httemplate/search/report_tax.cgi +++ b/httemplate/search/report_tax.cgi @@ -250,8 +250,10 @@ my $conf = new FS::Conf; my $out = 'Out of taxable region(s)'; my %label_opt = ( out => 1 ); #enable 'Out of Taxable Region' label -$label_opt{no_city} = 1 unless $cgi->param('show_cities'); -$label_opt{no_taxclass} = 1 unless $cgi->param('show_taxclasses'); +$label_opt{with_city} = 1 if $cgi->param('show_cities'); +$label_opt{with_district} = 1 if $cgi->param('show_districts'); + +$label_opt{with_taxclass} = 1 if $cgi->param('show_taxclasses'); my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi); @@ -487,7 +489,8 @@ my $tot_tax = 0; my $tot_credit = 0; my @loc_params = qw(country state county); -push @loc_params, qw(city district) if $cgi->param('show_cities'); +push @loc_params, 'city' if $cgi->param('show_cities'); +push @loc_params, 'district' if $cgi->param('show_districts'); foreach my $r ( qsearch({ 'table' => 'cust_main_county', })) { my $taxnum = $r->taxnum; @@ -522,7 +525,7 @@ foreach my $r ( qsearch({ 'table' => 'cust_main_county', })) { } if ( $cgi->param('show_taxclasses') ) { - my $base_label = $r->label(%label_opt, 'no_taxclass' => 1); + my $base_label = $r->label(%label_opt, 'with_taxclass' => 0); $base_regions{$base_label} ||= { label => $base_label, diff --git a/httemplate/search/report_tax.html b/httemplate/search/report_tax.html index 2ab0e0b2e..8a207aafb 100755 --- a/httemplate/search/report_tax.html +++ b/httemplate/search/report_tax.html @@ -34,9 +34,21 @@ % if ( $city ) { <TR> - <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="show_cities" VALUE="1"></TD> + <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="show_cities" VALUE="1" onclick="toggle_show_cities(this)"></TD> <TD>Show cities</TD> </TR> + <TR> + <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="show_districts" VALUE="1" DISABLED></TD> + <TD>Show districts</TD> + </TR> + <SCRIPT TYPE="text/javascript"> + function toggle_show_cities() { + what = document.getElementsByName('show_cities')[0]; + what.form.show_districts.disabled = !what.checked; + what.form.show_districts.checked = what.checked; + } + toggle_show_cities(); + </SCRIPT> % } % if ( $conf->exists('enable_taxclasses') ) { diff --git a/httemplate/search/rt_ticket.html b/httemplate/search/rt_ticket.html index 1ed5a3883..f5ac023b5 100644 --- a/httemplate/search/rt_ticket.html +++ b/httemplate/search/rt_ticket.html @@ -1,21 +1,21 @@ -<% include('elements/search.html', +<& elements/search.html, 'title' => 'Time worked summary', 'name_singular' => 'ticket', 'query' => $query, 'count_query' => $count_query, 'count_addl' => [ $format_seconds_sub, - $applied_time ? $format_seconds_sub : () ], + $applied ? $format_seconds_sub : () ], 'header' => [ 'Ticket #', 'Ticket', 'Time', - $applied_time ? 'Applied' : (), + $applied ? 'Applied' : (), ], 'fields' => [ 'ticketid', sub { encode_entities(shift->get('subject')) }, sub { my $seconds = shift->get('ticket_time'); &{ $format_seconds_sub }( $seconds ); }, - ($applied_time ? + ($applied ? sub { my $seconds = shift->get('applied_time'); &{ $format_seconds_sub }( $seconds ); } : () ), @@ -23,7 +23,7 @@ 'sort_fields' => [ 'ticketid', 'subject', 'transaction_time', - $applied_time ? 'applied_time' : (), + $applied ? 'applied_time' : (), ], 'links' => [ $link, @@ -31,8 +31,7 @@ '', '', ], - ) -%> +&> <%once> my $format_seconds_sub = sub { @@ -60,7 +59,6 @@ my @select = ( ); my @select_total = ( 'COUNT(*)' ); -my ($transaction_time, $applied_time); my $join = 'JOIN Users ON Transactions.Creator = Users.Id '; #. my $twhere = " @@ -68,6 +66,8 @@ my $twhere = " AND Transactions.ObjectId = Tickets.Id "; +my $transaction_time; +my $applied = ''; my $cfname = ''; if ( $cgi->param('cfname') =~ /^\w(\w|\s)*$/ ) { @@ -104,15 +104,14 @@ if ( $cgi->param('cfname') =~ /^\w(\w|\s)*$/ ) { $twhere .= " AND CustomFields.Name = '$cfname' AND (ocfv_new.Id IS NOT NULL OR ocfv_old.Id IS NOT NULL OR ocfv_main.Id IS NOT NULL)"; -} -else { +} else { + $transaction_time = " CASE transactions.type when 'Set' THEN (to_number(newvalue,'999999')-to_number(oldvalue, '999999')) * 60 ELSE timetaken*60 END"; - my $applied = ''; if ( $cgi->param('svcnum') =~ /^\s*(\d+)\s*$/ ) { $twhere .= " AND EXISTS( SELECT 1 FROM acct_rt_transaction WHERE acct_rt_transaction.transaction_id = Transactions.id AND svcnum = $1 )"; $applied = "AND svcnum = $1"; @@ -122,13 +121,11 @@ else { AND ( ( Transactions.Type = 'Set' AND Transactions.Field = 'TimeWorked' AND Transactions.NewValue != Transactions.OldValue ) - OR ( ( Transactions.Type='Create' OR Transactions.Type='Comment' OR Transactions.Type='Correspond' OR Transactions.Type='Touch' ) + OR ( Transactions.Type IN ( 'Create', 'Comment', 'Correspond', 'Touch' ) AND Transactions.TimeTaken > 0 ) )"; - $applied_time = "( SELECT SUM(support) FROM acct_rt_transaction LEFT JOIN Transactions ON ( transaction_id = Id ) $twhere $applied )"; - } @@ -155,9 +152,13 @@ my $ticket_time = "( SELECT SUM($transaction_time) $transactions )"; push @select, "$ticket_time AS ticket_time"; push @select_total, "SUM($ticket_time)"; -if ( $applied_time) { +if ( $applied ) { + + my $applied_time = "( SELECT SUM(support) FROM acct_rt_transaction LEFT JOIN Transactions ON ( transaction_id = Id ) $twhere $applied )"; + push @select, "$applied_time AS applied_time"; push @select_total, "SUM($applied_time)"; + } my $query = { diff --git a/httemplate/search/rt_transaction.html b/httemplate/search/rt_transaction.html index 1ae607be1..eb250fb27 100644 --- a/httemplate/search/rt_transaction.html +++ b/httemplate/search/rt_transaction.html @@ -1,4 +1,4 @@ -<% include('elements/search.html', +<& elements/search.html, 'title' => 'Time worked', 'name_singular' => 'transaction', 'query' => $query, @@ -29,8 +29,7 @@ '', '', ], - ) -%> +&> <%once> my $format_seconds_sub = sub { diff --git a/httemplate/search/sql.html b/httemplate/search/sql.html index bf5446975..71aa00671 100644 --- a/httemplate/search/sql.html +++ b/httemplate/search/sql.html @@ -1,9 +1,9 @@ -<% include( 'elements/search.html', +<& elements/search.html, 'title' => 'Query Results', 'name' => 'rows', 'query' => "SELECT $sql", - ) -%> + +&> <%init> die "access denied" diff --git a/httemplate/search/sqlradius.cgi b/httemplate/search/sqlradius.cgi index 5363944e4..22984731a 100644 --- a/httemplate/search/sqlradius.cgi +++ b/httemplate/search/sqlradius.cgi @@ -51,7 +51,7 @@ % @{ $part_export->usage_sessions( { % 'stoptime_start' => $beginning, % 'stoptime_end' => $ending, -% 'open_sessions' => $open_sessions, +% 'session_status' => $status, % 'starttime_start' => $starttime_beginning, % 'starttime_end' => $starttime_ending, % 'svc_acct' => $cgi_svc_acct, @@ -117,9 +117,9 @@ if ( $cgi->param('end') && $cgi->param('end') =~ /^(\d+)$/ ) { $ending = $1; } -my $open_sessions = ''; -if ( $cgi->param('open_sessions') =~ /^(\d*)$/ ) { - $open_sessions = $1; +my $status = ''; +if ( $cgi->param('session_status') =~ /^(closed|open)$/ ) { + $status = $1; } my( $starttime_beginning, $starttime_ending ) = ( '', '' ); @@ -242,8 +242,15 @@ my $time_format = sub { $pretty; }; +my $time_format_or_open = sub { + my $time = shift; + return '<CENTER>OPEN</CENTER>' if $time == 0; + &{$time_format}($time); +}; + my $duration_format = sub { my $seconds = shift; + return '' if $seconds eq ''; # open session my $hour = int($seconds/3600); my $min = int( ($seconds%3600) / 60 ); my $sec = $seconds%60; @@ -339,7 +346,7 @@ tie %fields, 'Tie::IxHash', 'acctstoptime' => { name => 'End time', attrib => 'Acct-Stop-Time', - fmt => $time_format, + fmt => $time_format_or_open, align => 'left', }, 'acctsessiontime' => { diff --git a/httemplate/search/sqlradius.html b/httemplate/search/sqlradius.html index 7b9fce310..547a9bb44 100644 --- a/httemplate/search/sqlradius.html +++ b/httemplate/search/sqlradius.html @@ -52,8 +52,9 @@ <TR> <TD>Show:</TD> <TD> - <INPUT TYPE="radio" NAME="open_sessions" VALUE="0" onClick="open_changed(this);" CHECKED>Completed sessions<BR> - <INPUT TYPE="radio" NAME="open_sessions" VALUE="1" onClick="open_changed(this);">Open sessions + <INPUT TYPE="radio" NAME="session_status" VALUE="" onClick="enable_stop(true);" CHECKED>All sessions<BR> + <INPUT TYPE="radio" NAME="session_status" VALUE="closed" onClick="enable_stop(true);">Completed sessions<BR> + <INPUT TYPE="radio" NAME="session_status" VALUE="open" onClick="enable_stop(false);">Open sessions </TD> </TR> @@ -69,41 +70,31 @@ <SCRIPT TYPE="text/javascript"> - function open_changed(what) { - - var value=get_open_value(what); - if ( value == '1' ) { - what.form.stoptime_beginning_text.disabled = true; - what.form.stoptime_ending_text.disabled = true; - what.form.stoptime_beginning_text.style.backgroundColor = '#dddddd'; - what.form.stoptime_ending_text.style.backgroundColor = '#dddddd'; - what.form.stoptime_beginning_button.style.display = 'none'; - what.form.stoptime_ending_button.style.display = 'none'; - what.form.stoptime_beginning_disabled.style.display = ''; - what.form.stoptime_ending_disabled.style.display = ''; - } else if ( value == '0' ) { - what.form.stoptime_beginning_text.disabled = false; - what.form.stoptime_ending_text.disabled = false; - what.form.stoptime_beginning_text.style.backgroundColor = '#ffffff'; - what.form.stoptime_ending_text.style.backgroundColor = '#ffffff'; - what.form.stoptime_beginning_button.style.display = ''; - what.form.stoptime_ending_button.style.display = ''; - what.form.stoptime_beginning_disabled.style.display = 'none'; - what.form.stoptime_ending_disabled.style.display = 'none'; + function enable_stop(value) { + + var f = document.OneTrueForm; + if ( value ) { + f.stoptime_beginning_text.disabled = false; + f.stoptime_ending_text.disabled = false; + f.stoptime_beginning_text.style.backgroundColor = '#ffffff'; + f.stoptime_ending_text.style.backgroundColor = '#ffffff'; + f.stoptime_beginning_button.style.display = ''; + f.stoptime_ending_button.style.display = ''; + f.stoptime_beginning_disabled.style.display = 'none'; + f.stoptime_ending_disabled.style.display = 'none'; + } else { + f.stoptime_beginning_text.disabled = true; + f.stoptime_ending_text.disabled = true; + f.stoptime_beginning_text.style.backgroundColor = '#dddddd'; + f.stoptime_ending_text.style.backgroundColor = '#dddddd'; + f.stoptime_beginning_button.style.display = 'none'; + f.stoptime_ending_button.style.display = 'none'; + f.stoptime_beginning_disabled.style.display = ''; + f.stoptime_ending_disabled.style.display = ''; } } - function get_open_value(what) { - var rad_val = ''; - for (var i=0; i < what.form.open_sessions.length; i++) { - if (what.form.open_sessions[i].checked) { - var rad_val = what.form.open_sessions[i].value; - } - } - return rad_val; - } - </SCRIPT> <TR> diff --git a/httemplate/search/svc_acct.cgi b/httemplate/search/svc_acct.cgi index 92e1c500c..b9e5a7cc9 100755 --- a/httemplate/search/svc_acct.cgi +++ b/httemplate/search/svc_acct.cgi @@ -1,4 +1,4 @@ -<& elements/search.html, +<& elements/svc_Common.html, 'title' => emt('Account Search Results'), 'name' => emt('accounts'), 'query' => $sql_query, diff --git a/httemplate/search/svc_broadband.cgi b/httemplate/search/svc_broadband.cgi index ee62e9084..8366d214b 100755 --- a/httemplate/search/svc_broadband.cgi +++ b/httemplate/search/svc_broadband.cgi @@ -1,4 +1,4 @@ -<% include( 'elements/search.html', +<& elements/svc_Common.html, 'title' => 'Broadband Search Results', 'name' => 'broadband services', 'html_init' => $html_init, @@ -49,8 +49,8 @@ '', FS::UI::Web::cust_styles(), ], - ) -%> + +&> <%init> die "access denied" unless @@ -72,7 +72,7 @@ else { } if ( $cgi->param('sortby') =~ /^(\w+)$/ ) { - $search_hash{'order_by'} = $1; + $search_hash{'order_by'} = "ORDER BY $1"; } my $sql_query = FS::svc_broadband->search(\%search_hash); diff --git a/httemplate/search/svc_dish.cgi b/httemplate/search/svc_dish.cgi index 94da03537..1f8cbc395 100755 --- a/httemplate/search/svc_dish.cgi +++ b/httemplate/search/svc_dish.cgi @@ -1,4 +1,4 @@ -<% include( 'elements/search.html', +<& elements/svc_Common.html, 'title' => 'Dish Network Search Results', 'name' => 'services', 'query' => $sql_query, @@ -34,8 +34,8 @@ '', FS::UI::Web::cust_styles(), ], - ) -%> + +&> <%init> die "access denied" @@ -61,7 +61,7 @@ if ( $cgi->param('magic') =~ /^(all|unlinked)$/ ) { my $addl_from = ' LEFT JOIN cust_svc USING ( svcnum ) '. ' LEFT JOIN part_svc USING ( svcpart ) '. ' LEFT JOIN cust_pkg USING ( pkgnum ) '. - ' LEFT JOIN cust_main USING ( custnum ) '; + FS::UI::Web::join_cust_main('cust_pkg', 'cust_pkg'); #here is the agent virtualization push @extra_sql, $FS::CurrentUser::CurrentUser->agentnums_sql( diff --git a/httemplate/search/svc_domain.cgi b/httemplate/search/svc_domain.cgi index 9827b8d38..56cfa30c8 100755 --- a/httemplate/search/svc_domain.cgi +++ b/httemplate/search/svc_domain.cgi @@ -1,4 +1,4 @@ -<% include( 'elements/search.html', +<& elements/search.html, 'title' => "Domain Search Results", 'name' => 'domains', 'query' => $sql_query, @@ -34,8 +34,8 @@ '', FS::UI::Web::cust_styles(), ], - ) -%> + +&> <%init> die "access denied" @@ -66,7 +66,7 @@ if ( $cgi->param('magic') =~ /^(all|unlinked)$/ ) { my $addl_from = ' LEFT JOIN cust_svc USING ( svcnum ) '. ' LEFT JOIN part_svc USING ( svcpart ) '. ' LEFT JOIN cust_pkg USING ( pkgnum ) '. - ' LEFT JOIN cust_main USING ( custnum ) '; + FS::UI::Web::join_cust_main('cust_pkg', 'cust_pkg'); #here is the agent virtualization push @extra_sql, $FS::CurrentUser::CurrentUser->agentnums_sql( diff --git a/httemplate/search/svc_external.cgi b/httemplate/search/svc_external.cgi index cb51d44fd..b282939a7 100755 --- a/httemplate/search/svc_external.cgi +++ b/httemplate/search/svc_external.cgi @@ -1,4 +1,4 @@ -<% include( 'elements/search.html', +<& elements/svc_Common.html, 'title' => 'External service search results', 'name' => 'external services', 'query' => $sql_query, @@ -40,9 +40,8 @@ '', FS::UI::Web::cust_styles(), ], - ) -%> - + +&> <%init> die "access denied" @@ -90,7 +89,7 @@ if ( $cgi->param('magic') =~ /^(all|unlinked)$/ ) { my $addl_from = ' LEFT JOIN cust_svc USING ( svcnum ) '. ' LEFT JOIN part_svc USING ( svcpart ) '. ' LEFT JOIN cust_pkg USING ( pkgnum ) '. - ' LEFT JOIN cust_main USING ( custnum ) '; + FS::UI::Web::join_cust_main('cust_pkg', 'cust_pkg'); #here is the agent virtualization push @extra_sql, $FS::CurrentUser::CurrentUser->agentnums_sql( diff --git a/httemplate/search/svc_forward.cgi b/httemplate/search/svc_forward.cgi index f17f131ab..6a23bb3bb 100755 --- a/httemplate/search/svc_forward.cgi +++ b/httemplate/search/svc_forward.cgi @@ -1,4 +1,4 @@ -<% include( 'elements/search.html', +<& elements/search.html, 'title' => "Mail forward Search Results", 'name' => 'mail forwards', 'query' => $sql_query, @@ -39,8 +39,8 @@ '', FS::UI::Web::cust_styles(), ], - ) -%> + +&> <%init> die "access denied" @@ -67,7 +67,7 @@ if ( $cgi->param('magic') =~ /^(all|unlinked)$/ ) { my $addl_from = ' LEFT JOIN cust_svc USING ( svcnum ) '. ' LEFT JOIN part_svc USING ( svcpart ) '. ' LEFT JOIN cust_pkg USING ( pkgnum ) '. - ' LEFT JOIN cust_main USING ( custnum ) '; + FS::UI::Web::join_cust_main('cust_pkg', 'cust_pkg'); #here is the agent virtualization push @extra_sql, $FS::CurrentUser::CurrentUser->agentnums_sql( diff --git a/httemplate/search/svc_hardware.cgi b/httemplate/search/svc_hardware.cgi index ec09be82b..93fc2c391 100644 --- a/httemplate/search/svc_hardware.cgi +++ b/httemplate/search/svc_hardware.cgi @@ -1,4 +1,4 @@ -<% include('elements/search.html', +<& elements/svc_Common.html, 'title' => 'Hardware service search results', 'name' => 'installations', 'query' => $sql_query, @@ -34,8 +34,7 @@ FS::UI::Web::cust_colors() ], 'style' => [ $svc_cancel_style, ('') x 7, FS::UI::Web::cust_styles() ], - ) -%> +&> <%init> die "access denied" @@ -44,8 +43,8 @@ die "access denied" my $addl_from = ' LEFT JOIN cust_svc USING ( svcnum ) LEFT JOIN part_svc USING ( svcpart ) - LEFT JOIN cust_pkg USING ( pkgnum ) - LEFT JOIN cust_main USING ( custnum ) + LEFT JOIN cust_pkg USING ( pkgnum )'. + FS::UI::Web::join_cust_main('cust_pkg', 'cust_pkg').' LEFT JOIN hardware_type USING ( typenum )'; my @extra_sql; diff --git a/httemplate/search/svc_phone.cgi b/httemplate/search/svc_phone.cgi index 29434083f..f3a056475 100644 --- a/httemplate/search/svc_phone.cgi +++ b/httemplate/search/svc_phone.cgi @@ -1,4 +1,4 @@ -<% include( 'elements/search.html', +<& elements/svc_Common.html, 'title' => "Phone number search results", 'name' => 'phone numbers', 'query' => $sql_query, @@ -9,7 +9,7 @@ 'Country code', 'Phone number', @header, - FS::UI::Web::cust_header(), + FS::UI::Web::cust_header($cgi->param('cust_fields')), ], 'fields' => [ 'svcnum', 'svc', @@ -24,7 +24,7 @@ $link, ( map '', @header ), ( map { $_ ne 'Cust. Status' ? $link_cust : '' } - FS::UI::Web::cust_header() + FS::UI::Web::cust_header($cgi->param('cust_fields')) ), ], 'align' => 'rlrr'. @@ -46,8 +46,8 @@ ( map '', @header ), FS::UI::Web::cust_styles(), ], - ) -%> + +&> <%init> die "access denied" @@ -56,8 +56,6 @@ die "access denied" my $conf = new FS::Conf; my @select = (); -my %svc_phone = (); -my @extra_sql = (); my $orderby = 'ORDER BY svcnum'; my @header = (); @@ -65,9 +63,12 @@ my @fields = (); my $link = [ "${p}view/svc_phone.cgi?", 'svcnum' ]; my $redirect = $link; +my %search_hash = (); +my @extra_sql = (); + if ( $cgi->param('magic') =~ /^(all|unlinked)$/ ) { - push @extra_sql, 'pkgnum IS NULL' + $search_hash{'unlinked'} = 1 if $cgi->param('magic') eq 'unlinked'; if ( $cgi->param('sortby') =~ /^(\w+)$/ ) { @@ -119,52 +120,31 @@ if ( $cgi->param('magic') =~ /^(all|unlinked)$/ ) { } +} elsif ( $cgi->param('magic') =~ /^advanced$/ ) { + + for (qw( agentnum custnum cust_status balance balance_days cust_fields )) { + $search_hash{$_} = $cgi->param($_) if length($cgi->param($_)); + } + + for (qw( payby pkgpart svcpart )) { + $search_hash{$_} = [ $cgi->param($_) ] if $cgi->param($_); + } + } elsif ( $cgi->param('svcpart') =~ /^(\d+)$/ ) { - push @extra_sql, "svcpart = $1"; + $search_hash{'svcpart'} = [ $1 ]; } else { $cgi->param('phonenum') =~ /^([\d\- ]+)$/; - ( $svc_phone{'phonenum'} = $1 ) =~ s/\D//g; + my $phonenum = $1; + $phonenum =~ s/\D//g; + push @extra_sql, "phonenum = '$phonenum'"; } -my $addl_from = ' LEFT JOIN cust_svc USING ( svcnum ) '. - ' LEFT JOIN part_svc USING ( svcpart ) '. - ' LEFT JOIN cust_pkg USING ( pkgnum ) '. - ' LEFT JOIN cust_main USING ( custnum ) '; - -#here is the agent virtualization -push @extra_sql, $FS::CurrentUser::CurrentUser->agentnums_sql( - 'null_right' => 'View/link unlinked services' - ); - -my $extra_sql = ''; -if ( @extra_sql ) { - $extra_sql = ( keys(%svc_phone) ? ' AND ' : ' WHERE ' ). - join(' AND ', @extra_sql ); -} +$search_hash{'addl_select'} = \@select; +$search_hash{'order_by'} = $orderby; +$search_hash{'where'} = \@extra_sql; -my $count_query = "SELECT COUNT(*) FROM svc_phone $addl_from "; -if ( keys %svc_phone ) { - $count_query .= ' WHERE '. - join(' AND ', map "$_ = ". dbh->quote($svc_phone{$_}), - keys %svc_phone - ); -} -$count_query .= $extra_sql; - -my $sql_query = { - 'table' => 'svc_phone', - 'hashref' => \%svc_phone, - 'select' => join(', ', - 'svc_phone.*', - 'part_svc.svc', - @select, - 'cust_main.custnum', - FS::UI::Web::cust_sql_fields(), - ), - 'extra_sql' => $extra_sql, - 'order_by' => $orderby, - 'addl_from' => $addl_from, -}; +my $sql_query = FS::svc_phone->search(\%search_hash); +my $count_query = delete($sql_query->{'count_query'}); #smaller false laziness w/svc_*.cgi here my $link_cust = sub { diff --git a/httemplate/search/svc_www.cgi b/httemplate/search/svc_www.cgi index adc31c88a..7410262e8 100755 --- a/httemplate/search/svc_www.cgi +++ b/httemplate/search/svc_www.cgi @@ -1,4 +1,4 @@ -<% include( 'elements/search.html', +<& elements/svc_Common.html, 'title' => 'Virtual Host Search Results', 'name' => 'virtual hosts', 'query' => $sql_query, @@ -45,8 +45,8 @@ '', FS::UI::Web::cust_styles(), ], - ) -%> + +&> <%init> die "access denied" @@ -73,7 +73,7 @@ if ( $cgi->param('magic') =~ /^(all|unlinked)$/ ) { my $addl_from = ' LEFT JOIN cust_svc USING ( svcnum ) '. ' LEFT JOIN part_svc USING ( svcpart ) '. ' LEFT JOIN cust_pkg USING ( pkgnum ) '. - ' LEFT JOIN cust_main USING ( custnum ) '; + FS::UI::Web::join_cust_main('cust_pkg', 'cust_pkg'); #here is the agent virtualization push @extra_sql, $FS::CurrentUser::CurrentUser->agentnums_sql( diff --git a/httemplate/search/timeworked.html b/httemplate/search/timeworked.html index bbfd0334d..fa4b89539 100644 --- a/httemplate/search/timeworked.html +++ b/httemplate/search/timeworked.html @@ -1,4 +1,4 @@ -<% include( 'elements/search.html', +<& elements/search.html, 'title' => 'Time Worked', 'name' => 'time', 'html_form' => qq!<FORM NAME="timeForm" ACTION="${p}misc/timeworked.html" METHOD="POST">!, @@ -33,9 +33,8 @@ '', ], 'html_foot' => $html_foot, - ) - -%> + +&> <%init> die "access denied" diff --git a/httemplate/search/unearned_detail.html b/httemplate/search/unearned_detail.html index f61de052e..285fb50a7 100644 --- a/httemplate/search/unearned_detail.html +++ b/httemplate/search/unearned_detail.html @@ -114,13 +114,12 @@ if ( $cgi->param('status') =~ /^([a-z]+)$/ ) { push @where, "cust_bill._date >= $beginning", "cust_bill._date <= $ending"; -if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) { - push @where, "cust_main.agentnum = $1"; -} - -if ( $cgi->param('cust_classnum') ) { - my @classnums = grep /^\d+$/, $cgi->param('cust_classnum'); - push @where, 'cust_main.classnum IN('.join(',',@classnums).')' +# cust_classnum (false laziness w/ elements/cust_main_dayranges.html, elements/cust_pay_or_refund.html, prepaid_income.html, cust_bill_pay.html, cust_bill_pkg.html, cust_bill_pkg_referral.html, cust_credit.html, cust_credit_refund.html, cust_main::Search::search_sql) +if ( grep { $_ eq 'cust_classnum' } $cgi->param ) { + my @classnums = grep /^\d*$/, $cgi->param('cust_classnum'); + push @where, 'COALESCE( cust_main.classnum, 0) IN ( '. + join(',', map { $_ || '0' } @classnums ). + ' )' if @classnums; } @@ -210,8 +209,8 @@ push @select, '(edate - 82799) AS before_edate'; #usage always excluded # always 'nottax', not 'istax' -$join_cust = ' JOIN cust_bill USING ( invnum ) - LEFT JOIN cust_main USING ( custnum ) '; +$join_cust = ' JOIN cust_bill USING ( invnum ) '. + FS::UI::Web::join_cust_main('cust_pkg', 'cust_pkg'); $join_pkg .= ' LEFT JOIN cust_pkg USING ( pkgnum ) LEFT JOIN part_pkg USING ( pkgpart ) @@ -222,7 +221,7 @@ my $where = ' WHERE '. join(' AND ', @where); my $count_query = "SELECT COUNT(DISTINCT billpkgnum), SUM( $unearned_base ), SUM( $unearned_sql ) - FROM cust_bill_pkg $join_cust $join_pkg $where"; + FROM cust_bill_pkg $join_pkg $join_cust $where"; push @select, 'part_pkg.pkg', 'part_pkg.freq', @@ -231,7 +230,7 @@ push @select, 'part_pkg.pkg', my $query = { 'table' => 'cust_bill_pkg', - 'addl_from' => "$join_cust $join_pkg", + 'addl_from' => "$join_pkg $join_cust", 'hashref' => {}, 'select' => join(",\n", @select ), 'extra_sql' => $where, diff --git a/httemplate/search/unprovisioned_services.html b/httemplate/search/unprovisioned_services.html index f85e4fb19..a7791ba86 100644 --- a/httemplate/search/unprovisioned_services.html +++ b/httemplate/search/unprovisioned_services.html @@ -1,4 +1,4 @@ -<% include( 'elements/search.html', +<& elements/search.html, 'title' => 'Unprovisioned Service Search Results', 'name' => 'packages with unprovisioned services', 'query' => { @@ -54,8 +54,8 @@ '', FS::UI::Web::cust_styles(), ], - ) -%> + +&> <%init> die "access denied" @@ -74,7 +74,8 @@ my $search = " where cust_pkg.cancel is null and pkg_svc.quantity > 0 and " . " cust_svc.pkgnum = cust_pkg.pkgnum and " . " cust_svc.svcpart = pkg_svc.svcpart) $svcpart_limit"; -my $addl_from = " join pkg_svc using (pkgpart) join cust_main using (custnum) "; +my $addl_from = " join pkg_svc using (pkgpart) ". + FS::UI::Web::join_cust_main('cust_pkg', 'cust_pkg'); # this was very painful to derive but it appears correct #select cust_pkg.custnum,cust_pkg.pkgpart,cust_pkg.pkgnum, pkg_svc.svcpart from cust_pkg join diff --git a/httemplate/view/bill_batch.cgi b/httemplate/view/bill_batch.cgi index 7d640395e..55ee4be1c 100644 --- a/httemplate/view/bill_batch.cgi +++ b/httemplate/view/bill_batch.cgi @@ -13,7 +13,7 @@ 'hashref' => { }, 'addl_from' => 'LEFT JOIN cust_bill USING ( invnum ) '. - 'LEFT JOIN cust_main USING ( custnum )', + FS::UI::Web::join_cust_main('cust_bill'), 'extra_sql' => " WHERE batchnum = $batchnum", }, 'count_query' => "SELECT COUNT(*) FROM cust_bill_batch WHERE batchnum = $batchnum", diff --git a/httemplate/view/cust_main/billing.html b/httemplate/view/cust_main/billing.html index 5c46803d2..b863a734b 100644 --- a/httemplate/view/cust_main/billing.html +++ b/httemplate/view/cust_main/billing.html @@ -247,6 +247,10 @@ <TD ALIGN="right"><% mt('Email address(es)') |h %></TD> <TD BGCOLOR="#ffffff"> <% join(', ', grep { $_ !~ /^(POST|FAX)$/ } @invoicing_list ) || $no %> +% if ( $cust_main->message_noemail ) { + <BR> + <SPAN STYLE="font-size: small"><% emt('(do not send notices)') %></SPAN> +% } </TD> </TR> % } diff --git a/httemplate/view/cust_main/change_history.html b/httemplate/view/cust_main/change_history.html index ea84b8f75..bf32a49f9 100644 --- a/httemplate/view/cust_main/change_history.html +++ b/httemplate/view/cust_main/change_history.html @@ -43,10 +43,12 @@ tie my %tables, 'Tie::IxHash', 'svc_external' => 'External service', 'svc_phone' => 'Phone', 'phone_device' => 'Phone device', + 'cust_pkg_discount' => 'Discount', #? it gets provisioned anyway 'phone_avail' => 'Phone', ; -my $svc_join = 'JOIN cust_svc USING ( svcnum ) JOIN cust_pkg USING ( pkgnum )'; +my $pkg_join = "JOIN cust_pkg USING ( pkgnum )"; +my $svc_join = "JOIN cust_svc USING ( svcnum ) $pkg_join"; my %table_join = ( 'svc_acct' => $svc_join, @@ -58,6 +60,7 @@ my %table_join = ( 'svc_external' => $svc_join, 'svc_phone' => $svc_join, 'phone_device' => $svc_join, + 'cust_pkg_discount'=> $pkg_join, ); @@ -104,7 +107,7 @@ my $conf = new FS::Conf; my $curuser = $FS::CurrentUser::CurrentUser; -die "access deined" +die "access denied" unless $curuser->access_right('View customer history'); # find out the beginning of this customer history, if possible diff --git a/httemplate/view/cust_main/packages.html b/httemplate/view/cust_main/packages.html index 7d7930634..546dd89c3 100755 --- a/httemplate/view/cust_main/packages.html +++ b/httemplate/view/cust_main/packages.html @@ -1,3 +1,29 @@ +<STYLE TYPE="text/css"> +td.package { + vertical-align: top; + border-width: 0; + border-style: solid; + border-color: #bbbbff; +} +table.package { + border: none; + padding: 0; + border-spacing: 0; + width: 100%; +} +table.usage { + border: 1px solid black; + margin: auto; + width: 60%; + border-spacing: 0px; +} +.shared > * { + background-color: #ffffaa; +} +.row0 { background-color: #eeeeee; } +.row1 { background-color: #ffffff; } + +</STYLE> % my $s = 0; % if ( $curuser->access_right('Qualify service') ) { @@ -75,7 +101,7 @@ <TR> <TD COLSPAN=2> -% if ( $conf->exists('cust_pkg-group_by_location') and $show_location ) { +% if ( $conf->exists('cust_pkg-group_by_location') ) { <& locations.html, 'cust_main' => $cust_main, 'packages' => $packages, @@ -87,7 +113,6 @@ <& packages/section.html, 'cust_main' => $cust_main, 'packages' => $packages, - 'show_location' => $show_location, &> </TABLE> % } @@ -114,10 +139,6 @@ my $curuser = $FS::CurrentUser::CurrentUser; my( $packages, $num_old_packages ) = get_packages($cust_main, $conf); - -my $show_location = $conf->exists('cust_pkg-always_show_location') - || (grep $_->locationnum, @$packages); # ? '1' : '0'; - my $countrydefault = scalar($conf->config('countrydefault')) || 'US'; #subroutines @@ -178,6 +199,10 @@ sub get_packages { } $num_old_packages -= scalar(@packages); + + # don't include supplemental packages in this list; they'll be found from + # their main packages + @packages = grep !$_->main_pkgnum, @packages; ( \@packages, $num_old_packages ); } diff --git a/httemplate/view/cust_main/packages/contact.html b/httemplate/view/cust_main/packages/contact.html new file mode 100644 index 000000000..93129915f --- /dev/null +++ b/httemplate/view/cust_main/packages/contact.html @@ -0,0 +1,61 @@ +% if ( $contact ) { + <% $contact->line |h %> +% if ( $show_link ) { + <FONT SIZE=-1> + ( <%pkg_change_contact_link($cust_pkg)%> ) + </FONT> +% } +% } elsif ( $show_link ) { + <FONT SIZE=-1> + ( <%pkg_add_contact_link($cust_pkg)%> ) + </FONT> +% } +<%init> + +my $conf = new FS::Conf; +my %opt = @_; + +my $cust_pkg = $opt{'cust_pkg'}; + +my $show_link = + ! $cust_pkg->get('cancel') + && $FS::CurrentUser::CurrentUser->access_right('Change customer package'); + +my $contact = $cust_pkg->contact_obj; + +sub pkg_change_contact_link { + my $cust_pkg = shift; + #my $pkgpart = $cust_pkg->pkgpart; + include( '/elements/popup_link-cust_pkg.html', + 'action' => $p. "misc/change_pkg_contact.html", + 'label' => emt('Change'), # contact'), + 'actionlabel' => emt('Change'), + 'cust_pkg' => $cust_pkg, + 'width' => 616, + 'height' => 220, + ); +} + +sub pkg_add_contact_link { + my $cust_pkg = shift; + #my $pkgpart = $cust_pkg->pkgpart; + include( '/elements/popup_link-cust_pkg.html', + 'action' => $p. "misc/change_pkg_contact.html", + 'label' => emt('Add contact'), + 'actionlabel' => emt('Change'), + 'cust_pkg' => $cust_pkg, + 'width' => 616, + 'height' => 192, + ); +} + +#sub edit_contact_link { +# my $contactnum = shift; +# include( '/elements/popup_link.html', +# 'action' => $p. "edit/cust_contact.cgi?contactnum=$contactnum", +# 'label' => emt('Edit contact'), +# 'actionlabel' => emt('Edit'), +# ); +#} + +</%init> diff --git a/httemplate/view/cust_main/packages/location.html b/httemplate/view/cust_main/packages/location.html index 34e3a64c3..f2d379841 100644 --- a/httemplate/view/cust_main/packages/location.html +++ b/httemplate/view/cust_main/packages/location.html @@ -1,7 +1,5 @@ -<TD CLASS="inv" BGCOLOR="<% $bgcolor %>" WIDTH="20%"> - -% unless ( $cust_pkg->locationnum ) { - <I><FONT SIZE=-1>(<% mt('default service address') |h %>)</FONT><BR> +% if ( $default ) { + <DIV STYLE="font-style: italic; font-size: small"> % } <% $loc->location_label( 'join_string' => '<BR>', @@ -24,8 +22,8 @@ </FONT> % } -% unless ( $cust_pkg->locationnum ) { - </I> +% if ( $default ) { + </DIV> % } % if ( ! $cust_pkg->get('cancel') @@ -41,19 +39,19 @@ </FONT> % } -</TD> <%init> my $conf = new FS::Conf; my %opt = @_; -my $bgcolor = $opt{'bgcolor'}; my $cust_pkg = $opt{'cust_pkg'}; my $countrydefault = $opt{'countrydefault'} || 'US'; my $statedefault = $opt{'statedefault'} || ($countrydefault eq 'US' ? 'CA' : ''); my $loc = $cust_pkg->cust_location_or_main; +# dubious--they should all have a location now +my $default = $cust_pkg->locationnum == $opt{'cust_main'}->ship_locationnum; sub pkg_change_location_link { my $cust_pkg = shift; diff --git a/httemplate/view/cust_main/packages/package.html b/httemplate/view/cust_main/packages/package.html index 5d93ad46f..520305a9a 100644 --- a/httemplate/view/cust_main/packages/package.html +++ b/httemplate/view/cust_main/packages/package.html @@ -1,5 +1,6 @@ -<TD CLASS="inv" BGCOLOR="<% $bgcolor %>" VALIGN="top"> - <TABLE CLASS="inv" BORDER=0 CELLSPACING=0 CELLPADDING=0 WIDTH="100%"> +<TD CLASS="inv package" BGCOLOR="<% $bgcolor %>" VALIGN="top" + STYLE="border-left-width: <% $supplemental * 30 %>px"> + <TABLE CLASS="inv package"> <TR> <TD COLSPAN=2> <A NAME="cust_pkg<% $cust_pkg->pkgnum %>" @@ -17,50 +18,62 @@ <B><% $cust_pkg->quantity %></B> </TD> </TR> -% } +% } <TR> <TD COLSPAN=2> <FONT SIZE=-1> -% unless ( $cust_pkg->get('cancel') ) { +% unless ( $cust_pkg->get('cancel') ) { % -% my $br = 0; -% if ( $curuser->access_right('Change customer package') ) { -% $br=1; - ( <%pkg_change_link($cust_pkg)%> ) -% } +% if ( $supplemental or $part_pkg->freq eq '0' ) { +% # Supplemental packages can't be changed independently. +% # One-time charges don't need to be changed. +% # For both of those, we only show "Edit dates", "Add comments", +% # and "Add invoice details". +% if ( $curuser->access_right('Edit customer package dates') ) { + ( <%pkg_dates_link($cust_pkg)%> ) +% } +% } else { +% # the usual case: links to change package definition, +% # discount, and customization +% my $br = 0; +% if ( $curuser->access_right('Change customer package') ) { +% $br=1; + ( <%pkg_change_link($cust_pkg)%> ) +% } % -% if ( $curuser->access_right('Edit customer package dates') ) { -% $br=1; - ( <%pkg_dates_link($cust_pkg)%> ) -% } +% if ( $curuser->access_right('Edit customer package dates') ) { +% $br=1; + ( <%pkg_dates_link($cust_pkg)%> ) +% } % -% if ( $curuser->access_right('Discount customer package') -% && $part_pkg->can_discount -% && ! scalar($cust_pkg->cust_pkg_discount_active) -% && ! scalar($cust_pkg->part_pkg->part_pkg_discount) -% ) -% { -% $br=1; - ( <%pkg_discount_link($cust_pkg)%> ) -% } +% if ( $curuser->access_right('Discount customer package') +% && $part_pkg->can_discount +% && ! scalar($cust_pkg->cust_pkg_discount_active) +% && ! scalar($cust_pkg->part_pkg->part_pkg_discount) +% ) +% { +% $br=1; + ( <%pkg_discount_link($cust_pkg)%> ) +% } % -% if ( $curuser->access_right('Customize customer package') ) { -% $br=1; - ( <%pkg_customize_link($cust_pkg,$part_pkg)%> ) -% } +% if ( $curuser->access_right('Customize customer package') ) { +% $br=1; + ( <%pkg_customize_link($cust_pkg,$part_pkg)%> ) +% } % - <% $br ? '<BR>' : '' %> -% } + <% $br ? '<BR>' : '' %> +% } -% if ( $cust_pkg->num_cust_event -% && ( $curuser->access_right('Billing event reports') -% || $curuser->access_right('View customer billing events') -% ) -% ) { - ( <%pkg_event_link($cust_pkg)%> ) -% } +% if ( $cust_pkg->num_cust_event +% && ( $curuser->access_right('Billing event reports') +% || $curuser->access_right('View customer billing events') +% ) +% ) { + ( <%pkg_event_link($cust_pkg)%> ) +% } +% } #!$supplemental </FONT> </TD> @@ -170,15 +183,40 @@ </TR> % if ( $curuser->access_right('Change customer package') and % !$cust_pkg->get('cancel') and -% !$opt{'show_location'}) { +% !$supplemental and +% $part_pkg->freq ne '0' ) { <TR> +% if ( FS::Conf->new->exists('invoice-unitprice') ) { <TD><FONT SIZE="-1"> - ( <% pkg_change_location_link($cust_pkg) %> ) + ( <% pkg_change_quantity_link($cust_pkg) %> ) </FONT></TD> +% } </TR> % } % } </TABLE> +% if ( @cust_pkg_usage ) { + <TABLE CLASS="usage inv"> + <TR><TH COLSPAN=4><% mt('Included usage') %></TH></TR> +% foreach my $usage (@cust_pkg_usage) { +% my $part = $usage->part_pkg_usage; +% my $ratio = 255 * ($usage->minutes / $part->minutes); +% $ratio = 255 if $ratio > 255; # because rollover +% my $color = sprintf('STYLE="font-weight: bold; color: #%02x%02x00"', 255 - $ratio, $ratio); +% my $trstyle = ''; +% $trstyle = ' CLASS="shared"' if $part->shared; + <TR<%$trstyle%>> + <TD ALIGN="right"><% $part->description %>: </TD> + <TD <%$color%> ALIGN="right"><% $usage->minutes %></TD> + <TD <%$color%>> / </TD> + <TD <%$color%>><% $part->minutes %></TD> +% if ( $part->shared ) { + <TD><I>(shared)</I></TD> +% } + </TR> +% } + </TABLE> +% } </TD> @@ -196,6 +234,18 @@ my $countrydefault = $opt{'countrydefault'} || 'US'; my $statedefault = $opt{'statedefault'} || ($countrydefault eq 'US' ? 'CA' : ''); +my $supplemental = $opt{'supplemental'} || 0; + +$cust_pkg->pkgnum =~ /^(\d+)$/; +my $pkgnum = $1; +my @cust_pkg_usage = qsearch({ + 'select' => 'cust_pkg_usage.*', + 'table' => 'cust_pkg_usage', + 'addl_from' => ' JOIN part_pkg_usage USING (pkgusagepart)', + 'extra_sql' => " WHERE pkgnum = $1", + 'order_by' => ' ORDER BY priority ASC, description ASC', +}); + #subroutines #false laziness w/status.html @@ -229,6 +279,17 @@ sub pkg_change_location_link { ); } +sub pkg_change_quantity_link { + include( '/elements/popup_link-cust_pkg.html', + 'action' => $p. 'edit/cust_pkg_quantity.html?', + 'label' => emt('Change quantity'), + 'actionlabel' => emt('Change'), + 'cust_pkg' => shift, + 'width' => 390, + 'height' => 220, + ); +} + sub pkg_dates_link { pkg_link('edit/REAL_cust_pkg', emt('Edit dates'), @_ ); } sub pkg_discount_link { diff --git a/httemplate/view/cust_main/packages/section.html b/httemplate/view/cust_main/packages/section.html index 85f0c795a..5f54c0a36 100755 --- a/httemplate/view/cust_main/packages/section.html +++ b/httemplate/view/cust_main/packages/section.html @@ -1,53 +1,48 @@ % if ( @$packages ) { -% my $bgcolor1 = '#eeeeee'; -% my $bgcolor2 = '#ffffff'; -% my $bgcolor = ''; - <TR> % #my $width = $show_location ? 'WIDTH="25%"' : 'WIDTH="33%"'; <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('Package') |h %></TH> <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('Status') |h %></TH> -% if ( $show_location ) { - <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('Location') |h %></TH> -% } + <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('Contact/Location') |h %></TH> <TH CLASS="grid" BGCOLOR="#cccccc"><% mt('Services') |h %></TH> </TR> % #$FS::cust_pkg::DEBUG = 2; % foreach my $cust_pkg (@$packages) { + <& .packagerow, $cust_pkg, + 'cust_main' => $opt{'cust_main'}, + 'bgcolor' => $opt{'bgcolor'}, + %conf_opt + &> +% } +% } else { # there are no packages +<BR> +% } +<%def .packagerow> % -% if ( $bgcolor eq $bgcolor1 ) { -% $bgcolor = $bgcolor2; -% } else { -% $bgcolor = $bgcolor1; -% } -% -% my %iopt = ( -% 'bgcolor' => $bgcolor, -% 'cust_pkg' => $cust_pkg, -% 'part_pkg' => $cust_pkg->part_pkg, -% 'cust_main' => $opt{'cust_main'}, -% %conf_opt, -% ); -% - +% my ($cust_pkg, %iopt) = @_; +% $iopt{'cust_pkg'} = $cust_pkg; +% $iopt{'part_pkg'} = $cust_pkg->part_pkg; <!--pkgnum: <% $cust_pkg->pkgnum %>--> - <TR> + <TR CLASS="row<%$row % 2%>"> <& package.html, %iopt &> - <& status.html, %iopt &> -% if ( $show_location ) { - <& location.html, %iopt &> -% } + <& status.html, %iopt &> + <TD CLASS="inv" BGCOLOR="<% $iopt{bgcolor} %>" WIDTH="20%" VALIGN="top"> + <& contact.html, %iopt &> + <& location.html, %iopt &> + </TD> <& services.html, %iopt &> </TR> - -% } #foreach $cust_pkg -%# </TABLE> -% } #if @$packages -% else { -<BR> +% $row++; +% # include supplemental packages if any +% $iopt{'supplemental'} = ($iopt{'supplemental'} || 0) + 1; +% foreach my $supp_pkg ($cust_pkg->supplemental_pkgs) { + <& .packagerow, $supp_pkg, %iopt &> % } - +</%def> +<%shared> +my $row = 0; +</%shared> <%init> my %opt = @_; @@ -56,7 +51,6 @@ my $conf = new FS::Conf; my $curuser = $FS::CurrentUser::CurrentUser; my $packages = $opt{'packages'}; -my $show_location = $opt{'show_location'}; # Sort order is hardcoded for now, can change this if needed. @$packages = sort { @@ -89,10 +83,8 @@ my %conf_opt = ( 'manage_link_loc' => scalar($conf->config('svc_broadband-manage_link_loc')), 'manage_link-new_window' => $conf->exists('svc_broadband-manage_link-new_window'), 'maestro-status_test' => $conf->exists('maestro-status_test'), - 'cust_pkg-large_pkg_size' => $conf->config('cust_pkg-large_pkg_size'), + 'cust_pkg-large_pkg_size' => scalar($conf->config('cust_pkg-large_pkg_size')), - # for packages.html Change location link - 'show_location' => $show_location, ); </%init> diff --git a/httemplate/view/cust_main/packages/status.html b/httemplate/view/cust_main/packages/status.html index e9017745b..9d5a88e0f 100644 --- a/httemplate/view/cust_main/packages/status.html +++ b/httemplate/view/cust_main/packages/status.html @@ -1,9 +1,11 @@ -<TD CLASS="inv" BGCOLOR="<% $bgcolor %>"> +<TD CLASS="inv" BGCOLOR="<% $bgcolor %>" VALIGN="top"> <TABLE CLASS="inv" BORDER=0 CELLSPACING=0 CELLPADDING=0 WIDTH="100%"> %#this should use cust_pkg->status and cust_pkg->statuscolor eventually -% if ( $cust_pkg->order_date ) { +% if ( $supplemental ) { + <% pkg_status_row_colspan($cust_pkg, emt('Supplemental'), '', 'color' => '7777FF', %opt) %> +% } elsif ( $cust_pkg->order_date ) { <% pkg_status_row($cust_pkg, emt('Ordered'), 'order_date', %opt ) %> % } @@ -12,30 +14,25 @@ <% pkg_status_row($cust_pkg, emt('Cancelled'), 'cancel', 'color'=>'FF0000', %opt ) %> - <% pkg_status_row_colspan( $cust_pkg, - ( $cpr ? $cpr->reasontext. ' by '. $cpr->otaker : '' ), '', - 'align'=>'right', 'color'=>'ff0000', 'size'=>'-2', 'colspan'=>$colspan, - %opt - ) - %> + <% pkg_reason_row($cust_pkg, $cpr, color => 'ff0000', %opt) %> % unless ( $cust_pkg->get('setup') ) { - <% pkg_status_row_colspan( $cust_pkg, emt('Never billed'), '', 'colspan'=>$colspan, %opt, ) %> + <% pkg_status_row_colspan( $cust_pkg, emt('Never billed'), '', %opt, ) %> % } else { <% pkg_status_row( $cust_pkg, emt('Setup'), 'setup', %opt ) %> - <% pkg_status_row_changed( $cust_pkg, %opt, 'colspan'=>$colspan ) %> + <% pkg_status_row_changed( $cust_pkg, %opt ) %> <% pkg_status_row_if( $cust_pkg, $last_bill_or_renewed, 'last_bill', %opt, curuser=>$curuser ) %> <% pkg_status_row_if( $cust_pkg, emt('Suspended'), 'susp', %opt, curuser=>$curuser ) %> % } % -% if ( $part_pkg->freq ) { #? +% if ( $part_pkg->freq and !$supplemental ) { #? <TR> - <TD COLSPAN=<%$colspan%>> + <TD COLSPAN=<%$opt{colspan}%>> <FONT SIZE=-1> % if ( $curuser->access_right('Un-cancel customer package') ) { ( <% pkg_uncancel_link($cust_pkg) %> ) @@ -52,26 +49,21 @@ <% pkg_status_row( $cust_pkg, emt('Suspended'), 'susp', 'color'=>'FF9900', %opt ) %> - <% pkg_status_row_colspan( $cust_pkg, - ( $cpr ? $cpr->reasontext. ' by '. $cpr->otaker : '' ), '', - 'align'=>'right', 'color'=>'FF9900', 'size'=>'-2', 'colspan'=>$colspan, - %opt, - ) - %> + <% pkg_reason_row( $cust_pkg, $cpr, 'color' => 'FF9900', %opt ) %> - <% pkg_status_row_noauto( $cust_pkg, %opt, 'colspan'=>$colspan ) %> + <% pkg_status_row_noauto( $cust_pkg, %opt ) %> - <% pkg_status_row_discount( $cust_pkg, %opt, 'colspan'=>$colspan ) %> + <% pkg_status_row_discount( $cust_pkg, %opt ) %> % unless ( $cust_pkg->get('setup') ) { - <% pkg_status_row_colspan( $cust_pkg, emt('Never billed'), '', 'colspan'=>$colspan, %opt ) %> + <% pkg_status_row_colspan( $cust_pkg, emt('Never billed'), '', %opt ) %> % } else { <% pkg_status_row($cust_pkg, emt('Setup'), 'setup', %opt ) %> % } <% pkg_status_row_if($cust_pkg, emt('Un-cancelled'), 'uncancel', %opt ) %> - <% pkg_status_row_changed( $cust_pkg, %opt, 'colspan'=>$colspan ) %> + <% pkg_status_row_changed( $cust_pkg, %opt ) %> <% pkg_status_row_if( $cust_pkg, $last_bill_or_renewed, 'last_bill', %opt, curuser=>$curuser ) %> % if ( $cust_pkg->option('suspend_bill', 1) % || ( $part_pkg->option('suspend_bill', 1) @@ -85,31 +77,33 @@ <% pkg_status_row_if( $cust_pkg, emt('Expires'), 'expire', %opt, curuser=>$curuser ) %> <% pkg_status_row_if( $cust_pkg, emt('Contract ends'), 'contract_end', %opt ) %> - <TR> - <TD COLSPAN=<%$colspan%>> - <FONT SIZE=-1> -% if ( $curuser->access_right('Unsuspend customer package') ) { - ( <% pkg_unsuspend_link($cust_pkg) %> ) - ( <% pkg_resume_link($cust_pkg) %> ) -% } -% if ( $curuser->access_right('Cancel customer package immediately') ) { - ( <% pkg_cancel_link($cust_pkg) %> ) -% } - </FONT> - </TD> - </TR> - +% if ( !$supplemental ) { + <TR> + <TD COLSPAN=<%$opt{colspan}%>> + <FONT SIZE=-1> +% if ( $curuser->access_right('Unsuspend customer package') ) { + ( <% pkg_unsuspend_link($cust_pkg) %> ) + ( <% pkg_resume_link($cust_pkg) %> ) +% } +% if ( $curuser->access_right('Cancel customer package immediately') ) { + ( <% pkg_cancel_link($cust_pkg) %> ) +% } + </FONT> + </TD> + </TR> +% } +% % } else { #status: active % % unless ( $cust_pkg->get('setup') ) { #not setup % % unless ( $part_pkg->freq ) { - <% pkg_status_row_colspan( $cust_pkg, emt('Not yet billed (one-time charge)'), '', 'colspan'=>$colspan, %opt ) %> + <% pkg_status_row_colspan( $cust_pkg, emt('Not yet billed (one-time charge)'), '', %opt ) %> - <% pkg_status_row_noauto( $cust_pkg, %opt, 'colspan'=>$colspan ) %> + <% pkg_status_row_noauto( $cust_pkg, %opt ) %> - <% pkg_status_row_discount( $cust_pkg, %opt, 'colspan'=>$colspan ) %> + <% pkg_status_row_discount( $cust_pkg, %opt ) %> <% pkg_status_row_if( $cust_pkg, @@ -121,8 +115,9 @@ <% pkg_status_row_if($cust_pkg, emt('Un-cancelled'), 'uncancel', %opt ) %> +% if (!$supplemental) { <TR> - <TD COLSPAN=<%$colspan%>> + <TD COLSPAN=<%$opt{colspan}%>> <FONT SIZE=-1> % if ( $curuser->access_right('Cancel customer package immediately') ) { ( <% pkg_cancel_link($cust_pkg) %> ) @@ -130,14 +125,15 @@ </FONT> </TD> </TR> +% } % } else { - <% pkg_status_row_colspan($cust_pkg, emt("Not yet billed ($billed_or_prepaid [_1])", myfreq($part_pkg) ), '', 'colspan'=>$colspan, %opt ) %> + <% pkg_status_row_colspan($cust_pkg, emt("Not yet billed ($billed_or_prepaid [_1])", myfreq($part_pkg) ), '', %opt ) %> - <% pkg_status_row_noauto( $cust_pkg, %opt, 'colspan'=>$colspan ) %> + <% pkg_status_row_noauto( $cust_pkg, %opt ) %> - <% pkg_status_row_discount( $cust_pkg, %opt, 'colspan'=>$colspan ) %> + <% pkg_status_row_discount( $cust_pkg, %opt ) %> <% pkg_status_row_if($cust_pkg, emt('Start billing'), 'start_date', %opt) %> <% pkg_status_row_if($cust_pkg, emt('Un-cancelled'), 'uncancel', %opt ) %> @@ -148,13 +144,13 @@ % % unless ( $part_pkg->freq ) { - <% pkg_status_row_colspan($cust_pkg, emt('One-time charge'), '', 'colspan'=>$colspan, %opt ) %> + <% pkg_status_row_colspan($cust_pkg, emt('One-time charge'), '', %opt ) %> <% pkg_status_row($cust_pkg, emt('Billed'), 'setup', %opt) %> - <% pkg_status_row_noauto( $cust_pkg, %opt, 'colspan'=>$colspan ) %> + <% pkg_status_row_noauto( $cust_pkg, %opt ) %> - <% pkg_status_row_discount( $cust_pkg, %opt, 'colspan'=>$colspan ) %> + <% pkg_status_row_discount( $cust_pkg, %opt ) %> <% pkg_status_row_if($cust_pkg, emt('Un-cancelled'), 'uncancel', %opt ) %> @@ -170,7 +166,7 @@ <% pkg_status_row_colspan( $cust_pkg, emt('Overlimit'), $billed_or_prepaid. ' '. myfreq($part_pkg), - 'color'=>'FFD000', 'colspan'=>$colspan, + 'color'=>'FFD000', %opt ) %> @@ -179,15 +175,15 @@ <% pkg_status_row_colspan( $cust_pkg, emt('Active'), $billed_or_prepaid. ' '. myfreq($part_pkg), - 'color'=>'00CC00', 'colspan'=>$colspan, + 'color'=>'00CC00', %opt ) %> % } - <% pkg_status_row_noauto( $cust_pkg, %opt, 'colspan'=>$colspan ) %> + <% pkg_status_row_noauto( $cust_pkg, %opt ) %> - <% pkg_status_row_discount( $cust_pkg, %opt, 'colspan'=>$colspan ) %> + <% pkg_status_row_discount( $cust_pkg, %opt ) %> <% pkg_status_row($cust_pkg, emt('Setup'), 'setup', %opt) %> @@ -202,7 +198,7 @@ % $cust_pkg->set('autosuspend', $autosuspend) if $autosuspend; % } - <% pkg_status_row_changed( $cust_pkg, %opt, 'colspan'=>$colspan ) %> + <% pkg_status_row_changed( $cust_pkg, %opt ) %> <% pkg_status_row_if( $cust_pkg, $last_bill_or_renewed, 'last_bill', %opt, curuser=>$curuser ) %> <% pkg_status_row_if( $cust_pkg, $next_bill_or_prepaid_until, 'bill', %opt, curuser=>$curuser ) %> <% pkg_status_row_if($cust_pkg, emt('Will automatically suspend by'), 'autosuspend', %opt) %> @@ -212,10 +208,10 @@ <% pkg_status_row_if( $cust_pkg, emt('Expires'), 'expire', %opt, curuser=>$curuser ) %> <% pkg_status_row_if( $cust_pkg, emt('Contract ends'), 'contract_end', %opt ) %> -% if ( $part_pkg->freq ) { +% if ( $part_pkg->freq and !$supplemental ) { <TR> - <TD COLSPAN=<%$colspan%>> + <TD COLSPAN=<%$opt{colspan}%>> <FONT SIZE=-1> % if ( $curuser->access_right('Suspend customer package') ) { ( <% pkg_suspend_link($cust_pkg) %> ) @@ -251,8 +247,10 @@ my $bgcolor = $opt{'bgcolor'}; my $cust_pkg = $opt{'cust_pkg'}; my $part_pkg = $opt{'part_pkg'}; my $curuser = $FS::CurrentUser::CurrentUser; -my $colspan = $opt{'cust_pkg-display_times'} ? 8 : 4; my $width = $opt{'cust_pkg-display_times'} ? '38%' : '56%'; +my $supplemental = $opt{'supplemental'}; + +$opt{colspan} = $opt{'cust_pkg-display_times'} ? 8 : 4; #false laziness w/edit/REAL_cust_pkg.cgi my( $billed_or_prepaid, $last_bill_or_renewed, $next_bill_or_prepaid_until ); @@ -285,9 +283,27 @@ sub pkg_link { sub pkg_status_row { my( $cust_pkg, $title, $field, %opt ) = @_; + if ( $field and $cust_pkg->main_pkgnum ) { + # for supplemental packages, we mostly only show these if they're + # different from the main package + my $main_pkg = $cust_pkg-> main_pkg; + if ( $main_pkg->get($field) ne $cust_pkg->get($field) + # with some exceptions + or $field eq 'bill' + or $field eq 'last_bill' + or $field eq 'setup' + or $field eq 'susp' + or $field eq 'cancel' + ) { + # handle it normally + } else { + return ''; + } + } + my $color = $opt{'color'}; - my $html = qq(<TR><TD WIDTH="<%$width%>" ALIGN="right">); + my $html = qq(<TR><TD WIDTH="$width" ALIGN="right">); $html .= qq(<FONT COLOR="#$color"><B>) if length($color); $html .= qq($title ); $html .= qq(</B></FONT>) if length($color); @@ -338,7 +354,6 @@ sub pkg_status_row_changed { '', 'size' => '-1', 'align' => 'right', - 'colspan' => $opt{'colspan'}, ); } @@ -356,9 +371,7 @@ sub pkg_status_row_noauto { return '' unless $cust_main->payby =~ /^(CARD|CHEK)$/; my $what = lc(FS::payby->shortname($cust_main->payby)); - pkg_status_row_colspan( $cust_pkg, emt("No automatic $what charge"), '', - 'colspan' => $opt{'colspan'}, - ); + pkg_status_row_colspan( $cust_pkg, emt("No automatic $what charge"), ''); } sub pkg_status_row_discount { @@ -382,15 +395,24 @@ sub pkg_status_row_discount { $cust_pkg_discount->pkgdiscountnum. '">'.emt('remove discount').'</A>)</FONT>'; - $html .= pkg_status_row_colspan( $cust_pkg, $label, '', - 'colspan' => $opt{'colspan'}, - ); + $html .= pkg_status_row_colspan( $cust_pkg, $label, '', %opt ); } $html; } +sub pkg_reason_row { + my ($cust_pkg, $cpr, %opt) = @_; + return '' if $cust_pkg->main_pkgnum; + + my $reasontext = ''; + $reasontext = $cpr->reasontext . ' by ' . $cpr->otaker if $cpr; + pkg_status_row_colspan( $cust_pkg, $reasontext, '', + 'align'=>'right', 'size'=>'-2', %opt + ); +} + sub pkg_status_row_colspan { my($cust_pkg, $title, $addl, %opt) = @_; diff --git a/httemplate/view/cust_main/payment_history.html b/httemplate/view/cust_main/payment_history.html index 942b42f54..915be49e5 100644 --- a/httemplate/view/cust_main/payment_history.html +++ b/httemplate/view/cust_main/payment_history.html @@ -34,7 +34,7 @@ <A HREF="<% $p %>edit/cust_pay.cgi?payby=WEST;custnum=<% $custnum %>"><% mt('Enter Western Union payment') |h %></A> % } -<BR> +<% $s ? '<BR>' : '' %> % $s=0; % if ( ( $payby{'CARD'} || $payby{'DCRD'} ) @@ -58,11 +58,13 @@ <A HREF="<% $p %>edit/cust_pay.cgi?payby=MCRD;custnum=<% $custnum %>"><% mt('Post manual (offline/POS) credit card payment') |h %></A> % } -<BR> +<% $s ? '<BR>' : '' %> -%# credit link +%# credit links +% $s=0; % if ( $curuser->access_right('Post credit') ) { + <% $s++ ? ' | ' : '' %> <& /elements/popup_link-cust_main.html, 'label' => emt('Enter credit'), 'action' => "${p}edit/cust_credit.cgi", @@ -70,7 +72,9 @@ 'actionlabel' => emt('Enter credit'), 'width' => 616, #make room for reasons #540 default &> - | +% } +% if ( $curuser->access_right('Credit line items') ) { + <% $s++ ? ' | ' : '' %> <& /elements/popup_link-cust_main.html, 'label' => emt('Credit line items'), #'action' => "${p}search/cust_bill_pkg.cgi?nottax=1;type=select", @@ -80,8 +84,8 @@ 'width' => 968, #763, 'height' => 575, &> - <BR> % } +<% $s ? '<BR>' : '' %> %# refund links @@ -224,57 +228,20 @@ %#display payment history -%my $money_char = $conf->config('money_char') || '$'; -% -%sub balance_forward_row { -% my( $b, $date, $money_char ) = @_; -% ( my $balance_forward = $money_char. $b ) =~ s/^\$\-/- \$/; - - <TR ID="balance_forward_row"> - <TD CLASS="grid" BGCOLOR="#dddddd"> - <% time2str($date_format, $date) %> - </TD> - - <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>) - </TD> - - <TD CLASS="grid" BGCOLOR="#dddddd"></TD> - <TD CLASS="grid" BGCOLOR="#dddddd"></TD> - <TD CLASS="grid" BGCOLOR="#dddddd"></TD> - <TD CLASS="grid" BGCOLOR="#dddddd"></TD> - <TD CLASS="grid" BGCOLOR="#dddddd" ALIGN="right"><I><% $balance_forward %></I></TD> - - </TR> -%} -% -%my $balance = 0; %my %target = (); % -%my $years = $conf->config('payment_history-years') || 2; -%my $older_than = time - $years * 31556926; #60*60*24*365.2422 %my $hidden = 0; %my $seen = 0; %my $old_history = 0; %my $lastdate = 0; % -%foreach my $item ( sort { $a->{'date'} <=> $b->{'date'} } @history ) { +%foreach my $item ( @history ) { % % $lastdate = $item->{'date'}; % -% my $display; -% if ( $item->{'date'} < $older_than ) { +% my $display = ''; +% if ( $item->{'hide'} ) { % $display = ' STYLE="display:none" '; -% $hidden = 1; -% } else { -% -% $display = ''; -% -% if ( $hidden && ! $seen++ ) { -% balance_forward_row($balance, $item->{'date'}, $money_char); -% } -% % } % % if ( $bgcolor eq $bgcolor1 ) { @@ -310,16 +277,8 @@ % % my $target = exists($item->{'target'}) ? $item->{'target'} : ''; % -% $balance += $item->{'charge'} if exists $item->{'charge'}; -% $balance -= $item->{'payment'} if exists $item->{'payment'}; -% $balance -= $item->{'credit'} if exists $item->{'credit'}; -% $balance += $item->{'refund'} if exists $item->{'refund'}; -% $balance = sprintf("%.2f", $balance); -% $balance =~ s/^\-0\.00$/0.00/; #yay ieee fp -% ( my $showbalance = $money_char. $balance ) =~ s/^\$\-/- \$/; -% -% - +% my $showbalance = $money_char . $item->{'balance'}; +% $showbalance =~ s/^\$\-/- \$/; <TR <% $display ? $display.' ID="old_history'.$old_history++.'"' : ''%>> <TD VALIGN="top" CLASS="grid" BGCOLOR="<% $bgcolor %>"> @@ -355,11 +314,11 @@ <% $showbalance %> </TD> </TR> -% } -%if ( scalar(@history) && $hidden && ! $seen++ ) { -% balance_forward_row($balance, $lastdate, $money_char); -%} +% if ( $item->{'balance_forward'} ) { +<& .balance_forward_row, $item->{'balance'}, $item->{'date'} &> +% } +%} # foreach $item </TABLE> </TD> @@ -382,14 +341,37 @@ function show_history () { } </SCRIPT> +<%def .balance_forward_row> +% my( $b, $date ) = @_; +% ( my $balance_forward = $money_char. $b ) =~ s/^\$\-/- \$/; -<%init> + <TR ID="balance_forward_row"> + <TD CLASS="grid" BGCOLOR="#dddddd"> + <% time2str($date_format, $date) %> + </TD> -my( $cust_main ) = @_; -my $custnum = $cust_main->custnum; + <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>) + </TD> + + <TD CLASS="grid" BGCOLOR="#dddddd"></TD> + <TD CLASS="grid" BGCOLOR="#dddddd"></TD> + <TD CLASS="grid" BGCOLOR="#dddddd"></TD> + <TD CLASS="grid" BGCOLOR="#dddddd"></TD> + <TD CLASS="grid" BGCOLOR="#dddddd" ALIGN="right"><I><% $balance_forward %></I></TD> + </TR> +</%def> +<%shared> my $conf = new FS::Conf; my $date_format = $conf->config('date_format') || '%m/%d/%Y'; +my $money_char = $conf->config('money_char') || '$'; +</%shared> +<%init> + +my( $cust_main ) = @_; +my $custnum = $cust_main->custnum; my $curuser = $FS::CurrentUser::CurrentUser; @@ -497,6 +479,17 @@ foreach my $cust_pay_pending ($cust_main->cust_pay_pending_attempt) { #'target' => $target, #XXX }; } +#declined batch payments +foreach my $cust_pay_batch ( + $cust_main->cust_pay_batch(hashref => {status => 'Declined'}) +) { + my $pay_batch = $cust_pay_batch->pay_batch; + push @history, { + 'date' => $pay_batch->upload, + 'desc' => include('payment_history/attempted_batch_payment.html', $cust_pay_batch, %opt), + 'void_payment' => $cust_pay_batch->amount, + }; +} #credits (some false laziness w/payments) foreach my $cust_credit ($cust_main->cust_credit) { @@ -518,6 +511,41 @@ foreach my $cust_refund ($cust_main->cust_refund) { } +# sort in forward order first, and calculate running balances +my $years = $conf->config('payment_history-years') || 2; +my $older_than = time - $years * 31556926; #60*60*24*365.2422 +my $balance = 0; + +@history = sort { $a->{date} <=> $b->{date} } @history; +my $i = 0; +my $balance_forward; +foreach my $item (@history) { + $balance += $item->{'charge'} if exists $item->{'charge'}; + $balance -= $item->{'payment'} if exists $item->{'payment'}; + $balance -= $item->{'credit'} if exists $item->{'credit'}; + $balance += $item->{'refund'} if exists $item->{'refund'}; + $balance = sprintf("%.2f", $balance); + $balance =~ s/^\-0\.00$/0.00/; + $item->{'balance'} = $balance; + + if ( $item->{'date'} < $older_than ) { + $item->{'hide'} = 1; + } elsif ( $history[$i-1]->{'hide'} ) { + # this is the end of the hidden section + $history[$i-1]->{'balance_forward'} = 1; + } + $i++; +} +if ( @history and $history[-1]->{'hide'} ) { + # then everything is hidden + $history[-1]->{'balance_forward'} = 1; +} + +# then sort in user-pref order +if ( $curuser->option('history_order') eq 'newest' ) { + @history = sort { $b->{date} <=> $a->{date} } @history; +} # else it's already oldest-first, and there are no other options yet + sub translate_payby { my ($payby,$payinfo) = (shift,shift); my %payby = ( diff --git a/httemplate/view/cust_main/payment_history/attempted_batch_payment.html b/httemplate/view/cust_main/payment_history/attempted_batch_payment.html new file mode 100644 index 000000000..95947f512 --- /dev/null +++ b/httemplate/view/cust_main/payment_history/attempted_batch_payment.html @@ -0,0 +1,13 @@ +<I><% mt('Payment attempt') |h %> <% $info |h %></I> +<%init> + +my( $cust_pay_batch, %opt ) = @_; + +my ($payby,$payinfo) = translate_payinfo($cust_pay_batch); +$payby = translate_payby($payby,$payinfo); +my $info = $payby ? "($payby$payinfo)" : ''; + +$info .= ': '. $cust_pay_batch->error_message + if length($cust_pay_batch->error_message); + +</%init> diff --git a/httemplate/view/elements/svc_Common.html b/httemplate/view/elements/svc_Common.html index f7c685c28..d735195fe 100644 --- a/httemplate/view/elements/svc_Common.html +++ b/httemplate/view/elements/svc_Common.html @@ -52,26 +52,39 @@ function areyousure(href) { <% mt('Service #') |h %><B><% $svcnum %></B> % my $url = $opt{'edit_url'} || $p. 'edit/'. $opt{'table'}. '.cgi?'; -| <& /view/elements/svc_edit_link.html, 'svc' => $svc_x, 'edit_url' => $url &> +<& /view/elements/svc_edit_link.html, 'svc' => $svc_x, 'edit_url' => $url &> <BR> <% ntable("#cccccc") %><TR><TD><% ntable("#cccccc",2) %> +% my @inventory_items = $svc_x->inventory_item; % foreach my $f ( @$fields ) { % -% my($field, $type, $value, $hack_strict_refs); +% my($field, $type, $value); % if ( ref($f) ) { % $field = $f->{'field'}; -% $hack_strict_refs = \&{ $f->{'value'} } if $f->{'value'}; -% $value = $f->{'value'} ? &$hack_strict_refs($svc_x) : $svc_x->$field; % $type = $f->{'type'} || 'text'; +% if ( $f->{'value_callback'} ) { +% my $hack_strict_refs = \&{ $f->{'value_callback'} }; +% $value = &$hack_strict_refs($svc_x); +% } else { +% $value = exists($f->{'value'}) ? $f->{'value'} : $svc_x->$field; +% } % } else { % $field = $f; -% $value = $svc_x->$field; % $type = 'text'; +% $value = $svc_x->$field; % } % % my $columndef = $part_svc->part_svc_column($field); +% if ( $columndef->columnflag =~ /^[MA]$/ && $columndef->columnvalue =~ /,/ ) +% { +% # inventory-select field with multiple classes +% # show the class name to disambiguate +% my ($item) = grep { $_->svc_field eq $field } @inventory_items; +% my $class = qsearchs('inventory_class', { classnum => $item->classnum }); +% $value .= ' <i>('. $class->classname . ')</i>' if $class; +% } % unless ($columndef->columnflag eq 'F' && !length($columndef->columnvalue)) { <TR> diff --git a/httemplate/view/elements/svc_devices.html b/httemplate/view/elements/svc_devices.html index d71c82f07..38c6d0919 100644 --- a/httemplate/view/elements/svc_devices.html +++ b/httemplate/view/elements/svc_devices.html @@ -12,91 +12,86 @@ ) </%doc> -<% $devices %> +%if ( @devices || $num_part_device || $table eq 'dsl_device' ) { +% my $svcnum = $svc_x->svcnum; + + Devices + (<A HREF="<%$p%>edit/<%$table%>.html?svcnum=<%$svcnum%>">Add device</A>) + <BR> + +% if ( @devices ) { + + <SCRIPT> + function areyousure(href) { + if (confirm("Are you sure you want to delete this device?") == true) + window.location.href = href; + } + </SCRIPT> + + <& /elements/table-grid.html &> + <TR> +% if ( $table eq 'phone_device' ) { + <TH CLASS="grid" BGCOLOR="#cccccc">Type</TH> +% } + <TH CLASS="grid" BGCOLOR="#cccccc">MAC Addr</TH> + <TH CLASS="grid" BGCOLOR="#cccccc"></TH> + <TH CLASS="grid" BGCOLOR="#cccccc"></TH> + </TR> + +% my $bgcolor1 = '#eeeeee'; +% my $bgcolor2 = '#ffffff'; +% my $bgcolor = ''; +% +% foreach my $device ( @devices ) { +% +% if ( $bgcolor eq $bgcolor1 ) { +% $bgcolor = $bgcolor2; +% } else { +% $bgcolor = $bgcolor1; +% } +% +% my $td = qq(<TD CLASS="grid" BGCOLOR="$bgcolor">); +% +% my $devicenum = $device->devicenum; +% my $export_links = ''; +% $export_links = join( '<BR>', @{ $device->export_links } ) +% if $device->can('export_links'); + + <TR> +% if ( $table eq 'phone_device' ) { #$devices->can('part_device') + <% $td %><% $device->part_device->devicename |h %></TD> +% } + <% $td %><% $device->mac_addr %></TD> + <% $td %><% $export_links %></TD> + <% $td %>( +% unless ( $opt{'no_edit'} ) { + <A HREF="<%$p%>edit/<%$table%>.html?<%$devicenum%>">edit</A> | +% } + <A HREF="javascript:areyousure('<%$p%>misc/delete-<%$table%>.html?<%$devicenum%>')">delete</A> + )</TD> + </TR> +% } + </TABLE> + <BR> + +% } + <BR> +%} <%init> - my %opt = @_; - my $table = $opt{'table'}; #part_device, dsl_device - my $svc_x = $opt{'svc_x'}; - - my $devices = ''; - - my $num_part_device = 0; - if ( $table eq 'phone_device' ) { - my $sth = dbh->prepare("SELECT COUNT(*) FROM part_device") - #WHERE disabled = '' OR disabled IS NULL;"); - or die dbh->errstr; - $sth->execute or die $sth->errstr; - $num_part_device = $sth->fetchrow_arrayref->[0]; +my %opt = @_; +my $table = $opt{'table'}; #part_device, dsl_device +my $svc_x = $opt{'svc_x'}; + +my $num_part_device = 0; +if ( $table eq 'phone_device' ) { + my $sth = dbh->prepare("SELECT COUNT(*) FROM part_device") + #WHERE disabled = '' OR disabled IS NULL;"); + or die dbh->errstr; + $sth->execute or die $sth->errstr; + $num_part_device = $sth->fetchrow_arrayref->[0]; } - my @devices = $svc_x->$table(); - - #should move the below to proper mason code above instead of making $devices - if ( @devices || $num_part_device || $table eq 'dsl_device' ) { - my $svcnum = $svc_x->svcnum; - $devices .= - qq[Devices (<A HREF="${p}edit/$table.html?svcnum=$svcnum">Add device</A>)<BR>]; - if ( @devices ) { - - $devices .= qq! - <SCRIPT> - function areyousure(href) { - if (confirm("Are you sure you want to delete this device?") == true) - window.location.href = href; - } - </SCRIPT> - !; - - - $devices .= - include('/elements/table-grid.html'). - '<TR>'; - - $devices .= - '<TH CLASS="grid" BGCOLOR="#cccccc">Type</TH>' - if $table eq 'phone_device'; - - $devices .= - '<TH CLASS="grid" BGCOLOR="#cccccc">MAC Addr</TH>'. - '<TH CLASS="grid" BGCOLOR="#cccccc"></TH>'. - '<TH CLASS="grid" BGCOLOR="#cccccc"></TH>'. - '</TR>'; - my $bgcolor1 = '#eeeeee'; - my $bgcolor2 = '#ffffff'; - my $bgcolor = ''; - - foreach my $device ( @devices ) { - - if ( $bgcolor eq $bgcolor1 ) { - $bgcolor = $bgcolor2; - } else { - $bgcolor = $bgcolor1; - } - my $td = qq(<TD CLASS="grid" BGCOLOR="$bgcolor">); - - my $devicenum = $device->devicenum; - my $export_links = join( '<BR>', @{ $device->export_links } ) - if $device->can('export_links'); - - $devices .= '<TR>'; - $devices .= $td. $device->part_device->devicename. '</TD>' - if $table eq 'phone_device'; #$devices->can('part_device'); - - $devices .= $td. $device->mac_addr. '</TD>'. - $td. $export_links. '</TD>'. - "$td( "; - - $devices .= qq(<A HREF="${p}edit/$table.html?$devicenum">edit</A> | ) - unless $opt{'no_edit'}; - - $devices .= qq(<A HREF="javascript:areyousure('${p}misc/delete-$table.html?$devicenum')">delete</A>). - ' )</TD>'. - '</TR>'; - } - $devices .= '</TABLE><BR>'; - } - $devices .= '<BR>'; - } +my @devices = $svc_x->$table(); </%init> diff --git a/httemplate/view/elements/svc_edit_link.html b/httemplate/view/elements/svc_edit_link.html index d65db0a8f..5438ed266 100644 --- a/httemplate/view/elements/svc_edit_link.html +++ b/httemplate/view/elements/svc_edit_link.html @@ -7,8 +7,12 @@ function areyousure_delete() { window.location.href = '<% $cancel_url %>'; } </SCRIPT> -<A HREF="<% $edit_url %>"><% mt("Edit this [_1]", $label) |h %></A> | -<A HREF="javascript:areyousure_delete()"><% mt('Unprovision this Service') |h %></A> +% 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') ) { +| <A HREF="javascript:areyousure_delete()"><% mt('Unprovision this Service') |h %></A> +% } % } <%init> my %opt = @_; @@ -20,4 +24,5 @@ my $cancel_url = $p . 'misc/unprovision.cgi?' . $svc_x->svcnum; my $cust_svc = $svc_x->cust_svc; # always exists my $cancel_date = $cust_svc->pkg_cancel_date; my ($label) = $cust_svc->label; +my $curuser = $FS::CurrentUser::CurrentUser; </%init> diff --git a/httemplate/view/elements/svc_export_status.html b/httemplate/view/elements/svc_export_status.html index d96bb277d..4ce869e27 100644 --- a/httemplate/view/elements/svc_export_status.html +++ b/httemplate/view/elements/svc_export_status.html @@ -7,7 +7,15 @@ % foreach my $key ( sort {$a cmp $b} keys %$hashref ) { <TR> <TD ALIGN="right"><% $key |h %></TD> - <TD BGCOLOR="#ffffff"><% $hashref->{$key} |h %></TD> + <TD BGCOLOR="#ffffff"> +% if ( ref($hashref->{$key}) eq 'ARRAY' ) { +% foreach (@{ $hashref->{$key} }) { + <% $_ |h %><BR> +% } +% } else { + <% $hashref->{$key} |h %> +% } + </TD> </TR> % } diff --git a/httemplate/view/quotation-pdf.cgi b/httemplate/view/quotation-pdf.cgi new file mode 100755 index 000000000..7f62ce173 --- /dev/null +++ b/httemplate/view/quotation-pdf.cgi @@ -0,0 +1,29 @@ +<% $content %>\ +<%init> + +#false laziness w/elements/cust_bill-typeset + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Generate quotation'); #View quotations ? + +my $quotationnum = $cgi->param('quotationnum'); + +my $conf = new FS::Conf; + +my $quotation = qsearchs({ + 'select' => 'quotation.*', + 'table' => 'quotation', + #'addl_from' => 'LEFT JOIN cust_main USING ( custnum )', + 'hashref' => { 'quotationnum' => $quotationnum }, + #'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql, +}); +die "Quotation #$quotationnum not found!" unless $quotation; + +my $content = $quotation->print_pdf(); #\%opt); + +http_header('Content-Type' => 'application/pdf'); +http_header('Content-Disposition' => "filename=$quotationnum.pdf" ); +http_header('Content-Length' => length($content) ); +http_header('Cache-control' => 'max-age=60' ); + +</%init> diff --git a/httemplate/view/svc_acct.cgi b/httemplate/view/svc_acct.cgi index 199591356..858ccbe67 100755 --- a/httemplate/view/svc_acct.cgi +++ b/httemplate/view/svc_acct.cgi @@ -22,6 +22,7 @@ % } + <& svc_acct/radius_usage.html, 'svc_acct' => $svc_acct, 'part_svc' => $part_svc, @@ -29,6 +30,7 @@ %gopt, &> + <& svc_acct/change_svc_form.html, 'part_svc' => \@part_svc, 'svcnum' => $svcnum, @@ -37,13 +39,15 @@ &> <% mt('Service #') |h %><B><% $svcnum %></B> -| <& /view/elements/svc_edit_link.html, 'svc' => $svc_acct &> <& svc_acct/change_svc.html, 'part_svc' => \@part_svc, %gopt, &> +</FORM> + + <& svc_acct/basics.html, 'svc_acct' => $svc_acct, 'part_svc' => $part_svc, @@ -90,8 +94,12 @@ die "access denied" my $addl_from = ' LEFT JOIN cust_svc USING ( svcnum ) '. ' LEFT JOIN cust_pkg USING ( pkgnum ) '. ' LEFT JOIN cust_main USING ( custnum ) '; - -my($query) = $cgi->keywords; +my $query; +if ( $cgi->keywords ) { + ($query) = $cgi->keywords; +} else { + $query = $cgi->param('svcnum'); +} $query =~ /^(\d+)$/; my $svcnum = $1; my $svc_acct = qsearchs({ diff --git a/httemplate/view/svc_acct/basics.html b/httemplate/view/svc_acct/basics.html index 2d9953fcc..04e7bcff8 100644 --- a/httemplate/view/svc_acct/basics.html +++ b/httemplate/view/svc_acct/basics.html @@ -20,7 +20,7 @@ % if ( $password =~ /^\*\w+\* (.*)$/ ) { % $password = $1; % $show_pw .= '<I>('. mt('login disabled') .')</I> '; -% } +% } % if ( ! $password % && $svc_acct->_password_encryption ne 'plain' % && $svc_acct->_password @@ -28,13 +28,27 @@ % { % $show_pw .= '<I>('. uc($svc_acct->_password_encryption). ' '.mt('encrypted').')</I>'; % } elsif ( $conf->exists('showpasswords') ) { -% $show_pw .= '<PRE>'. encode_entities($password). '</PRE>'; +% $show_pw .= '<SPAN >'. encode_entities($password). '</PRE>'; % } else { +% $password = ''; % $show_pw .= '<I>('. mt('hidden') .')</I>'; -% } -% $password = ''; -<& /view/elements/tr.html, label=>mt('Password'), value=>$show_pw &> - +% } +<TR> + <TD ALIGN="right"><% mt('Password') %></TD> + <TD STYLE="background-color: #ffffff; white-space: nowrap"> + <% $show_pw %> +% my $curuser = $FS::CurrentUser::CurrentUser; +% if ( $curuser->access_right('Provision customer service') or +% ($curuser->access_right('Edit password') and +% ! $part_svc->restrict_edit_password) ) +% { + <& /elements/change_password.html, + 'svc_acct' => $svc_acct, + 'curr_value' => $password, + &> +% } + </TD> +</TR> % if ( $conf->exists('security_phrase') ) { <& /view/elements/tr.html, label=>mt('Security phrase'), value=>$svc_acct->sec_phrase &> diff --git a/httemplate/view/svc_broadband.cgi b/httemplate/view/svc_broadband.cgi index 75e673c4f..7d6520e57 100644 --- a/httemplate/view/svc_broadband.cgi +++ b/httemplate/view/svc_broadband.cgi @@ -26,23 +26,31 @@ $labels{'coordinates'} = 'Latitude/Longitude'; my @fields = ( 'description', - { field => 'routernum', value => \&router }, + { field => 'routernum', value_callback => \&router }, 'speed_down', 'speed_up', - { field => 'ip_addr', value => \&ip_addr }, - { field => 'sectornum', value => \§ornum }, - { field => 'mac_addr', value => \&mac_addr }, + { field => 'ip_addr', value_callback => \&ip_addr }, + { field => 'sectornum', value_callback => \§ornum }, + { field => 'mac_addr', value_callback => \&mac_addr }, #'latitude', #'longitude', - { field => 'coordinates', value => \&coordinates }, + { field => 'coordinates', value_callback => \&coordinates }, 'altitude', + + 'radio_serialnum', + 'radio_location', + 'poe_location', + 'rssi', + 'suid', + { field => 'shared_svcnum', value_callback=> \&shared_svcnum, }, #value_callback => + 'vlan_profile', 'authkey', 'plan_id', ); push @fields, - { field => 'usergroup', value => \&usergroup } + { field => 'usergroup', value_callback => \&usergroup } if $conf->exists('svc_broadband-radius'); sub router { @@ -112,9 +120,36 @@ sub coordinates { ); } +sub shared_svcnum { + my $svc_broadband = shift; + return '' unless $svc_broadband->shared_svcnum; + + my $shared_svc_broadband = + qsearchs('svc_broadband', { 'svcnum' => $svc_broadband->shared_svcnum, + } + #agent virt? + ) + or return ''; + my $shared_cust_pkg = $shared_svc_broadband->cust_svc->cust_pkg; + + $shared_svc_broadband->label. + ( $shared_cust_pkg + ? ' ('. $shared_cust_pkg->cust_main->name. ')' + : '' + ); +} + sub svc_callback { # trying to move to the callback style my ($cgi, $svc_x, $part_svc, $cust_pkg, $fields, $opt) = @_; + + if ( $part_svc->part_svc_column('latitude')->columnflag eq 'F' + && $part_svc->part_svc_column('longitude')->columnflag eq 'F' + ) + { + @$fields = grep { !ref($_) || $_->{field} ne 'coordinates' } @$fields; + } + # again, we assume at most one of these exports per part_svc my ($nas_export) = $part_svc->part_export('broadband_nas'); if ( $nas_export ) { diff --git a/httemplate/view/svc_cert.cgi b/httemplate/view/svc_cert.cgi index 0cd66b422..964b808ab 100644 --- a/httemplate/view/svc_cert.cgi +++ b/httemplate/view/svc_cert.cgi @@ -17,7 +17,7 @@ my %labels = map { $_ => ( ref($fields->{$_}) my @fields = ( { field=>'privatekey', - value=> sub { + value_callback=> sub { my $svc_cert = shift; if ( $svc_cert->privatekey && $svc_cert->check_privatekey ) { '<FONT COLOR="#33ff33">Verification OK</FONT>'; @@ -31,7 +31,7 @@ my @fields = ( qw( common_name organization organization_unit city state country cert_contact ), { 'field'=>'csr', - 'value'=> sub { + 'value_callback'=> sub { my $svc_cert = shift; if ( $svc_cert->csr ) { @@ -67,7 +67,7 @@ my @fields = ( }, }, { 'field'=>'certificate', - 'value'=> sub { + 'value_callback'=> sub { my $svc_cert = shift; if ( $svc_cert->certificate ) { @@ -137,7 +137,7 @@ my @fields = ( }, }, { 'field'=>'cacert', - 'value'=> sub { + 'value_callback'=> sub { my $svc_cert = shift; if ( $svc_cert->cacert ) { diff --git a/httemplate/view/svc_hardware.cgi b/httemplate/view/svc_hardware.cgi index 7f5e889d8..eef1c1140 100644 --- a/httemplate/view/svc_hardware.cgi +++ b/httemplate/view/svc_hardware.cgi @@ -13,17 +13,20 @@ my %labels = map { $_ => ( ref($fields->{$_}) : $fields->{$_} ); } keys %$fields; + +$labels{'display_hw_addr'} = 'Hardware address'; + my $model = { field => 'typenum', type => 'text', - value => sub { $_[0]->hardware_type->description } + value_callback => sub { $_[0]->hardware_type->description } }; my $status = { field => 'statusnum', type => 'text', - value => sub { $_[0]->status_label } + value_callback => sub { $_[0]->status_label } }; my $note = { field => 'note', type => 'text', - value => sub { encode_entities($_[0]->note) } + value_callback => sub { encode_entities($_[0]->note) } }; my @fields = ( diff --git a/httemplate/view/svc_phone.cgi b/httemplate/view/svc_phone.cgi index 323be63dc..ed95c4cea 100644 --- a/httemplate/view/svc_phone.cgi +++ b/httemplate/view/svc_phone.cgi @@ -16,9 +16,20 @@ my %labels = map { $_ => ( ref($fields->{$_}) ); } keys %$fields; -my @fields = qw( countrycode phonenum ); +my @fields = qw( countrycode phonenum sim_imsi ); push @fields, 'domain' if $conf->exists('svc_phone-domain'); -push @fields, qw( pbx_title sip_password pin phone_name forwarddst email ); +push @fields, qw( pbx_title ); + +if ( $conf->exists('showpasswords') ) { + push @fields, qw( sip_password ); +} else { + push @fields, { 'field' => 'sip_password', #'_HIDDEN_sip_password', + 'type' => 'fixed', + 'value' => '<I>('. mt('hidden') .')</I>', + }; +} + +push @fields, qw( pin phone_name forwarddst email ); if ( $conf->exists('svc_phone-lnp') ) { push @fields, 'lnp_status', |