diff options
Diffstat (limited to 'httemplate')
| -rwxr-xr-x | httemplate/browse/part_pkg.cgi | 1 | ||||
| -rwxr-xr-x | httemplate/browse/part_svc.cgi | 2 | ||||
| -rwxr-xr-x | httemplate/edit/part_pkg.cgi | 21 | ||||
| -rw-r--r-- | httemplate/elements/email-link.html | 3 | ||||
| -rw-r--r-- | httemplate/elements/form-create_ticket.html | 2 | ||||
| -rw-r--r-- | httemplate/elements/menu.html | 2 | ||||
| -rw-r--r-- | httemplate/elements/select-tower_sector.html | 4 | ||||
| -rw-r--r-- | httemplate/graph/elements/report.html | 6 | ||||
| -rw-r--r-- | httemplate/misc/batch-cust_pay.html | 97 | ||||
| -rwxr-xr-x | httemplate/search/cust_bill.html | 4 | ||||
| -rw-r--r-- | httemplate/search/tax_sales.cgi | 172 | ||||
| -rwxr-xr-x | httemplate/search/tax_sales.html | 35 |
12 files changed, 307 insertions, 42 deletions
diff --git a/httemplate/browse/part_pkg.cgi b/httemplate/browse/part_pkg.cgi index 2769f8512..11eca6702 100755 --- a/httemplate/browse/part_pkg.cgi +++ b/httemplate/browse/part_pkg.cgi @@ -537,6 +537,7 @@ push @fields, }, ]; } + sort grep { $options{$_} =~ /\S/ } grep { $_ !~ /^(setup|recur)_fee$/ and $_ !~ /^report_option_\d+$/ } diff --git a/httemplate/browse/part_svc.cgi b/httemplate/browse/part_svc.cgi index ec5f321dd..88f8d8d19 100755 --- a/httemplate/browse/part_svc.cgi +++ b/httemplate/browse/part_svc.cgi @@ -161,7 +161,7 @@ function part_export_areyousure(href) { % } % % my($n1)=''; -% foreach my $field ( @fields ) { +% foreach my $field ( sort @fields ) { % % #a few lines of false laziness w/edit/part_svc.cgi % my $def = FS::part_svc->svc_table_fields($svcdb)->{$field}; diff --git a/httemplate/edit/part_pkg.cgi b/httemplate/edit/part_pkg.cgi index bdceb3364..2802ddc33 100755 --- a/httemplate/edit/part_pkg.cgi +++ b/httemplate/edit/part_pkg.cgi @@ -866,9 +866,13 @@ my $html_bottom = sub { #$html .= '</SELECT></TD></TR>'; my $href = $plans{$layer}->{'fields'}; - my @fields = exists($plans{$layer}->{'fieldorder'}) - ? @{$plans{$layer}->{'fieldorder'}} - : keys %{ $href }; + my @fields; + if ( $plans{$layer}->{'fieldorder'} ) { + @fields = @{ $plans{$layer}->{'fieldorder'} }; + } else { + warn "FS::part_pkg::$layer has no fieldorder.\n"; + @fields = keys %$href; + } # hash of dependencies for each of the Pricing Plan fields. # make sure NOT to use double-quotes inside the 'msg' value. @@ -906,7 +910,8 @@ my $html_bottom = sub { next if !$display; } - $html .= '<TR><TD ALIGN="right">'. $href->{$field}{'name'}. '</TD><TD>'; + $html .= '<TR><TD ALIGN="right">'. $href->{$field}{'name'}. '</TD><TD> + '; my $format = sub { shift }; $format = $href->{$field}{'format'} if exists($href->{$field}{'format'}); @@ -1005,9 +1010,11 @@ my $html_bottom = sub { $html .= '</TD></TR>'; } $html .= '</TABLE>'; - - $html .= qq(<INPUT TYPE="hidden" NAME="${layer}__OPTIONS" VALUE="). - join(',', keys %{ $href } ). '">'; + + $html .= include('/elements/hidden.html', + field => $layer.'__OPTIONS', + value => join(',', @fields) + ); $html; diff --git a/httemplate/elements/email-link.html b/httemplate/elements/email-link.html index 2612faabb..16935cf98 100644 --- a/httemplate/elements/email-link.html +++ b/httemplate/elements/email-link.html @@ -10,7 +10,8 @@ die "'table' required" if !$table; die "'search_hash' required" if !$search_hash; my $uri = new URI; -$uri->query_form($search_hash); +my @params = map { $_, $search_hash->{$_} } sort keys %$search_hash; +$uri->query_form(@params); my $query = $uri->query; my $label = ($opt{'label'} || 'Email a notice to these customers'); </%init> diff --git a/httemplate/elements/form-create_ticket.html b/httemplate/elements/form-create_ticket.html index 362e82397..d76c0d83e 100644 --- a/httemplate/elements/form-create_ticket.html +++ b/httemplate/elements/form-create_ticket.html @@ -6,7 +6,7 @@ function updateTicketLink() { link.href = "<% $new_base.'?'. join(';', map( { ($_ eq 'Queue') ? () : "$_=$new_param{$_}"} - keys %new_param),'Queue=') %>" + selector.options[selector.selectedIndex].value; + sort keys %new_param),'Queue=') %>" + selector.options[selector.selectedIndex].value; } </SCRIPT> <A NAME="tickets"><FONT CLASS="fsinnerbox-title">Tickets</FONT></A> diff --git a/httemplate/elements/menu.html b/httemplate/elements/menu.html index f96c05ea5..efd9033db 100644 --- a/httemplate/elements/menu.html +++ b/httemplate/elements/menu.html @@ -389,6 +389,8 @@ if( $curuser->access_right('Financial reports') ) { $report_financial{'Tax Liability (vendor tax data)'} = [ $fsurl.'search/report_newtax.html', 'Tax liability report (vendor tax data)' ] if $taxproducts; + $report_financial{'Monthly Sales and Taxes'} = [$fsurl.'search/tax_sales.html', 'Monthly sales and taxes report']; + # most sites don't need this but there isn't really a config to enable it $report_financial{'E911 Fee Summary'} = [ $fsurl.'search/report_e911.html', 'E911 fee summary' ]; diff --git a/httemplate/elements/select-tower_sector.html b/httemplate/elements/select-tower_sector.html index a16d3bfa0..59b016359 100644 --- a/httemplate/elements/select-tower_sector.html +++ b/httemplate/elements/select-tower_sector.html @@ -12,7 +12,7 @@ table => 'tower', name_col => 'towername', id => 'towernum', - field => 'dummy_towernum', + field => 'towernum', onchange => 'change_towernum(this.value);', element_etc => 'STYLE="vertical-align:top"', &> @@ -63,5 +63,5 @@ foreach my $towernum (keys %sectors_of) { } } -my $empty_label = $opt{'empty_label'} || 'Include services with no tower/sector'; +my $empty_label = $opt{'empty_label'} || 'Include services with no sector'; </%init> diff --git a/httemplate/graph/elements/report.html b/httemplate/graph/elements/report.html index f1b0d166d..b5d214816 100644 --- a/httemplate/graph/elements/report.html +++ b/httemplate/graph/elements/report.html @@ -11,6 +11,7 @@ Example: #these run parallel to items, and can be given as hashes 'row_labels' => \@row_labels, #required 'colors' => \@colors, #required + 'bgcolors' => \@bgcolors, #optional 'graph_labels' => \@graph_labels, #defaults to row_labels 'links' => \@links, #optional @@ -22,7 +23,7 @@ Example: #optional 'nototal' => 1, - 'graph_type' => 'LinesPoints', + 'graph_type' => 'LinesPoints', #can be 'none' for no graph 'bottom_total' => 1, 'sprintf' => '%u', #sprintf format, overrides default %.2f 'disable_money' => 1, @@ -231,7 +232,8 @@ any delimiter and linked from the elements in @data. % foreach my $row ( @items ) { % #make a style % my $color = shift @{ $opt{'colors'} }; -% push @styles, ".i$i { text-align: right; color: #$color; }"; +% my $bgcolor = $opt{'bgcolors'} ? (shift @{ $opt{'bgcolors'} }) : 'ffffff'; +% push @styles, ".i$i { text-align: right; color: #$color; background: #$bgcolor; }"; % #create the data row % my $links = shift @{$opt{'links'}} || ['']; % my $link_prefix = shift @$links; diff --git a/httemplate/misc/batch-cust_pay.html b/httemplate/misc/batch-cust_pay.html index 9f2540cc7..197ade14f 100644 --- a/httemplate/misc/batch-cust_pay.html +++ b/httemplate/misc/batch-cust_pay.html @@ -101,6 +101,10 @@ function select_discount_term(row) { var invoices_for_row = new Object; +var preloading = 0; // the number of preloading threads currently running + +// callback from toggle_application_row: we've received a list of +// the customer's open invoices. store them. function update_invoices(rownum, invoices) { invoices_for_row[rownum] = new Object; // only called before create_application_row @@ -113,6 +117,12 @@ function toggle_application_row(ev, next) { if (!next) next = function(){}; //optional continuation var rownum = this.getAttribute('rownum'); if ( this.checked ) { + // the user has opted to apply the payment to specific invoices. + // - lock the customer + // - fetch the list of open invoices + // - create a row to select an invoice + // - then optionally call "next", with this as the invocant + // and the rownum as argument; we use this to preload rows. var custnum = document.getElementById('custnum'+rownum).value; if (!custnum) return; lock_payment_row(rownum, true); @@ -124,6 +134,9 @@ function toggle_application_row(ev, next) { } ); } else { + // the user has opted not to do that. + // - remove all application rows + // - unlock the customer var row = document.getElementById('row'+rownum); var table_rows = row.parentNode.rows; for (i = row.sectionRowIndex; i < table_rows.count; i++) { @@ -183,6 +196,16 @@ function amount_unapplied(rownum) { var change_app_amount; +// the user has chosen an invoice. the previously chosen invoice is still +// in curr_invoice +// - if there is a value there, put it back on the invoices_for_row list for +// this customer. +// - then _remove_ the newly chosen invoice from that list. +// - find the "owed" element for this application row and set its value to the +// amount owed on that invoice. +// - find the "amount" element for this application row and set its value to +// either "owed" or the remaining payment amount, whichever is less. +// - call change_app_amount() on that element. function choose_app_invnum() { var rownum = this.getAttribute('rownum'); var appnum = this.getAttribute('appnum'); @@ -210,8 +233,10 @@ function choose_app_invnum() { } } +// the invoice selector has gained focus. clear its list of options, and +// replace them with the list of open invoices (from invoices_for_row). +// if there's already a selected invoice, prepend that to the list. function focus_app_invnum() { -% # invoice numbers just display as invoice numbers var rownum = this.getAttribute('rownum'); var add_opt = function(obj, value, label) { var o = document.createElement('OPTION'); @@ -233,14 +258,15 @@ function focus_app_invnum() { } } +// an application amount has been changed. if there's any unapplied payment +// amount, and any remaining invoices_for_row, add a blank application row. +// (but don't do this while preloading; it will unconditionally add enough +// rows to show all the attempted applications) function change_app_amount() { var rownum = this.getAttribute('rownum'); var appnum = this.getAttribute('appnum'); -%# maybe some kind of warning if amount_unapplied < 0? -%# only spawn a new application row if there are open invoices left, -%# and this is the highest-numbered application row for the customer, -%# and the sum of the applied amounts is < the amount of the payment, - if ( Object.keys(invoices_for_row[rownum]).length > 0 + if ( preloading == 0 + && Object.keys(invoices_for_row[rownum]).length > 0 && !document.getElementById( 'row'+rownum+'.'+(parseInt(appnum) + 1) ) && amount_unapplied(rownum) > 0 ) { @@ -248,6 +274,9 @@ function change_app_amount() { } } +// we're creating a payment application row. +// create the following elements: <TR>, <TD>s, "Apply to invoice" caption, +// invnum selector, "owed" display, amount input box, delete button. function create_application_row(rownum, appnum) { var payment_row = document.getElementById('row'+rownum); var tr_app = document.createElement('TR'); @@ -341,29 +370,45 @@ function preload() { var enable = document.getElementById('enable_app'+rownum); enable.checked = true; var preload_row = function(r) {//continuation from toggle_application_row - for (appnum=0; appnum < row_obj[r].length; appnum++) { - this_app = row_obj[r][appnum]; - var x = r + '.' + appnum; - //set invnum - var select_invnum = document.getElementById('invnum'+x); - focus_app_invnum.call(select_invnum); - for (i=0; i<select_invnum.options.length; i++) { - if (select_invnum.options[i].value == this_app.invnum) { - select_invnum.selectedIndex = i; + + preloading++; + + try { + for (appnum=0; appnum < row_obj[r].length; appnum++) { + this_app = row_obj[r][appnum]; + var x = r + '.' + appnum; + //set invnum + var select_invnum = document.getElementById('invnum'+x); + focus_app_invnum.call(select_invnum); + for (i=0; i<select_invnum.options.length; i++) { + if (select_invnum.options[i].value == this_app.invnum) { + select_invnum.selectedIndex = i; + } } - } - choose_app_invnum.call(select_invnum); - //set amount - var input_amount = document.getElementById('amount'+x); - input_amount.value = this_app.amount; - - //set error - var span_error = document.getElementById('error'+x); - span_error.innerHTML = this_app.error; - change_app_amount.call(input_amount); //creates next row - } //for appnum + choose_app_invnum.call(select_invnum); + //set amount + var input_amount = document.getElementById('amount'+x); + input_amount.value = this_app.amount; + + //set error + var span_error = document.getElementById('error'+x); + span_error.innerHTML = this_app.error; + + // create another row (unconditionally) + create_application_row(r, appnum + 1); + + } //for appnum + + } finally { + preloading--; + } + }; //preload_row function + + // enable application rows on the selected customer. this creates + // the first row, then kicks off preloading. toggle_application_row.call(enable, null, preload_row); + } // if (row_obj[rownum].length } //for rownum } diff --git a/httemplate/search/cust_bill.html b/httemplate/search/cust_bill.html index 62f5f7afa..88e291b5b 100755 --- a/httemplate/search/cust_bill.html +++ b/httemplate/search/cust_bill.html @@ -197,7 +197,7 @@ my $html_init = join("\n", map { ( my $action = $_ ) =~ s/_$//; include('/elements/progress-init.html', $_.'form', - [ keys %search ], + [ sort keys %search ], "../misc/${_}invoices.cgi", { 'message' => "Invoices re-${action}ed" }, #would be nice to show the number of them, but... $_, #key @@ -207,7 +207,7 @@ my $html_init = join("\n", map { my @values = ref($search{$f}) ? @{ $search{$f} } : $search{$f}; map qq!<INPUT TYPE="hidden" NAME="$f" VALUE="$_">!, @values; } - keys %search + sort keys %search ), qq!</FORM>! } qw( print_ email_ fax_ ftp_ spool_ ) ). diff --git a/httemplate/search/tax_sales.cgi b/httemplate/search/tax_sales.cgi new file mode 100644 index 000000000..4b28c934a --- /dev/null +++ b/httemplate/search/tax_sales.cgi @@ -0,0 +1,172 @@ + +<% include('/graph/elements/report.html', + 'title' => 'Monthly Sales and Taxes Report', + 'items' => \@row_labels, + 'data' => \@rowdata, + 'row_labels' => \@row_labels, + 'colors' => \@rowcolors, + 'bgcolors' => \@rowbgcolors, + 'col_labels' => \@col_labels, + 'graph_type' => 'none', + ) %> + +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Financial reports'); + +# validate cgi input +my $start_month = $cgi->param('start_month'); +die "Bad start month" unless $start_month =~ /^\d*$/; +my $start_year = $cgi->param('start_year'); +die "Bad start year" unless $start_year =~ /^\d*$/; +my $end_month = $cgi->param('end_month'); +die "Bad end month" unless $end_month =~ /^\d*$/; +my $end_year = $cgi->param('end_year'); +die "Bad end year" unless $end_year =~ /^\d*$/; +die "End year before start year" if $end_year < $start_year; +die "End month before start month" if ($start_year == $end_year) && ($end_month < $start_month); +my $country = $cgi->param('country'); +die "Bad country code" unless $country =~ /^\w\w$/; + +# Data structure for building final table +# row order will be calculated separately +# +# $data->{$rowlabel} = \@rowvalues +# + +my $data = {}; + +### Calculate package values + +my @pkg_class = qsearch('pkg_class'); +my @pkg_classnum = map { $_->classnum } @pkg_class; +unshift(@pkg_classnum,0); +my @pkg_classname = map { $_->classname } @pkg_class; +unshift(@pkg_classname,'(empty class)'); + +# some false laziness with graph/elements/monthly.html +my %reportopts = ( + 'items' => [ qw( cust_bill_pkg cust_bill_pkg_credits ) ], + 'cross_params' => [ map { [ 'classnum', $_ ] } @pkg_classnum ], + 'start_month' => $start_month, + 'start_year' => $start_year, + 'end_month' => $end_month, + 'end_year' => $end_year, +); +my $pkgreport = new FS::Report::Table::Monthly(%reportopts); +my $pkgdata = $pkgreport->data; + +# assuming every month/year combo is included in results, +# just use this list for the final table +my @col_labels = @{$pkgdata->{'label'}}; + +# unpack report data into a more manageable format +foreach my $item ( qw( invoiced credited ) ) { # invoiced, credited + my $itemref = shift @{$pkgdata->{'data'}}; + foreach my $label (@{$pkgdata->{'label'}}) { # month/year + my $labelref = shift @$itemref; + foreach my $classname (@pkg_classname) { # pkg class + my $value = shift @$labelref; + my $rowlabel = $classname.' '.$item; + $data->{$rowlabel} ||= []; + push(@{$data->{$rowlabel}},$value); + } + } +} + +### Calculate tax values + +# false laziness w report_tax.html, put this in FS::Report::Tax? +my $sth = dbh->prepare('SELECT DISTINCT(COALESCE(taxname, \'Tax\')) FROM cust_main_county'); +$sth->execute or die $sth->errstr; +my @taxnames = map { $_->[0] } @{ $sth->fetchall_arrayref }; +$sth->finish; + +# get DateTime objects for start & end +my $startdate = DateTime->new( + year => $start_year, + month => $start_month, + day => 1 + ); +my $enddate = DateTime->new( + year => $end_year, + month => $end_month, + day => 1 + ); +$enddate->add( months => 1 )->subtract( seconds => 1 ); # the last second of the month + +# common to all tax reports +my %params = ( + 'country' => $country, + 'credit_date' => 'cust_bill', +); + +# run a report for each month, for each tax +my $countdate = $startdate->clone; +while ($countdate < $enddate) { + + # set report start date, iterate to end of this month, set report end date + $params{'beginning'} = $countdate->epoch; + $params{'ending'} = $countdate->add( months => 1 )->subtract( seconds => 1 )->epoch; + + # run a report for each tax name + foreach my $taxname (@taxnames) { + $params{'taxname'} = $taxname; + my $report = FS::Report::Tax->report_internal(%params); + + # extract totals from report, kinda awkward + my $pkgclass = ''; # this will get more complicated if we breakdown by pkgclass + my @values = (0,0); + if ($report->{'total'}->{$pkgclass}) { + my %totals = map { $$_[0] => $$_[2] } @{$report->{'total'}->{$pkgclass}}; + $values[0] = $totals{'tax'}; + $values[1] = $totals{'credit'}; + } + + # treat each tax class like it's an additional pkg class + foreach my $item ( qw ( invoiced credited ) ) { + my $rowlabel = $taxname . ' ' . $item; + my $value = shift @values; + $data->{$rowlabel} ||= []; + push(@{$data->{$rowlabel}},$value); + } + + } + + # iterate to next month + $countdate->add( seconds => 1 ); +} + +# put the data in the order we want it +my @row_labels; +my @rowdata; +my @rowcolors; +my @rowbgcolors; +my $pkgcount = 0; #for colors +foreach my $classname (@pkg_classname,@taxnames) { + my $istax = ($pkgcount++ < @pkg_classname) ? 0 : 1; + my @classlabels = (); + my @classdata = (); + my @classcolors = (); + my @classbgcolors = (); + my $hasdata = 0; + foreach my $item ( qw( invoiced credited ) ) { + my $rowlabel = $classname . ' ' . $item; + my $rowdata = $data->{$rowlabel}; + my $rowcolor = $istax ? '0000ff' : '000000'; + my $rowbgcolor = ($item eq 'credited') ? 'cccccc' : 'ffffff'; + $hasdata = 1 if grep { $_ } @$rowdata; + push(@classlabels,$rowlabel); + push(@classdata,$rowdata); + push(@classcolors,$rowcolor); + push(@classbgcolors,$rowbgcolor); + } + next unless $hasdata; # don't include class if it has no data in time range + push(@row_labels,@classlabels); + push(@rowdata,@classdata); + push(@rowcolors,@classcolors); + push(@rowbgcolors,@classbgcolors); +} + +</%init> diff --git a/httemplate/search/tax_sales.html b/httemplate/search/tax_sales.html new file mode 100755 index 000000000..61cf86e2e --- /dev/null +++ b/httemplate/search/tax_sales.html @@ -0,0 +1,35 @@ +<% include('/elements/header.html', 'Monthly Sales and Taxes Report' ) %> + +<FORM ACTION="tax_sales.cgi" METHOD="GET"> + +<TABLE> + + <% include('/elements/tr-select-from_to.html') %> + + <% include('/elements/tr-select.html', + 'label' => 'Country', + 'field' => 'country', + 'options' => \@countries, + 'curr_value' => ($conf->config('countrydefault') || 'US'), + ) %> + +</TABLE> + +<BR><INPUT TYPE="submit" VALUE="Get Report"> + +</FORM> + +<% include('/elements/footer.html') %> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Financial reports'); + +my $conf = new FS::Conf; + +# false laziness w report_tax.html, put this in FS::Report::Tax? +my $sth = dbh->prepare('SELECT DISTINCT(country) FROM cust_location'); +$sth->execute or die $sth->errstr; +my @countries = map { $_->[0] } @{ $sth->fetchall_arrayref }; + +</%init> |
