diff options
author | Ivan Kohler <ivan@freeside.biz> | 2013-10-08 23:00:26 -0700 |
---|---|---|
committer | Ivan Kohler <ivan@freeside.biz> | 2013-10-08 23:00:26 -0700 |
commit | fe4515eb37d76849dd08c62782d86bc7ba311dcd (patch) | |
tree | 6952cc3598de0c72b6a3eab1d53bde07a16c27f2 /httemplate | |
parent | f2766e203e1aa144d046a26cf13e01e1f5b00f64 (diff) | |
parent | 81ae0992cf8506c6a77485548ebde25eb946a9a9 (diff) |
Merge branch 'master' of git.freeside.biz:/home/git/freeside
Conflicts:
FS/FS/cust_main.pm
Diffstat (limited to 'httemplate')
26 files changed, 815 insertions, 25 deletions
diff --git a/httemplate/browse/cable_provider.html b/httemplate/browse/cable_provider.html new file mode 100644 index 000000000..0d344984b --- /dev/null +++ b/httemplate/browse/cable_provider.html @@ -0,0 +1,32 @@ +<& elements/browse.html, + 'title' => 'Cable providers', + 'html_init' => $html_init, + 'name' => 'providers', + 'disableable' => 1, + 'disabled_statuspos' => 1, + 'query' => { 'table' => 'cable_provider', + 'hashref' => {}, + 'order_by' => 'ORDER BY provider', + }, + 'count_query' => $count_query, + 'header' => $header, + 'fields' => $fields, + 'links' => $links, +&> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); + +my $html_init = + qq!<A HREF="${p}edit/cable_provider.html"><I>Add a provider</I></A><BR><BR>!; + +my $count_query = 'SELECT COUNT(*) FROM cable_provider'; + +my $link = [ $p.'edit/cable_provider.html?', 'providernum' ]; + +my $header = [ 'Provider' ]; +my $fields = [ 'provider' ]; +my $links = [ $link ]; + +</%init> diff --git a/httemplate/browse/invoice_conf.html b/httemplate/browse/invoice_conf.html new file mode 100644 index 000000000..c8fd1bffb --- /dev/null +++ b/httemplate/browse/invoice_conf.html @@ -0,0 +1,70 @@ +<& elements/browse.html, + 'title' => 'Invoice modes', + 'name_singular' => 'configuration', + 'menubar' => \@menubar, + 'query' => { + 'select' => $select, + 'table' => 'invoice_conf', + 'addl_from' => ' JOIN invoice_mode USING (modenum)', + 'extra_sql' => ' WHERE '.$curuser->agentnums_sql( + 'table' => 'invoice_mode', + 'null_right' => ['Edit global templates'], + ), + 'order_by' => q( ORDER BY modename asc, COALESCE(locale,'') asc), + }, + 'count_query' => 'SELECT COUNT(*) FROM invoice_conf JOIN invoice_mode USING (modenum)', + 'header' => [ 'Name', 'Agent', 'Locale', 'Overrides', ], + 'fields' => [ $modename, + $agent, + $locale_label, + $overrides, + ], + 'align' => 'llcl', + 'links' => [ '', '', $link ], + 'disable_maxselect' => 1, +&> +<%init> + +my $curuser = $FS::CurrentUser::CurrentUser; + +die "access denied" + unless $curuser->access_right([ 'View templates', 'View global templates', + 'Edit templates', 'Edit global templates', ]); + +my @overrides = grep {$_ ne 'modenum' and $_ ne 'confnum'} FS::invoice_conf->fields; +my $select = join(',', 'modename', 'agentnum', 'confnum', 'invoice_conf.*'); + +my @menubar = (); +if ( $curuser->access_right(['Edit templates', 'Edit global templates']) ) { + push @menubar, 'Add a new invoice mode' => $p.'edit/invoice_conf.html'; +} + +my $locale_style = 'font-size:0.8em; padding:3px; background-color:'; + +my $last_modenum = 0; +my $modename = sub { + return '' if $_[0]->modenum == $last_modenum; + $_[0]->modename; +}; + +my $agent = sub { + return '' if $_[0]->modenum == $last_modenum; + $last_modenum = $_[0]->modenum; + $_[0]->agentnum ? FS::agent->by_key($_[0]->agentnum)->agent : '(global)'; +}; + +my $locale_label = sub { + my $l = $_[0]->locale; + $l ? +{ FS::Locales->locale_info($l) }->{'label'} : '(default)'; +}; + +my $overrides = sub { + my $invoice_conf = shift; + [ map { [ { data => $_ } ] } + grep { length $invoice_conf->get($_) } + @overrides + ], +}; + +my $link = [ $p.'edit/invoice_conf.html?', 'confnum' ]; +</%init> diff --git a/httemplate/docs/about.html b/httemplate/docs/about.html index c2ba4e4a0..80d9488b6 100644 --- a/httemplate/docs/about.html +++ b/httemplate/docs/about.html @@ -56,7 +56,7 @@ GNU <b>Affero</b> General Public License.<BR> % unless ( $agentnum ) { <CENTER> - <FONT SIZE="-3">"I can't figure out ... if it's an end or the beginning" - R. Hunter</FONT> + <FONT SIZE="-3">"" - R. Hunter</FONT> </CENTER> % } diff --git a/httemplate/edit/cable_provider.html b/httemplate/edit/cable_provider.html new file mode 100644 index 000000000..9a911ccfa --- /dev/null +++ b/httemplate/edit/cable_provider.html @@ -0,0 +1,20 @@ +<& elements/edit.html, + 'name_singular' => 'Provider', + 'table' => 'cable_provider', + 'fields' => [ + 'provider', + { field=>'disabled', type=>'checkbox', value=>'Y', }, + ], + 'labels' => { + 'providernum' => 'Provider', + 'provider' => 'Provider', + 'disabled' => 'Disabled', + }, + 'viewall_dir' => 'browse', +&> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); + +</%init> diff --git a/httemplate/edit/cust_main/bottomfixup.js b/httemplate/edit/cust_main/bottomfixup.js index 9e18fa0df..ecfcb3cbc 100644 --- a/httemplate/edit/cust_main/bottomfixup.js +++ b/httemplate/edit/cust_main/bottomfixup.js @@ -10,17 +10,20 @@ my @fixups = ('copy_payby_fields', 'standardize_locations'); push @fixups, 'confirm_censustract' if $conf->exists('cust_main-require_censustract'); -# currently doesn't work; disable to avoid problems -#push @fixups, 'check_unique' -# if $conf->exists('cust_main-check_unique') and !$opt{'custnum'}; +my $uniqueness = $conf->config('cust_main-check_unique'); +push @fixups, 'check_unique' + if $uniqueness and !$opt{'custnum'}; push @fixups, 'do_submit'; # always last </%init> - var fixups = <% encode_json(\@fixups) %>; var fixup_position; var running = false; +<&| /elements/onload.js &> +submit_abort(); +</&> + %# state machine to deal with all the asynchronous stuff we're doing %# call this after each fixup on success: function submit_continue() { @@ -132,10 +135,14 @@ function set_censustract(tract, year) { } function check_unique() { - var search_hash = new Object; -% foreach ($conf->config('cust_main-check_unique')) { - search_hash['<% $_ %>'] = document.CustomerForm.elements['<% $_ %>'].value; + var search_hash = {}; +% if ($uniqueness eq 'address') { + search_hash['address'] = [ + document.CustomerForm.elements['bill_address1'].value, + document.CustomerForm.elements['ship_address1'].value + ]; % } +%# no other options yet %# supported in IE8+, Firefox 3.5+, WebKit, Opera 10.5+ duplicates_form(JSON.stringify(search_hash), confirm_unique); diff --git a/httemplate/edit/cust_main/top_misc.html b/httemplate/edit/cust_main/top_misc.html index ebd9b927c..e25506f52 100644 --- a/httemplate/edit/cust_main/top_misc.html +++ b/httemplate/edit/cust_main/top_misc.html @@ -154,9 +154,10 @@ % } else { - <& /elements/tr-select-part_referral.html, - 'curr_value' => $refnum - &> + <& /elements/tr-select-part_referral.html, + 'curr_value' => $refnum, + 'label' => "<B>${r}".emt('Advertising source')."</B>" + &> % } diff --git a/httemplate/edit/elements/edit.html b/httemplate/edit/elements/edit.html index 060281115..6c965326b 100644 --- a/httemplate/edit/elements/edit.html +++ b/httemplate/edit/elements/edit.html @@ -338,6 +338,7 @@ Example: % qw( width height config ), #htmlarea % qw( alt_format ), #select-cust_location % qw( classnum ), # select-inventory_item +% qw( aligned ), # columnstart % ; % % #select-table diff --git a/httemplate/edit/invoice_conf.html b/httemplate/edit/invoice_conf.html new file mode 100644 index 000000000..b7b3a4ebc --- /dev/null +++ b/httemplate/edit/invoice_conf.html @@ -0,0 +1,296 @@ +<& elements/edit.html, + 'body_etc' => $body_etc, + 'name_singular' => 'invoice configuration', + 'table' => 'invoice_conf', + 'viewall_dir' => 'browse', + 'fields' => \@fields, + 'labels' => \%labels, + 'new_callback' => \&new_callback, + 'edit_callback' => \&edit_callback, + 'error_callback' => \&error_callback, + 'html_init' => \&html_init, + 'html_table_bottom' => \&html_table_bottom, + 'html_bottom' => '</DIV>', # close tablebreak-tabs +&> +<%init> + +my $curuser = $FS::CurrentUser::CurrentUser; + +# ??? +die "access denied" + unless $curuser->access_right([ 'Edit templates', 'Edit global templates' ]); + +my $body_etc = ''; +$body_etc = q!onload="document.getElementById('locale').onchange()"! + if $cgi->param('locale') eq 'new'; + +my $modenum = $cgi->param('modenum'); +my $mode = $modenum ? qsearchs('invoice_mode', { modenum => $modenum }) : ''; + +my %textarea = (type => 'textarea', rows => 10, cols => 40); +my @fields = ( + { field => 'modenum', type => 'hidden' }, + { field => 'agentnum', + type => 'select-agent', + }, + { field => 'modename', size=>60, }, + { type => 'tablebreak-tabs', + include_opt_callback => \&menubar_opt_callback, + }, + { field => 'locale', type => 'hidden' }, + { field => 'notice_name', size=>60, }, + { field => 'subject', size=>60, }, + { field => 'lpr', size=>60, }, + + { type => 'columnstart', aligned => 1 }, + { type => 'title', value => '<BR>' }, + map ( { +{ type => 'justtitle', value => $_ } } + 'Notes', + 'Footer', + 'Summary header', + 'Return address', + 'Coupon', + 'Small footer', + 'Top margin', + 'Header separation', + 'Address separation', + 'Text height', + 'Coupon height', + 'Footer separation', + ), + + { type => 'columnnext' }, + { type => 'title', value => 'LaTeX' }, + { field => 'latexnotes', %textarea }, + { field => 'latexfooter', %textarea }, + { field => 'latexsummary', %textarea }, + { field => 'latexreturnaddress', %textarea }, + { field => 'latexcoupon', %textarea }, + { field => 'latexsmallfooter', %textarea }, + { field => 'latextopmargin', size => 16 }, + { field => 'latexheadsep', size => 16 }, + { field => 'latexaddresssep', size => 16 }, + { field => 'latextextheight', size => 16 }, + { field => 'latexextracouponspace', size => 16 }, + { field => 'latexcouponfootsep', size => 16 }, + # are these still used? + #{ field => 'latexcouponamountenclosedsep', size => 16 }, + #{ field => 'latexverticalreturnaddress', type => 'checkbox' }, + #{ field => 'latexcouponaddcompanytoaddress',type => 'checkbox' }, + # logo -- implement if someone really needs it... + + { type => 'columnnext' }, + { type => 'title', value => 'HTML' }, + { field => 'htmlnotes', %textarea }, #htmlarea? + { field => 'htmlfooter', %textarea }, + { field => 'htmlsummary', %textarea }, + { field => 'htmlreturnaddress', %textarea }, + # logo + + { type => 'columnend' }, +); + +my %labels = ( + 'confnum' => 'Configuration', + 'locale' => 'Locale', + 'agentnum' => 'Agent', + 'modename' => 'Mode name', + 'notice_name' => 'Notice name', + 'subject' => 'Email Subject: header', + 'lpr' => 'Alternate lpr command', + + map { $_ => '' } (qw( + latexnotes + latexfooter + latexsummary + latexreturnaddress + latexcoupon + latexsmallfooter + latextopmargin + latexheadsep + latexaddresssep + latextextheight + latexextracouponspace + latexcouponfootsep + htmlnotes + htmlfooter + htmlsummary + htmlreturnaddress + logo_png + logo_eps + ) ), + +); + +sub get_invoice_mode { # because we can't quite use agent_virt here + my $modenum = shift; + qsearchs({ + 'table' => 'invoice_mode', + 'hashref' => { 'modenum' => $modenum }, + 'extra_sql' => ' AND '. + $FS::CurrentUser::CurrentUser->agentnums_sql( + 'null_right' => 'Edit global templates', + 'viewall_right' => 'Edit global templates' ), + }); +}; + +sub error_callback { + my ($cgi, $object) = @_; + foreach (qw(modename agentnum)) { + $object->set($_, $cgi->param($_)); + } + if ($object->confnum) { + return edit_callback(@_); + } else { + return new_callback(@_); + } +} + +sub new_callback { + my ($cgi, $object, $fields_arrayref, $opt_hashref) = @_; + my $modenum; + if ( $cgi->param('locale') =~ /^(\w+)$/ ) { + $object->set('locale' => $1); + } + + if ( $cgi->param('modenum') =~ /^(\d+)$/ ) { + $modenum = $1; # we're adding a locale to an existing mode + $object->set('modenum' => $modenum); + my $invoice_mode = get_invoice_mode($modenum) + or die "invoice mode $modenum not found"; + + $object->set('modename', $invoice_mode->modename); + $object->set('agentnum', $invoice_mode->agentnum); + + # also, need to select a locale + # make a list of available locales + my %existing_locales = map { $_->locale } + qsearch('invoice_conf', { modenum => $modenum }); + + my @locales = grep { !exists($existing_locales{$_}) } + FS::Conf->new->config('available-locales'); + my %labels; + foreach (@locales) { + my %info = FS::Locales->locale_info($_); + $labels{$_} = $info{'label'}; + } + unshift @locales, 'new'; + $labels{'new'} = 'Select language'; + + # insert a field def + my $i = 0; + $i++ until ( $fields_arrayref->[$i]->{'field'} eq 'locale' ); + my $locale_field = $fields_arrayref->[$i]; + + my $onchange_locale = "document.getElementById('submit').disabled = + (this.options[this.selectedIndex].value == 'new');"; + + %$locale_field = ( + field => 'locale', + type => 'select', + options => \@locales, + labels => \%labels, + curr_value => 'new', + onchange => $onchange_locale, + ); + + } # otherwise it's a completely new mode, so the locale is default + +} + +sub edit_callback { + # massive false laziness with msg_template UI + my ($cgi, $object, $fields_arrayref, $opt_hashref) = @_; + + # a little different here in that we treat the content object + # as "primary" (this is edit/invoice_conf.html, etc.) + # so all we need from the invoice_mode is its name + # (and agent identity) + my $modenum = $object->modenum; + my $invoice_mode = get_invoice_mode($modenum) + or die "invoice mode $modenum not found"; + $object->set('modename', $invoice_mode->modename); + $object->set('agentnum', $invoice_mode->agentnum); + +} + +sub menubar_opt_callback { + my $object = shift; + my $modenum = $object->modenum or return; + my (@tabs, @options, %labels); + my $display_new = 0; + my $selected = ''; + foreach my $l ('', FS::Conf->new->config('available-locales')) { + my $invoice_conf = + qsearchs('invoice_conf', { modenum => $modenum, locale => $l }); + if ( $invoice_conf ) { + my %info = FS::Locales->locale_info($l) if $l; + my $label = $info{'label'} || mt('Default'); + push @tabs, $label, $invoice_conf->confnum; + $selected = $label if $object->locale eq $l; + } + else { + $display_new = 1; # there is at least one unused locale left + } + } + push @tabs, mt('New'), "modenum=$modenum;locale=new" if $display_new; + $selected = mt('New') if $object->locale eq 'new'; + $selected ||= mt('Default'); + ( + 'url_base' => $cgi->url() . '?', + 'selected' => $selected, + 'tabs' => \@tabs + ); +} + +sub html_init { +q! +<STYLE> +.fstabcontainer th { vertical-align: middle; text-align: center } +</STYLE> +! +} + +sub html_table_bottom { + my $object = shift; + my $locale = ''; + my $modenum = ''; + + if ($object->locale =~ /^(\w+)$/) { + $locale = $1; + } + if ($object->modenum =~ /^(\d+)$/) { + $modenum = $1; + } + my $html; + my $show_delete = 1; + # don't allow the default locale to be removed unless it's the last one + # in the mode + $show_delete = 0 if ( + $locale eq 'new' or + $modenum eq '' or + ($locale eq '' and + FS::invoice_conf->count("modenum = $modenum and locale is not null") > 0 + ) + ); + + if ( $show_delete ) { + # set up a delete link + my $confnum = $object->confnum; + my $url = $p."misc/delete-invoice_conf.html?$confnum"; + my $link = qq!<A HREF="javascript:areyousure('$url','Really delete this configuration?')">! . + 'Delete this configuration' . + '</A>'; + $html = qq!<TR><TD></TD> + <TD STYLE="font-style: italic; font-size: small">$link</TD></TR> + <SCRIPT TYPE="text/javascript"> + function areyousure(url, message) { + if (confirm(message)) window.location.href = url; + } + </SCRIPT> + !; + } + $html; +} + +</%init> diff --git a/httemplate/edit/process/cable_provider.html b/httemplate/edit/process/cable_provider.html new file mode 100644 index 000000000..ecffaf692 --- /dev/null +++ b/httemplate/edit/process/cable_provider.html @@ -0,0 +1,10 @@ +<& elements/process.html, + 'table' => 'cable_provider', + 'viewall_dir' => 'browse', +&> +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Configuration'); + +</%init> diff --git a/httemplate/edit/process/invoice_conf.html b/httemplate/edit/process/invoice_conf.html new file mode 100644 index 000000000..1d45e126f --- /dev/null +++ b/httemplate/edit/process/invoice_conf.html @@ -0,0 +1,21 @@ +<& elements/process.html, + 'table' => 'invoice_conf', + 'viewall_dir' => 'browse', + 'fields' => [ FS::invoice_conf->fields, 'modename', 'agentnum' ], + 'precheck_callback' => \&precheck_callback, +&> +<%init> +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right(['Edit templates','Edit global templates']); + +sub precheck_callback { + my $cgi = shift; + $cgi->param('locale') =~ /^(\w*)$/; + my $locale = $1; + return mt('Language required') if $locale eq 'new'; # the user didn't choose + die "unknown locale $locale" if ( $locale and + !FS::Locales->locale_info($locale) ); +} +# invoice_conf itself knows to create/update invoice_mode if necessary, +# so nothing special here +</%init> diff --git a/httemplate/elements/columnstart.html b/httemplate/elements/columnstart.html index be37d817d..1ffbcb9e8 100644 --- a/httemplate/elements/columnstart.html +++ b/httemplate/elements/columnstart.html @@ -1,6 +1,81 @@ +<%doc> +<table> + <& /elements/columnstart.html &> + <tr> ... </tr> + <tr> ... </tr> + <& /elements/columnnext.html &> + ... + <& /elements/columnend.html &> +</table> + +Pass 'aligned' => 1 to have corresponding rows in the columns line up. +</%doc> +% my $id = sprintf('table%08d', rand(100000000)); <TR> <TD CLASS="background" COLSPAN=99> - <TABLE BORDER=0 CELLSPACING=0 CELLPADDING=0> + <TABLE BORDER=0 CELLSPACING=0 CELLPADDING=0 id="<%$id%>"> <TR> <TD VALIGN="top"> <TABLE BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0> +% if ( $aligned ) { +%# Instead of changing all the tr-* elements to sometimes output table +%# cells without wrapping them in a row, we're just going to completely +%# rebuild the table on the client side. +<script type="text/javascript"> +<&| onload.js &> + var table = document.getElementById('<%$id%>'); // has one row, always + var rows = []; // row contents, each containing + var n_rows = []; // rows in each subtable + var n_cols = []; // cols in each subtable + var total_rows = 0; // max(n_rows) + for(var i=0; i < table.rows[0].cells.length; i++) { + // these are cells created by columnstart/columnnext + // each contains a table, and nothing else + var subtable = table.rows[0].cells[i].children[0]; + n_rows[i] = subtable.rows.length; + if ( total_rows < n_rows[i] ) { + total_rows = n_rows[i]; + } + n_cols[i] = 0; + var subrows = []; // the rows of this table + for(var j=0; j < n_rows[i]; j++) { + // these are the actual tr-* rows within the table, and + // can contain multiple cells + subrows[j] = []; + var tr = subtable.rows[j]; + if ( n_cols[i] < tr.cells.length ) { + n_cols[i] = tr.cells.length; + } + for(var k=0; k < tr.cells.length; k++) { + subrows[j][k] = tr.cells[k]; + } + } // for(j) + rows[i] = subrows; + } // for(i) + var new_table = document.createElement('TABLE'); + for (var j = 0; j < total_rows; j++) { + var tr = document.createElement('TR'); + for (var i = 0; i < rows.length; i++) { // subtables + var k = 0; // subrow position + if ( j < n_rows[i] ) { // then subtable i has this row + for (k = 0; k < rows[i][j].length; k++) { // cells + tr.appendChild(rows[i][j][k]); + } + } // else k is just 0 + if ( k < n_cols[i] ) { // then we need a spacer + var spacer = document.createElement('TD'); + spacer.setAttribute('colspan', n_cols[i] - k); + tr.appendChild(spacer); + } + } // for(i); subtables + // tr is complete + new_table.appendChild(tr); + } // for(j); rows + table.parentNode.insertBefore( new_table, table ); + table.parentNode.removeChild(table); +</&> +</script> +% } # if $aligned +<%args> +$aligned => 0 +</%args> diff --git a/httemplate/elements/menu.html b/httemplate/elements/menu.html index 8cbbd1742..8cb967518 100644 --- a/httemplate/elements/menu.html +++ b/httemplate/elements/menu.html @@ -197,6 +197,10 @@ foreach my $svcdb ( FS::part_svc->svc_tables() ) { [ $fsurl. 'search/report_svc_phone_usage.html', 'Total usage (minutes, and amount billed) for the specified time period, per phone number.', ]; + $report_svc{"${name} by state"} = + [ $fsurl. 'search/phone_state.html', + 'Current or historical phone services broken down by state.', + ]; } @@ -517,6 +521,7 @@ tie my %config_radius, 'Tie::IxHash', ; tie my %config_cable, 'Tie::IxHash', + 'Cable providers' => [ $fsurl.'browse/cable_provider.html', '' ], 'Cable modem models' => [ $fsurl.'browse/cable_model.html', '' ], ; @@ -600,6 +605,7 @@ $config_billing{'Billing events'} = [ $fsurl.'browse/part_event.html', 'Billing || $curuser->access_right('Edit global billing events'); if ( $curuser->access_right('Configuration') ) { #$config_billing{'Invoice events'} = [ $fsurl.'browse/part_bill_event.cgi', 'Deprecated, old-style actions for overdue invoices' ]; + $config_billing{'Invoice configurations'} = [ $fsurl.'browse/invoice_conf.html', 'Adjust invoice settings for special-purpose notices' ]; $config_billing{'Invoice templates'} = [ $fsurl.'browse/invoice_template.html', 'Edit templates for HTML, plaintext and typeset invoices' ]; $config_billing{'separator'} = ''; #its a separator! $config_billing{'Prepaid cards'} = [ $fsurl.'search/prepay_credit.html', 'View outstanding cards, generate new cards' ]; diff --git a/httemplate/elements/select-cable_provider.html b/httemplate/elements/select-cable_provider.html new file mode 100644 index 000000000..9530b78c0 --- /dev/null +++ b/httemplate/elements/select-cable_provider.html @@ -0,0 +1,7 @@ +<% include( '/elements/select-table.html', + 'table' => 'cable_provider', + 'name_col' => 'provider', + 'empty_label' => 'Select provider', + @_, + ) +%> diff --git a/httemplate/elements/tr-select-cable_provider.html b/httemplate/elements/tr-select-cable_provider.html new file mode 100644 index 000000000..abb8564dc --- /dev/null +++ b/httemplate/elements/tr-select-cable_provider.html @@ -0,0 +1,12 @@ +% #if ( scalar(@domains) < 2 ) { +% #} else { + <TR> + <TD ALIGN="right"><% $opt{'label'} || 'Provider' %></TD> + <TD> + <% include( '/elements/select-cable_provider.html', %opt) %> + </TD> + </TR> +% #} +<%init> + my %opt = @_; +</%init> diff --git a/httemplate/elements/tr-select-invoice_mode.html b/httemplate/elements/tr-select-invoice_mode.html new file mode 100644 index 000000000..3dccdccc2 --- /dev/null +++ b/httemplate/elements/tr-select-invoice_mode.html @@ -0,0 +1,10 @@ +<& tr-select-table.html, + 'label' => 'Invoice mode', + 'table' => 'invoice_mode', + 'field' => 'modenum', + 'name_col' => 'modename', + 'agent_virt' => 1, + 'agent_null' => 1, + 'empty_label' => '(none)', + @_ +&> diff --git a/httemplate/misc/delete-invoice_conf.html b/httemplate/misc/delete-invoice_conf.html new file mode 100644 index 000000000..6cc6ddc95 --- /dev/null +++ b/httemplate/misc/delete-invoice_conf.html @@ -0,0 +1,19 @@ +<%init> +my $curuser = $FS::CurrentUser::CurrentUser; +die "access denied" + unless $curuser->access_right(['Edit templates', 'Edit global templates']); + +my ($query) = $cgi->keywords; +$query =~ /^(\d+)$/ or die "bad confnum"; +my $invoice_conf = FS::invoice_conf->by_key($1) + or die "couldn't find invoice_conf #$1"; +if ( !$curuser->access_right('Edit global templates') ) { + my $agentnum = FS::invoice_mode->by_key($invoice_conf->modenum)->agentnum; + die "access denied" + unless $curuser->agentnums_href->{$agentnum}; +} + +my $error = $invoice_conf->delete; # may also delete the invoice_mode +my $url = $p.'browse/invoice_conf.html'; +</%init> +<% $cgi->redirect($url) %> diff --git a/httemplate/misc/email-invoice.cgi b/httemplate/misc/email-invoice.cgi index 269722f67..b24e0420f 100755 --- a/httemplate/misc/email-invoice.cgi +++ b/httemplate/misc/email-invoice.cgi @@ -12,7 +12,7 @@ my $invnum = $3; my $cust_bill = qsearchs('cust_bill',{'invnum'=>$invnum}); die "Can't find invoice!\n" unless $cust_bill; -$cust_bill->email($template); +$cust_bill->email({ 'template' => $template }); my $custnum = $cust_bill->getfield('custnum'); diff --git a/httemplate/misc/fax-invoice.cgi b/httemplate/misc/fax-invoice.cgi index 2591fceb8..f72fc7eaf 100755 --- a/httemplate/misc/fax-invoice.cgi +++ b/httemplate/misc/fax-invoice.cgi @@ -12,7 +12,7 @@ my $invnum = $3; my $cust_bill = qsearchs('cust_bill',{'invnum'=>$invnum}); die "Can't find invoice!\n" unless $cust_bill; -$cust_bill->fax_invoice($template); +$cust_bill->fax_invoice({ 'template' => $template }); my $custnum = $cust_bill->getfield('custnum'); diff --git a/httemplate/misc/print-invoice.cgi b/httemplate/misc/print-invoice.cgi index aeef68795..5ce6e76df 100755 --- a/httemplate/misc/print-invoice.cgi +++ b/httemplate/misc/print-invoice.cgi @@ -12,7 +12,7 @@ my $invnum = $3; my $cust_bill = qsearchs('cust_bill',{'invnum'=>$invnum}); die "Can't find invoice!\n" unless $cust_bill; -$cust_bill->print($template); +$cust_bill->print({ 'template' => $template}); my $custnum = $cust_bill->getfield('custnum'); diff --git a/httemplate/misc/send-invoice.cgi b/httemplate/misc/send-invoice.cgi index 32dfe276d..08dd0e01c 100644 --- a/httemplate/misc/send-invoice.cgi +++ b/httemplate/misc/send-invoice.cgi @@ -13,6 +13,10 @@ my $invnum = $cgi->param('invnum'); my $template = $cgi->param('template'); my $notice_name = $cgi->param('notice_name') if $cgi->param('notice_name'); my $method = $cgi->param('method'); +my $mode; +if ( $cgi->param('mode') =~ /^(\d+)$/ ) { + $mode = $1; +} $method .= '_invoice' if $method eq 'fax'; #! @@ -21,6 +25,7 @@ die "unknown method $method" unless $method{$method}; my $cust_bill = qsearchs('cust_bill',{'invnum'=>$invnum}); die "Can't find invoice!\n" unless $cust_bill; +$cust_bill->set('mode' => $mode) if $mode; $cust_bill->$method({ 'template' => $template, 'notice_name' => $notice_name, }); diff --git a/httemplate/misc/xmlhttp-cust_main-duplicates.html b/httemplate/misc/xmlhttp-cust_main-duplicates.html index 7ee00af66..7cd463371 100644 --- a/httemplate/misc/xmlhttp-cust_main-duplicates.html +++ b/httemplate/misc/xmlhttp-cust_main-duplicates.html @@ -50,7 +50,9 @@ my $conf = new FS::Conf; my $sub = $cgi->param('sub'); my $hashref = decode_json($cgi->param('arg')); -my @cust_main = qsearch('cust_main', $hashref); +my $search = FS::cust_main->search($hashref); +#warn Dumper($search); +my @cust_main = qsearch( $search ); my $set_to_customer = <<EOF; var custnum_array = document.getElementsByName('dup_custnum'); diff --git a/httemplate/search/phone_state.html b/httemplate/search/phone_state.html new file mode 100644 index 000000000..67965b702 --- /dev/null +++ b/httemplate/search/phone_state.html @@ -0,0 +1,167 @@ +<& elements/search.html, + 'title' => $title, + 'name' => 'states', + 'query' => $query, + 'count_query' => $count_query, + 'count_addl' => $count_addl, + 'header' => [ 'State', # if we add more group fields, change this + 'Count', + 'Phone numbers' + ], + 'fields' => [ 'state', + 'num_svcnums', + $detail_sub + ], + 'html_init' => include('.head', $time), +&> +<%def .head> +% my $time = shift; +<FORM STYLE="display:inline" ACTION=<% $cgi->url %> METHOD="GET"> +Active phone services as of <& /elements/input-date-field.html, { + 'name' => 'date', + 'value' => $time, + 'format' => FS::Conf->new->config('date_format') || '%m/%d/%Y' +} &> +<INPUT TYPE="hidden" NAME="order_by" VALUE="<% $cgi->param('order_by') %>"> +<INPUT TYPE="submit" VALUE="Refresh"> +</FORM> +<BR> +<BR> +</%def> +<%init> +# svc_phone-specific for now; may change later +my $curuser = $FS::CurrentUser::CurrentUser; +die "access denied" unless $curuser->access_right('Services: Phone numbers'); +my $title = 'Phone services by state'; + +my $time = time; +if ( $cgi->param('date') ) { + $time = parse_datetime($cgi->param('date')); + $title .= time2str(' (%b %o, %Y)', $time); +} + +my @tables = qw(svc_phone cust_svc cust_pkg cust_location cust_main); +my @pkeys = qw(svcnum svcnum pkgnum locationnum custnum); +my @h_tables = map "h_$_", @tables; + +my $addl_from = ''; +my @where; + +for(my $i = 0; $i < scalar(@tables); $i++) { + my $last_table = $h_tables[$i-1]; + my $pkey = $pkeys[$i]; + my $table = $tables[$i]; + my $h_table = $h_tables[$i]; + # alias the preceding table, and join to a subquery that finds the most + # recent change to $table.$pkey before $time + my $alias = $h_table; + my $inside = ''; + if ( $i > 0 ) { + $alias = "t$i"; + $inside = " AS $alias"; + } + $inside .= " + JOIN + (SELECT $pkey AS num, MAX(history_date) AS history_date + FROM $h_table + WHERE history_date <= $time AND + history_action IN ('insert', 'replace_new') + GROUP BY $pkey + ) AS mostrecent_$table + ON ($alias.$pkey = mostrecent_$table.num AND + $alias.history_date = mostrecent_$table.history_date AND + $alias.history_action IN ('insert', 'replace_new') + ) + LEFT JOIN + (SELECT $pkey AS num, MAX(history_date) AS history_date, 1 AS deleted + FROM $h_table + WHERE history_date <= $time AND + history_action = 'delete' + GROUP BY $pkey + ) AS deleted_$table + ON (mostrecent_$table.num = deleted_$table.num AND + mostrecent_$table.history_date < deleted_$table.history_date + ) +"; + # join to the preceding table if there is one, and filter out + # deleted records + if ( $i > 0 ) { + # special case to make pre-3.x data work; remove this later + if ( $table eq 'cust_main' ) { + $last_table = 'h_cust_pkg'; + } + $addl_from .= " + LEFT JOIN ( $h_table $inside ) AS $h_table + ON ($h_table.$pkey = $last_table.$pkey)"; + push @where, "$h_table.deleted IS NULL"; + } else { + $addl_from .= $inside; + push @where, "deleted_$table.deleted IS NULL"; + } +} + +# so that we know which services are still active +$addl_from .= " + LEFT JOIN svc_phone ON (h_svc_phone.svcnum = svc_phone.svcnum AND + h_svc_phone.phonenum = svc_phone.phonenum)"; + +#warn "\n\nJOIN EXPRESSION:\n$addl_from\n\n"; + +push @where, $curuser->agentnums_sql( + 'table' => 'h_cust_main', + 'null_right' => 'View/link unlinked services' +); +my $where = " WHERE ".join(' AND ', map "($_)", @where); + +# for pre-3.x data +my $group_field = 'COALESCE(h_cust_location.state, h_cust_main.ship_state, h_cust_main.state)'; + +my @select = ( + "$group_field AS state", + 'count(DISTINCT h_svc_phone.svcnum) AS num_svcnums', + # don't DISTINCT these (it reorders them) + "array_to_string(array_agg(h_svc_phone.phonenum), ',') AS all_phonenums", + "array_to_string(array_agg(h_svc_phone.svcnum), ',') AS all_svcnums", + "array_to_string(array_agg(svc_phone.svcnum), ',') AS active_svcnums", +); + +my $query = { + 'select' => join(',', @select), + 'table' => 'h_svc_phone', + 'addl_from' => $addl_from, + 'extra_sql' => " $where GROUP BY $group_field", +}; + +# DISTINCT on these because of cross-producting effects when a cust_pkg +# record (usually) was replaced more than once within one second. +my $count_query = + "SELECT COUNT(DISTINCT $group_field), COUNT(DISTINCT h_svc_phone.svcnum) ". + "FROM h_svc_phone $addl_from $where"; +my $count_addl = [ '%d phone services' ]; + +my $detail_sub = sub { + my $rec = shift; + warn Dumper $rec; + my @svcnums = split(',', $rec->all_svcnums); + my @phonenums = split(',', $rec->all_phonenums); + # identifies services that still exist with the same svcnum+phonenum + my %active = map { $_ => 1 } split(',', $rec->active_svcnums); + # make a single column of phonenums + my @return; + my %seen; + while (my $svcnum = shift @svcnums) { + my $phonenum = shift @phonenums; + next if $seen{$svcnum}; + $seen{$svcnum} = 1; + my $link = $active{$svcnum} ? + $p.'view/svc_phone.cgi?'.$svcnum : + ''; + push @return, [ { data => $phonenum, + link => $link, + data_style => ($active{$svcnum} ? '' : 'i') + } ]; + } + \@return; +}; + +</%init> diff --git a/httemplate/view/cust_bill.cgi b/httemplate/view/cust_bill.cgi index 95ce60b1d..4822ab718 100755 --- a/httemplate/view/cust_bill.cgi +++ b/httemplate/view/cust_bill.cgi @@ -104,12 +104,35 @@ % my $br = 0; % if ( $cust_bill->num_cust_event ) { $br++; <A HREF="<%$p%>search/cust_event.html?invnum=<% $cust_bill->invnum %>">( <% mt('View invoice events') |h %> )</A> -% } +% } % if ( $cust_bill->num_cust_bill_event ) { $br++; <A HREF="<%$p%>search/cust_bill_event.cgi?invnum=<% $cust_bill->invnum %>">( <% mt('View deprecated, old-style invoice events') |h %> )</A> % } +% my @modes = grep {! $_->disabled} +% $cust_bill->cust_main->agent->invoice_modes; +% if ( @modes ) { +( <% mt('View as:') %> +<FORM STYLE="display:inline" ACTION="<% $cgi->url %>" METHOD="GET"> +<INPUT NAME="invnum" VALUE="<% $invnum %>" TYPE="hidden"> +<& /elements/select-table.html, + table => 'invoice_mode', + field => 'mode', + curr_value => scalar($cgi->param('mode')), + records => \@modes, + name_col => 'modename', + onchange => 'change_invoice_mode', + empty_label => '(default)', +&> ) +<SCRIPT TYPE="text/javascript"> +function change_invoice_mode(obj) { + obj.form.submit(); +} +</SCRIPT> +% $br++; +% } + <% $br ? '<BR><BR>' : '' %> % if ( $conf->exists('invoice_html') ) { @@ -126,7 +149,9 @@ my $curuser = $FS::CurrentUser::CurrentUser; die "access denied" unless $curuser->access_right('View invoices'); -my( $invnum, $template, $notice_name ); +my $conf = FS::Conf->new; + +my( $invnum, $mode, $template, $notice_name ); my($query) = $cgi->keywords; if ( $query =~ /^((.+)-)?(\d+)$/ ) { $template = $2; @@ -136,10 +161,9 @@ if ( $query =~ /^((.+)-)?(\d+)$/ ) { $invnum = $cgi->param('invnum'); $template = $cgi->param('template'); $notice_name = $cgi->param('notice_name'); + $mode = $cgi->param('mode'); } -my $conf = new FS::Conf; - my %opt = ( 'unsquelch_cdr' => $conf->exists('voip-cdr_email'), 'template' => $template, @@ -163,10 +187,13 @@ my $cust_bill = qsearchs({ }); die "Invoice #$invnum not found!" unless $cust_bill; +$cust_bill->set('mode' => $mode); + my $custnum = $cust_bill->custnum; my $display_custnum = $cust_bill->cust_main->display_custnum; my $link = "invnum=$invnum"; +$link .= ';mode=' . $mode if $mode; $link .= ';template='. uri_escape($template) if $template; $link .= ';notice_name='. $notice_name if $notice_name; diff --git a/httemplate/view/cust_main.cgi b/httemplate/view/cust_main.cgi index 430c50c5f..391988190 100755 --- a/httemplate/view/cust_main.cgi +++ b/httemplate/view/cust_main.cgi @@ -147,7 +147,6 @@ function areyousure(href, message) { % if ( $br ) { <BR><BR> % } -</%doc> %my $signupurl = $conf->config('signupurl'); %if ( $signupurl ) { diff --git a/httemplate/view/cust_statement.html b/httemplate/view/cust_statement.html index 3e1345ed5..5d37b3167 100755 --- a/httemplate/view/cust_statement.html +++ b/httemplate/view/cust_statement.html @@ -35,10 +35,10 @@ % if ( $conf->exists('invoice_html') ) { - <% join('', $cust_statement->print_html('', $templatename) ) %> + <% join('', $cust_statement->print_html('template' => $templatename) ) %> % } else { - <PRE><% join('', $cust_statement->print_text('', $templatename) ) %></PRE> + <PRE><% join('', $cust_statement->print_text('template' => $templatename) ) %></PRE> % } <% include('/elements/footer.html') %> diff --git a/httemplate/view/elements/cust_bill-typeset b/httemplate/view/elements/cust_bill-typeset index 00f503fbb..778e538d1 100644 --- a/httemplate/view/elements/cust_bill-typeset +++ b/httemplate/view/elements/cust_bill-typeset @@ -6,7 +6,7 @@ die "access denied" my $type = shift; -my( $invnum, $template, $notice_name ); +my( $invnum, $mode, $template, $notice_name ); my($query) = $cgi->keywords; if ( $query =~ /^((.+)-)?(\d+)(.pdf)?$/ ) { #probably not necessary anymore? $template = $2; @@ -16,7 +16,8 @@ if ( $query =~ /^((.+)-)?(\d+)(.pdf)?$/ ) { #probably not necessary anymore? $invnum = $cgi->param('invnum'); $invnum =~ s/\.pdf//i; #probably not necessary anymore $template = $cgi->param('template'); - $notice_name = ( $cgi->param('notice_name') || 'Invoice' ); + $notice_name = $cgi->param('notice_name'); + $mode = $cgi->param('mode'); } my $conf = new FS::Conf; @@ -36,6 +37,8 @@ my $cust_bill = qsearchs({ }); die "Invoice #$invnum not found!" unless $cust_bill; +$cust_bill->set(mode => $mode); + my $method = "print_$type"; my $content = $cust_bill->$method(\%opt); |