summaryrefslogtreecommitdiff
path: root/httemplate
diff options
context:
space:
mode:
Diffstat (limited to 'httemplate')
-rwxr-xr-xhttemplate/browse/part_pkg.cgi1
-rwxr-xr-xhttemplate/browse/part_svc.cgi2
-rwxr-xr-xhttemplate/edit/part_pkg.cgi21
-rw-r--r--httemplate/elements/email-link.html3
-rw-r--r--httemplate/elements/form-create_ticket.html2
-rw-r--r--httemplate/elements/menu.html2
-rw-r--r--httemplate/elements/select-tower_sector.html4
-rw-r--r--httemplate/graph/elements/report.html6
-rw-r--r--httemplate/misc/batch-cust_pay.html97
-rwxr-xr-xhttemplate/search/cust_bill.html4
-rw-r--r--httemplate/search/tax_sales.cgi172
-rwxr-xr-xhttemplate/search/tax_sales.html35
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>