diff options
author | Ivan Kohler <ivan@freeside.biz> | 2018-02-09 19:10:00 -0800 |
---|---|---|
committer | Ivan Kohler <ivan@freeside.biz> | 2018-02-09 19:10:00 -0800 |
commit | d45dd4a826f314fb5459747590d3e11cd80c211f (patch) | |
tree | c1dd2edd4bc42b12cc9a995e95dd7fb630da925e /httemplate | |
parent | 4b67c9f8cfc9f944b7758e7e69ac1f9f188ffa47 (diff) | |
parent | 15d596e3090f3bde642917b56563736cd1ee2e90 (diff) |
Merge branch 'master' of git.freeside.biz:/home/git/freeside
Diffstat (limited to 'httemplate')
29 files changed, 1095 insertions, 137 deletions
diff --git a/httemplate/browse/realestate_location.html b/httemplate/browse/realestate_location.html new file mode 100644 index 000000000..be2cd11f8 --- /dev/null +++ b/httemplate/browse/realestate_location.html @@ -0,0 +1,43 @@ +<% include( 'elements/browse.html', + title => emt('Real Estate Locations'), + name => 'real estate locations', + + menubar => [ + 'Edit units' => "${p}browse/realestate_unit.html", + 'Add a new location' => "${p}edit/realestate_location.html", + 'Add a new unit' => "${p}edit/realestate_unit.html", + ], + + query => { table => 'realestate_location' }, + count_query => 'SELECT COUNT(*) FROM realestate_location', + + header => [ 'Location', 'Address', 'Address 2', 'City', 'State', 'Zip' ], + fields => [ + 'location_title', + 'address1', + 'address2', + 'city', + 'state', + 'zip' + ], + links => [ + ["${p}edit/realestate_location.html?", 'realestatelocnum' ], + ], + + agent_virt => 1, + agent_pos => 0, + disableable => 1, +) +%> +<%init> + + +my $curuser = $FS::CurrentUser::CurrentUser; +die("access denied") + unless $curuser->access_right('Edit inventory') + || $curuser->access_right('Edit global inventory') + || $curuser->access_right('Configuration'); + + + +</%init> diff --git a/httemplate/browse/realestate_unit.html b/httemplate/browse/realestate_unit.html new file mode 100644 index 000000000..399cd2583 --- /dev/null +++ b/httemplate/browse/realestate_unit.html @@ -0,0 +1,70 @@ +<% include( 'elements/browse.html', + title => emt('Real Estate Inventory'), + name => 'real estate inventory', + + menubar => [ + 'Edit locations' => "${p}browse/realestate_location.html", + 'Add a new location' => "${p}edit/realestate_location.html", + 'Add a new unit' => "${p}edit/realestate_unit.html", + ], + + query => { + table => 'realestate_unit', + select => join(', ',qw( + realestate_unit.* + realestate_location.location_title + cust_main.first + cust_main.last + cust_main.company + )), + addl_from => " + LEFT JOIN realestate_location + ON realestate_location.realestatelocnum + = realestate_unit.realestatelocnum + LEFT JOIN svc_realestate + ON realestate_unit.realestatenum = svc_realestate.realestatenum + LEFT JOIN cust_svc + ON svc_realestate.svcnum = cust_svc.svcnum + LEFT JOIN cust_pkg + ON cust_svc.pkgnum = cust_pkg.pkgnum + LEFT JOIN cust_main + ON cust_pkg.custnum = cust_main.custnum + ", + order_by => "ORDER BY location_title, unit_title" + }, + + count_query => 'SELECT COUNT(*) FROM realestate_unit', + + header => [ 'Location', 'Unit', 'Customer' ], + fields => [ + 'location_title', + 'unit_title', + sub { + return '' unless $_[0]->custnum; + return $_[0]->company if $_[0]->company; + return $_[0]->first.' '.$_[0]->last; + }, + ], + links => [ + ["${p}edit/realestate_location.html?", 'realestatelocnum' ], + ["${p}edit/realestate_unit.html?", 'realestatenum' ], + ["${p}view/cust_main.cgi?", 'custnum' ] + ], + + agent_virt => 1, + agent_pos => 0, + disableable => 1, +) +%> +<%init> + + +my $curuser = $FS::CurrentUser::CurrentUser; +die("access denied") + unless $curuser->access_right('Edit inventory') + || $curuser->access_right('Edit global inventory') + || $curuser->access_right('Configuration'); + + + +</%init> diff --git a/httemplate/docs/part_svc-table.html b/httemplate/docs/part_svc-table.html index 820d0b9cc..56a4d0e8c 100644 --- a/httemplate/docs/part_svc-table.html +++ b/httemplate/docs/part_svc-table.html @@ -39,6 +39,7 @@ <TR> <TH ALIGN="left">Hosting</TH> <TH ALIGN="left">Colocation</TH> + <TH ALIGN="left">Real Estate</TH> </TR> <TD VALIGN="top"> <UL STYLE="margin:0"> @@ -54,6 +55,11 @@ <LI><B>svc_port</B>: Customer router/switch port </UL> </TD> + <TD VALIGN="top"> + <UL STYLE="margin:0"> + <LI><B>svc_realestate</B>: Real estate properties + </UL> + </TD> </TR> <TABLE> <!-- <LI>svc_charge - One-time charges (Partially unimplemented) @@ -62,4 +68,3 @@ </BODY> </HTML> - diff --git a/httemplate/edit/process/cust_main-contacts.html b/httemplate/edit/process/cust_main-contacts.html index 2a7185b5c..5b8319f5a 100644 --- a/httemplate/edit/process/cust_main-contacts.html +++ b/httemplate/edit/process/cust_main-contacts.html @@ -1,3 +1,11 @@ +<%doc> + + This form works indirectly with the tables contact_email and + contact_phone. The columns are updated against an FS::contact + object. The insert/update methods of FS::contact will make the + necessary inserts/updates to contact_email and contact_phone. + +</%doc> <% include('elements/process.html', 'table' => 'cust_main', 'error_redirect' => popurl(3). 'edit/cust_main-contacts.html?', diff --git a/httemplate/edit/process/realestate_location.html b/httemplate/edit/process/realestate_location.html new file mode 100644 index 000000000..ab5cf230f --- /dev/null +++ b/httemplate/edit/process/realestate_location.html @@ -0,0 +1,14 @@ +<% include( 'elements/process.html', + table => 'realestate_location', + viewall_dir => 'browse', + ) +%> +<%init> + +my $curuser = $FS::CurrentUser::CurrentUser; +die "access denied" + unless $curuser->access_right('Edit inventory') + || $curuser->access_right('Edit global inventory') + || $curuser->access_right('Configuration'); + +</%init> diff --git a/httemplate/edit/process/realestate_unit.html b/httemplate/edit/process/realestate_unit.html new file mode 100644 index 000000000..ba9b5dc92 --- /dev/null +++ b/httemplate/edit/process/realestate_unit.html @@ -0,0 +1,13 @@ +<& elements/process.html, + 'table' => 'realestate_unit', + 'viewall_dir' => 'browse', +&> +<%init> + +my $curuser = $FS::CurrentUser::CurrentUser; +die("access denied") + unless $curuser->access_right('Edit inventory') + || $curuser->access_right('Edit global inventory') + || $curuser->access_right('Configuration'); + +</%init> diff --git a/httemplate/edit/realestate_location.html b/httemplate/edit/realestate_location.html new file mode 100644 index 000000000..34344e98f --- /dev/null +++ b/httemplate/edit/realestate_location.html @@ -0,0 +1,72 @@ +<% include( 'elements/edit.html', + 'name_singular' => 'Real Estate Location', + 'table' => 'realestate_location', + + 'labels' => { + realestatelocnum => 'Location', + location_title => "Location", + address1 => "Address", + address2 => "Address", + city => "City", + state => "State", + zip => "Zip-Code", + disabled => "Disabled", + }, + 'fields' => [ + { field => 'realestatelocnum', type => 'hidden' }, + + { field => 'location_title', + type=>'text', + size => 40, + maxlength => 80, + }, + { field => 'address1', + type=>'text', + size => 40, + maxlength => 80, + }, + { field => 'address2', + type=>'text', + size => 40, + maxlength => 80, + }, + { field => 'city', + type=>'text', + size => 40, + maxlength => 80, + }, + { field => 'state', + type=>'text', + size => 40, + maxlength => 80, + }, + { field => 'zip', + type=>'text', + size => 5, + maxlength => 5, + }, + + { field => 'agentnum', + type => 'select-agent', + value => 1, + }, + { field => 'disabled', + type=>'checkbox', + value=>'Y' + }, + ], + + 'viewall_dir' => 'browse', + 'agent_virt' => 1, +) +%> + +<%init> + +my $curuser = $FS::CurrentUser::CurrentUser; +die("access denied") + unless $curuser->access_right('Edit inventory') + || $curuser->access_right('Edit global inventory') + || $curuser->access_right('Configuration'); + +</%init> diff --git a/httemplate/edit/realestate_unit.html b/httemplate/edit/realestate_unit.html new file mode 100644 index 000000000..a7ca72dd9 --- /dev/null +++ b/httemplate/edit/realestate_unit.html @@ -0,0 +1,48 @@ +<% include( 'elements/edit.html', + 'name_singular' => 'Real Estate Unit', + 'table' => 'realestate_unit', + + 'labels' => { + realestatenum => 'Ref No', + unit_title => 'Unit Title', + agentnum => 'Agent', + realestatelocnum => 'Location', + }, + 'fields' => [ + { field => 'realestatenum', type => 'hidden' }, + + { field => 'unit_title', + type=>'text', + size => 40, + }, + { field => 'realestatelocnum', + type => 'select-realestate_location', + + # possible todo: + # I'd like to have this field disabled for editing on existing records, + # and only show the full selectbox for new records. + + }, + { field => 'agentnum', + type => 'select-agent', + }, + { field => 'disabled', + type=>'checkbox', + value=>'Y' + }, + ], + + 'viewall_dir' => 'browse', + 'agent_virt' => 1, +) +%> + +<%init> + +my $curuser = $FS::CurrentUser::CurrentUser; +die("access denied") + unless $curuser->access_right('Edit inventory') + || $curuser->access_right('Edit global inventory') + || $curuser->access_right('Configuration'); + +</%init> diff --git a/httemplate/elements/contact.html b/httemplate/elements/contact.html index faee7ead4..43e520155 100644 --- a/httemplate/elements/contact.html +++ b/httemplate/elements/contact.html @@ -42,7 +42,8 @@ % $value = join(', ', map $_->emailaddress, $contact->contact_email); % } elsif ( $field eq 'selfservice_access' % or $field eq 'comment' -% or $field eq 'invoice_dest' ) { +% or $field eq 'invoice_dest' +% or $field eq 'message_dest' ) { % $value = $X_contact->get($field); % } else { % $value = $contact->get($field); @@ -78,7 +79,7 @@ return false } </SCRIPT> -% } elsif ( $field eq 'invoice_dest' ) { +% } elsif ( $field eq 'invoice_dest' || $field eq 'message_dest' ) { % my $curr_value = $cgi->param($name . '_' . $field); % $curr_value = $value if !defined($curr_value); <& select.html, @@ -168,6 +169,7 @@ tie my %label, 'Tie::IxHash', unless ($opt{'for_prospect'}) { $label{'invoice_dest'} = 'Send invoices'; + $label{'message_dest'} = 'Send messages'; $label{'selfservice_access'} = 'Self-service'; } diff --git a/httemplate/elements/menu.html b/httemplate/elements/menu.html index 3b3d244db..eb065b668 100644 --- a/httemplate/elements/menu.html +++ b/httemplate/elements/menu.html @@ -418,6 +418,8 @@ if( $curuser->access_right('Financial reports') ) { $report_financial{'Customer Accounting Summary'} = [ $fsurl.'search/report_customer_accounting_summary.html', 'Customer accounting summary report' ]; + $report_financial{'Upcoming Auto-Bill Transactions'} = [ $fsurl.'search/report_future_autobill.html', 'Upcoming auto-bill transactions' ]; + } elsif($curuser->access_right('Receivables report')) { $report_financial{'A/R Aging'} = [ $fsurl.'search/report_receivables.html', 'Accounts Receivable Aging report' ]; @@ -835,6 +837,11 @@ $config_misc{'Inventory classes and inventory'} = [ $fsurl.'browse/inventory_cla || $curuser->access_right('Edit global inventory') || $curuser->access_right('Configuration'); +$config_misc{'Real estate inventory'} = [ $fsurl.'browse/realestate_unit.html', 'Setup real estate inventory' ] + if $curuser->access_right('Edit realestate inventory') + || $curuser->access_right('Edit global inventory') + || $curuser->access_right('Configuration'); + $config_misc{'Upload targets'} = [ $fsurl.'browse/upload_target.html', 'Billing and payment upload destinations' ] if $curuser->access_right('Configuration'); @@ -1039,4 +1046,3 @@ sub submenu { } </%init> - diff --git a/httemplate/elements/select-multiple-contact_class.html b/httemplate/elements/select-multiple-contact_class.html new file mode 100644 index 000000000..81a71cc25 --- /dev/null +++ b/httemplate/elements/select-multiple-contact_class.html @@ -0,0 +1,21 @@ +<%doc> + +Display a multi-select box containing all Email Types listed in +the contact_class table. + +NOTE: + Don't confuse "Contact Type" (contact_email.classnum) with + "Customer Class" (cust_main.classnum) + +</%doc> +<% include( '/elements/select-table.html', + table => 'contact_class', + hashref => { disabled => '' }, + name_col => 'classname', + field => 'classnum', + pre_options => [ 0 => '(No Type)' ], + multiple => 1, + all_selected => 1, + @_, + ) +%> diff --git a/httemplate/elements/select-realestate_location.html b/httemplate/elements/select-realestate_location.html new file mode 100644 index 000000000..001ed3e89 --- /dev/null +++ b/httemplate/elements/select-realestate_location.html @@ -0,0 +1,32 @@ +<%doc> + + Displays a selectbox populated with values from realestate_location. + key: realestate_location.realestatenum + value: realestate_location.location_title + +</%doc> + +<% include( '/elements/select-table.html', + %opt, + table => 'realestate_location', + name_col => 'location_title', + hashref => { 'disabled' => '' }, + value => $select_value, + disable_empty => 1, + ) +%> + +<%init> + +# +# possible todo: +# I'd like to change the behavior of this select based on if +# a new item is being created, or an existing item being edited + +my %opt = @_; +my $select_value = $opt{'curr_value'} || $opt{'value'}; + +# use Data::Dumper qw(Dumper); +# print Dumper(\%opt); + +</%init> diff --git a/httemplate/elements/select-realestate_unit.html b/httemplate/elements/select-realestate_unit.html new file mode 100644 index 000000000..e189d5d99 --- /dev/null +++ b/httemplate/elements/select-realestate_unit.html @@ -0,0 +1,59 @@ +<%doc> + +Display a pair of select boxes for provisioning a realestate_unit +- Real Estate Location +- Real Estate Unit + +NOTE: + Records are always suppresed if + - realestate_location.disabled is set + - realestate_unit is provisioned to a customer [not working] + + If it becomes necessary, an option may be added to the template + to show disabled/provisioned records, but is not yet implemented + +</%doc> +<& select-tiered.html, + 'tiers' => [ + { + + field => 'realestate_location', + table => 'realestate_location', + extra_sql => "WHERE realestate_location.disabled IS NULL " + . " OR realestate_location.disabled = '' ", + name_col => 'location_title', + empty_label => '(all)', + }, + { + field => 'realestatenum', + table => 'realestate_unit', + name_col => 'unit_title', + value_col => 'realestatenum', + link_col => 'realestatelocnum', + + # TODO: Filter units assigned to customers + # SQL below breaks the selectbox... why? + + # Also, can we assume if realestatenum doesn't appear in svc_realestate + # that the realestate_unit is unprovisioned to a customer? What indicator + # should be used to determine when a realestae_unit is not provisioned? + + # addl_from => " + # LEFT JOIN svc_realestate + # ON svc_realestate.realestatenum = realestate_unit.realestatenum + # ", + + #extra_sql => "WHERE svc_realestate.svcnum IS NULL ", + + disable_empty => 1, + debug => 1, + }, + ], + %opt, + 'prefix' => $opt{'prefix'}. $opt{'field'}. '_', #after %opt so it overrides +&> +<%init> + +my %opt = @_; + +</%init> diff --git a/httemplate/elements/tr-checkbox-multiple.html b/httemplate/elements/tr-checkbox-multiple.html index 4d754b007..baf18f916 100644 --- a/httemplate/elements/tr-checkbox-multiple.html +++ b/httemplate/elements/tr-checkbox-multiple.html @@ -1,3 +1,23 @@ +<%doc> + +Display a <tr> containing multiple checkboxes + +USAGE: + +<& /elements/tr-checkbox-multipe.html, + label => emt('Label'), + field => 'field_name', + options => ['opt1', 'opt2'], + labels => { + opt1 => 'Option 1', + opt2 => 'Option 2', + }, + value => { + opt2 => '1', # opt2 defaults as checked + } +&> + +</%doc> <% include('tr-td-label.html', @_ ) %> <TD <% $style %>> diff --git a/httemplate/elements/tr-select-multiple-contact_class.html b/httemplate/elements/tr-select-multiple-contact_class.html new file mode 100644 index 000000000..5de129324 --- /dev/null +++ b/httemplate/elements/tr-select-multiple-contact_class.html @@ -0,0 +1,32 @@ +<%doc> + + Displays Contact Types as a multi-select box. + + If no non-disabled Contact Types have been defined in contact_class table, + renders a hidden input field with a blank value. + +</%doc> + +% if ($has_types) { +<TR> + <TD ALIGN="right"><% $opt{'label'} || emt('Contact Type') %></TD> + <TD> + <% include( '/elements/select-multiple-contact_class.html', %opt ) %> + </TD> +</TR> +% } else { +<INPUT TYPE="hidden" NAME="<% $opt{field} %>" VALUE=""> +% } + +<%init> + +my %opt = @_; +$opt{field} ||= $opt{element_name} ||= 'classnum'; + +my $has_types =()= qsearch({ + table => 'contact_class', + hashref => { disabled => '' }, + extra_sql => ' LIMIT 1 ', +}); + +</%init> diff --git a/httemplate/elements/tr-select-realestate_location.html b/httemplate/elements/tr-select-realestate_location.html new file mode 100644 index 000000000..1367886ed --- /dev/null +++ b/httemplate/elements/tr-select-realestate_location.html @@ -0,0 +1,17 @@ +<TR> + <TH ALIGN="right"><% $opt{'label'} || 'Real Estate Location' %></TD> + <TD> + <% include( '/elements/select-realestate_location.html', + 'curr_value' => $curr_value, + %opt + ) + %> + </TD> +</TR> + +<%init> + +my %opt = @_; +my $curr_value = $opt{'curr_value'} || $opt{'value'}; + +</%init> diff --git a/httemplate/elements/tr-select-realestate_unit.html b/httemplate/elements/tr-select-realestate_unit.html new file mode 100644 index 000000000..b1a4296d4 --- /dev/null +++ b/httemplate/elements/tr-select-realestate_unit.html @@ -0,0 +1,5 @@ +<& tr-td-label.html, @_ &> +<td> +<& select-realestate_unit.html, @_ &> +</td> +</tr> diff --git a/httemplate/misc/cust_main-import_charges.cgi b/httemplate/misc/cust_main-import_charges.cgi index 4eacce13a..215cc4c9d 100644 --- a/httemplate/misc/cust_main-import_charges.cgi +++ b/httemplate/misc/cust_main-import_charges.cgi @@ -28,9 +28,9 @@ Import a CSV file containing customer charges. <TH ALIGN="right">Format</TH> <TD> <SELECT NAME="format"> - <OPTION VALUE="simple">Simple - <OPTION VALUE="ooma">Ooma -<!-- <OPTION VALUE="extended" SELECTED>Extended --> +% foreach my $format ( keys %formats ) { + <OPTION VALUE="<% $format %>"><% $formats{$format} %></OPTION> +% } </SELECT> </TD> </TR> @@ -94,6 +94,8 @@ Field information: die "access denied" unless $FS::CurrentUser::CurrentUser->access_right('Import'); + tie my %formats, 'Tie::IxHash', FS::cust_main::Import_Charges->import_formats; + my $custbatch = time2str('webimport-%Y/%m/%d-%T'. "-$$-". rand() * 2**32, time); </%init> diff --git a/httemplate/misc/email-customers.html b/httemplate/misc/email-customers.html index fe637abe1..f52c6b36a 100644 --- a/httemplate/misc/email-customers.html +++ b/httemplate/misc/email-customers.html @@ -171,16 +171,18 @@ Template: <TD>Send to contacts:</TD> <TD> <div id="contactclassesdiv"> - <& /elements/checkboxes.html, - 'style' => 'display: inline; vertical-align: top', - 'disable_links' => 1, - 'names_list' => \@contact_checkboxes, - 'element_name_prefix' => 'contact_class_', - 'checked_callback' => sub { - my($cgi, $name) = @_; - $name eq 'invoice' #others default to unchecked - }, - &> + <& /elements/checkboxes.html, + 'style' => 'display: inline; vertical-align: top', + 'disable_links' => 1, + 'names_list' => \@optin_checkboxes, + 'element_name_prefix' => 'contact_class_', + 'checked_callback' => sub { + # Called for each checkbox + # Return true to default as checked, false as unchecked + my($cgi, $name) = @_; + exists $dest_ischecked{$name}; + }, + &> </div> % if ($send_to_domain) { <div> @@ -197,6 +199,27 @@ Template: </div> % } </TD> +% if (@active_classes) { +</tr> +<tr> +<TD>Contact Type:</TD> +<TD> + <div id="contactclassesdiv"> + <& /elements/checkboxes.html, + 'style' => 'display: inline; vertical-align: top', + 'disable_links' => 1, + 'names_list' => \@classnum_checkboxes, + 'element_name_prefix' => 'contact_class_', + 'checked_callback' => sub { + # Called for each checkbox + # Return true to default as checked, false as unchecked + my($cgi, $name) = @_; + exists $classnum_ischecked{$name}; + }, + &> + </div> +</TD> +% } </TR> </TABLE> <BR> @@ -340,6 +363,21 @@ if ( !$cgi->param('preview') ) { } else { + my @checked_email_dest; + my @checked_contact_type; + for ($cgi->param) { + if (/^contact_class_(.+)$/) { + my $f = $1; + if ($f eq 'invoice' || $f eq 'message') { + push @checked_email_dest, $f; + } elsif ( $f =~ /^\d+$/ ) { + push @checked_contact_type, $f; + } + } + } + $search{with_email_dest} = \@checked_email_dest if @checked_email_dest; + $search{with_contact_type} = \@checked_contact_type if @checked_contact_type; + my $sql_query = "FS::$table"->search(\%search); my $count_query = delete($sql_query->{'count_query'}); my $count_sth = dbh->prepare($count_query) @@ -389,6 +427,8 @@ if ( !$cgi->param('preview') ) { $sql_query->{'select'} = "$table.*"; $sql_query->{'order_by'} = ''; my $object = qsearchs($sql_query); + # Could use better error handling here... + die "No customers match the search criteria" unless ref $object; $cust = $object->cust_main; my %msgopts = ( 'cust_main' => $cust, @@ -422,24 +462,59 @@ if ( !$cgi->param('preview') ) { push @contact_classnum, $1; if ( $1 eq 'invoice' ) { push @contact_classname, 'Invoice recipients'; + } elsif ( $1 eq 'message' ) { + push @contact_classname, 'Message recipients'; } else { my $contact_class = FS::contact_class->by_key($1); - push @contact_classname, encode_entities($contact_class->classname); + push @contact_classname, encode_entities( + $contact_class ? $contact_class->classname : '(none)' + ); } } } } } -my @contact_checkboxes = ( - [ 'invoice' => { label => 'Invoice recipients' } ] -); +# Build data structures for "Opt In" and "Contact Type" checkboxes +# +# By default, message recipients will be selected, this is a message. +# By default, all Contact Types will be selected, but this may be +# overridden by passing 'classnums' get/post values. If no contact +# types have been defined, the option will not be presented. + +my @active_classes = qsearch(contact_class => {disabled => ''} ); + +my %classnum_ischecked; +my %dest_ischecked; -foreach my $class (qsearch('contact_class', { disabled => '' })) { - push @contact_checkboxes, [ - $class->classnum, - { label => $class->classname } - ]; +$CGI::LIST_CONTEXT_WARN = 0; +if ( my @in_classnums = $cgi->param('classnums') ) { + # Set checked boxes from form input + for my $v (@in_classnums) { + + if ( $v =~ /^\d+$/ ) { + $classnum_ischecked{$v} = 1 + } elsif ( $v =~ /^(invoice|message)$/ ) { + $dest_ischecked{$v} = 1; + } + + } +} else { + # Checked boxes default values + $classnum_ischecked{$_->classnum} = 1 for @active_classes; + $classnum_ischecked{0} = 1; } +# At least one destination is required +$dest_ischecked{message} = 1 unless %dest_ischecked; + +my @optin_checkboxes = ( + [ 'message' => { label => 'Message recipients' } ], + [ 'invoice' => { label => 'Invoice recipients' } ], +); +my @classnum_checkboxes = ( + [ '0' => { label => '(None)' }], + map { [ $_->classnum => {label => $_->classname} ] } @active_classes, +); + </%init> diff --git a/httemplate/pref/pref-process.html b/httemplate/pref/pref-process.html index 75e57958f..1b18d2ec8 100644 --- a/httemplate/pref/pref-process.html +++ b/httemplate/pref/pref-process.html @@ -1,9 +1,15 @@ % if ( $error ) { % $cgi->param('error', $error); -<% $cgi->redirect(popurl(1). "pref.html?". $cgi->query_string ) %> + <% $cgi->redirect(popurl(1). "pref.html?". $cgi->query_string ) %> % } else { -<% include('/elements/header.html', mt('Preferences updated')) %> -<% include('/elements/footer.html') %> + <% $cgi->redirect( -uri => popurl(1). "pref.html", + -cookie => CGI::Cookie->new( + -name => 'freeside_status', + -value => mt('Preferences updated'), + -expires => '+5m', + ), + ) + %> % } <%init> diff --git a/httemplate/pref/pref.html b/httemplate/pref/pref.html index abd1ea57f..56fde6d44 100644 --- a/httemplate/pref/pref.html +++ b/httemplate/pref/pref.html @@ -1,5 +1,7 @@ <& /elements/header.html, mt('Preferences for [_1]', $FS::CurrentUser::CurrentUser->username) &> +% my $js_form_validate = { 'pref_form' => { 'name' => 'pref_form' } }; + <FORM METHOD="POST" NAME="pref_form" ACTION="pref-process.html"> <& /elements/error.html &> @@ -143,10 +145,14 @@ </TD> </TR> +% my $validate_field_cve = 'customer_view_emails'; +% $js_form_validate->{pref_form}->{validate_fields}{$validate_field_cve} = 'digits: true'; +% $js_form_validate->{pref_form}->{error_message}{$validate_field_cve} = 'Please only enter numbers here.'; + <TR> <TH ALIGN="right"><% emt("How many recent outbound emails to show in customer view") %></TH> <TD ALIGN="left" COLSPAN=2> - <INPUT TYPE="text" NAME="customer_view_emails" VALUE="<% $curuser->option('customer_view_emails') %>"></TD> + <INPUT TYPE="text" ID="<% $validate_field_cve %>" NAME="<% $validate_field_cve %>" VALUE="<% $curuser->option('customer_view_emails') %>"></TD> </TD> </TR> @@ -260,7 +266,11 @@ <INPUT TYPE="submit" VALUE="<% emt("Update preferences") %>"> -<&/elements/footer.html &> +% my %footerdata = ( +% 'formvalidation' => $js_form_validate, +% ); +<% include("/elements/footer.html", %footerdata) %> + <%init> my $curuser = $FS::CurrentUser::CurrentUser; diff --git a/httemplate/search/contact.html b/httemplate/search/contact.html index 5f02fef2f..aaa591cf4 100644 --- a/httemplate/search/contact.html +++ b/httemplate/search/contact.html @@ -1,126 +1,258 @@ <& elements/search.html, - title => 'Contacts', + title => emt('Contacts'), name_singular => 'contact', - query => { select => join(', ', @select), - table => 'contact', - addl_from => $addl_from, - hashref => \%hash, - extra_sql => $extra_sql, - }, - count_query => "SELECT COUNT(*) FROM contact $addl_from $extra_sql", #XXX - header => \@header, - fields => \@fields, - links => \@links, + query => { + select => join(', ', @select), + table => $link, + addl_from => $addl_from, + hashref => {}, + extra_sql => "WHERE $extra_sql", + order_by => "ORDER BY contact_last,contact_first,contact_email_emailaddress" + }, + count_query => " + SELECT COUNT(*) + FROM $link + $addl_from + WHERE $extra_sql + ", + header => \@header, + fields => \@fields, + links => \@links, + html_init => $send_email_link, + agent_virt => 1, + agent_pos => 11, &> <%init> die "access denied" unless $FS::CurrentUser::CurrentUser->access_right('List contacts'); -my @select = 'contact.contactnum AS contact_contactnum'; #if we select it as bare contactnum, the multi-customer listings go away -push @select, map "contact.$_", qw( first last title ); -my %hash = (); -my $addl_from = ''; +my $DEBUG = 0; -my $email_sub = sub { - my $contact = shift; - #can't because contactnum is in the wrong field #my @contact_email = $contact->contact_email; - my @contact_email = qsearch('contact_email', { 'contactnum' => $contact->contact_contactnum } ); - join(', ', map $_->emailaddress, @contact_email); -}; +# Catch classnum values from multi-select box +# A classnum of 0 indicates to include rows where classnum IS NULL +$CGI::LIST_CONTEXT_WARN = 0; +my @classnum = grep{ /^\d+$/ && $_ > 0 } $cgi->param('classnum'); +my $classnum_null = grep{ $_ eq 0 } $cgi->param('classnum'); -my $work_phone_sub = sub { - my $contact = shift; - my $phone_type = qsearchs('phone_type', { 'typename' => 'Work' }); - #can't because contactnum is in the wrong field - my @contact_workphone = qsearch('contact_phone', { 'contactnum' => $contact->contact_contactnum, 'phonetypenum' => $phone_type->phonetypenum } ); - join(', ', map $_->phonenum, @contact_workphone); -}; +# Catch destination values from dest multi-checkbox, default to message +# irrelevant to prospect contacts +my @dest = grep{ /^(message|invoice)$/ } $cgi->param('dest'); +@dest = ('message') unless @dest; -my $mobile_phone_sub = sub { - my $contact = shift; - my $phone_type = qsearchs('phone_type', { 'typename' => 'Mobile' }); - #can't because contactnum is in the wrong field - my @contact_mobilephone = qsearch('contact_phone', { 'contactnum' => $contact->contact_contactnum, 'phonetypenum' => $phone_type->phonetypenum } ); - join(', ', map $_->phonenum, @contact_mobilephone); -}; +# Cache the contact_class table +my %classname = + map {$_->classnum => $_->classname} + qsearch(contact_class => {disabled => ''}); -my $home_phone_sub = sub { - my $contact = shift; - my $phone_type = qsearchs('phone_type', { 'typename' => 'Home' }); - #can't because contactnum is in the wrong field - my @contact_homephone = qsearch('contact_phone', { 'contactnum' => $contact->contact_contactnum, 'phonetypenum' => $phone_type->phonetypenum } ); - join(', ', map $_->phonenum, @contact_homephone); -}; +# This data structure is used to generate the sql query parameters +my %colmap = ( + # These are included regardless of which tables we're viewing + common => { + cols => { + contact => [qw/first last title contactnum/], + contact_email => [qw/emailaddress/], + }, + joinsql => "", + }, -my $link; #for closure in this sub, we'll define it later -my $contact_classname_sub = sub { - my $contact = shift; - my %hash = ( 'contactnum' => $contact->contact_contactnum ); - my $X_contact; - if ( $link eq 'cust_main' ) { - $X_contact = qsearchs('cust_contact', { %hash, 'custnum' => $contact->custnum } ); - } elsif ( $link eq 'prospect_main' ) { - $X_contact = qsearchs('prospect_contact', { %hash, 'prospectnum' => $contact->prospectnum } ); - } else { - die 'guru meditation #5555'; - } - $X_contact->contact_classname; -}; + # These are included if we're viewing customer records + cust_main => { + cols => { + cust_main => [qw/first last company/], + cust_contact => [qw/ + custnum classnum invoice_dest message_dest selfservice_access comment + /], + }, + joinsql => " + LEFT JOIN cust_contact + ON (cust_main.custnum = cust_contact.custnum) + LEFT JOIN contact + on (cust_contact.contactnum = contact.contactnum) + LEFT JOIN contact_email + ON (cust_contact.contactnum = contact_email.contactnum) + ", + }, -my @header = ( 'First', 'Last', 'Title', 'Email', 'Work Phone', 'Mobile Phone', 'Home Phone', 'Type' ); -my @fields = ( 'first', 'last', 'title', $email_sub, $work_phone_sub, $mobile_phone_sub, $home_phone_sub, $contact_classname_sub ); -my @links = ( '', '', '', '', '', '', '', '', ); + # These are included if we're viewing prospect records + prospect_main => { + cols => { + prospect_main => [qw/company/], + prospect_contact => [qw/prospectnum classnum comment/], + }, + joinsql => " + LEFT JOIN prospect_contact + ON (prospect_main.prospectnum = prospect_contact.prospectnum) + LEFT JOIN contact + on (prospect_contact.contactnum = contact.contactnum) + LEFT JOIN contact_email + ON (prospect_contact.contactnum = contact_email.contactnum) + ", + }, +); -my $company_link = ''; +my @select; +my $addl_from; +my $extra_sql; +my $hashref; +my $link = $cgi->param('link'); # cust_main or prospect_main -if ( $cgi->param('selfservice_access') eq 'Y' ) { - $hash{'selfservice_access'} = 'Y'; -} +push @select,'agentnum'; + +# this shouldn't happen without funny-busines +die "Invalid \$link type ($link)" + unless $link eq 'cust_main' || $link eq 'prospect_main'; -my $extra_sql = ''; -$link = $cgi->param('link'); -if ( $link ) { - - my $as = ') AS prospect_or_customer'; - - if ( $link eq 'cust_main' ) { - push @header, 'Customer'; - push @select, - "COALESCE( cust_main.company, cust_main.first||' '||cust_main.last $as", - map "cust_contact.$_", qw( custnum classnum comment selfservice_access ); - $addl_from = - ' LEFT JOIN cust_contact USING ( contactnum ) '. - ' LEFT JOIN cust_main ON ( cust_contact.custnum = cust_main.custnum )'; - $extra_sql = ' cust_contact.custnum IS NOT NULL '; - $company_link = [ $p.'view/cust_main.cgi?', 'custnum' ]; - } elsif ( $link eq 'prospect_main' ) { - push @header, 'Prospect'; - push @select, - "COALESCE( prospect_main.company, contact.first||' '||contact.last $as", - map "prospect_contact.$_", qw( prospectnum classnum comment ); - $addl_from = - ' LEFT JOIN prospect_contact USING ( contactnum ) '. - ' LEFT JOIN prospect_main ON ( prospect_contact.prospectnum = prospect_main.prospectnum )'; - $extra_sql = ' prospect_contact.prospectnum IS NOT NULL '; - $company_link = [ $p.'view/prospect_main.html?', 'prospectnum' ]; - } else { - die "don't know how to report on contacts linked to specified table"; +# Build @select and $addl_from +for my $key ('common', $link) { + $addl_from .= $colmap{$key}->{joinsql}; + my $cols = $colmap{$key}->{cols}; + for my $tbl (keys %{$cols}) { + push @select, map{ "$tbl.$_ AS ${tbl}_$_" } @{$cols->{$tbl}}; } +} - #because right now its harder to show it for both kinds of contacts - push @fields, 'prospect_or_customer'; - push @links, $company_link; +# Filter for Contact Type +if (@classnum || $classnum_null) { + my @stm; + my $tbl = $link eq 'cust_main' ? 'cust_contact' : 'prospect_contact'; + push @stm, "${tbl}.classnum IN (".join(',',@classnum).')' if @classnum; + push @stm, "${tbl}.classnum IS NULL" if $classnum_null; + $extra_sql .= " (" . join(' OR ',@stm) . ') '; +} +# Filter for destination +if (@dest && $link eq 'cust_main') { + my @stm; + push @stm, "cust_contact.${_}_dest IS NOT NULL" for @dest; + $extra_sql .= "\nAND (".join(' OR ',@stm).') '; } -push @header, 'Self-service'; -push @fields, 'selfservice_access'; +if ($DEBUG) { + print "<pre>\n"; + print "select \n"; + print join ",\n",@select; + print "\n"; + print "from $link \n"; + print "$addl_from\n"; + print "WHERE \n $extra_sql\n"; + print "</pre>\n"; +} + +# Prepare to display phone numbers +# adds 3 additional queries per table record :-( +my %phonetype = (qw/1 Work 2 Home 3 Mobile 4 Fax/); +my %phoneid = (qw/Work 1 Home 2 Mobile 3 Fax 4/); +my $get_phone_sub = sub { + my $type = shift; + return sub { + my $rec = shift; + my @p = qsearch('contact_phone', { + contactnum => $rec->contact_contactnum, + phonetypenum => $phoneid{$type} + }); + @p ? (join ', ',map{$_->phonenum} @p) : undef; + }; +}; -push @header, 'Comment'; -push @fields, 'comment'; +# Cache contact types +my %classname = + map {$_->classnum => $_->classname} + qsearch(contact_class => {disabled => ''}); -$extra_sql = (keys(%hash) ? ' AND ' : ' WHERE '). $extra_sql - if $extra_sql; +# And now for something completly different: +my @report = ( + { label => 'First', field => sub { shift->contact_first }}, + { label => 'Last', field => sub { shift->contact_last }}, + { label => 'Title', field => sub { shift->contact_title }}, + { label => 'E-Mail', field => sub { shift->contact_email_emailaddress }}, + { label => 'Work Phone', field => $get_phone_sub->('Work') }, + { label => 'Mobile Phone', field => $get_phone_sub->('Mobile') }, + { label => 'Home Phone', field => $get_phone_sub->('Home') }, + { label => 'Type', + field => sub { + my $rec = shift; + if ($rec->cust_contact_custnum) { + return $rec->cust_contact_classnum + ? $classname{$rec->cust_contact_classnum} + : undef; + } else { + return $rec->prospect_contact_classnum + ? $classname{$rec->prospect_contact_classnum} + : undef; + } + }}, + { label => 'Send Invoices', + field => sub { + my $rec = shift; + return 'N/A' if $rec->prospect_contact_prospectnum; + $rec->cust_contact_invoice_dest ? 'Y' : 'N'; + }}, + { label => 'Send Messages', + field => sub { + my $rec = shift; + return 'N/A' if $rec->prospect_contact_prospectnum; + $rec->cust_contact_message_dest ? 'Y' : 'N'; + }}, + { label => 'Customer', + link => sub { + my $rec = shift; + $rec->cust_main_custnum + ? ["${p}view/cust_main.cgi?", 'cust_main_custnum' ] + : ["${p}view/prospect_main.html?", 'prospect_main_prospectnum' ]; + }, + field => sub { + my $rec = shift; + if ($rec->prospect_contact_prospectnum) { + return $rec->contact_company + || $rec->contact_last.' '.$rec->contact_first; + } + $rec->cust_main_company || $rec->cust_main_last.' '.$rec->cust_main_first; + }}, + { label => 'Self-service', + field => sub { + my $rec = shift; + return 'N/A' if $rec->prospect_contact_prospectnum; + $rec->cust_contact_selfservice_access ? 'Y' : 'N'; + }}, + { label => 'Comment', + field => sub { + my $rec = shift; + $rec->prospect_contact_prospectnum + ? $rec->prospect_contact_comment + : $rec->cust_contact_comment; + }}, +); + +my (@header, @fields, @links); +for my $col (@report) { + push @header, emt($col->{label}); + push @fields, $col->{field}; + push @links, ($col->{link} || ""); +} + +my $classnum_url_part; +if (@classnum) { + $classnum_url_part = join '', map{ "&classnums=$_" } @classnum, @dest; + $classnum_url_part .= '&classnums=0' if $classnum_null; +} + +# E-mail pipeline, from email-customers.html through to email queue job, +# doesn't support cust_prospect table +my $send_email_link = undef; +if ($link eq 'cust_main') { + $send_email_link = + "<a href=\"${fsurl}misc/email-customers.html?". + 'table=cust_main'. + '&agentnum='.$cgi->param('agentnum'). + '&POST=on'. + '&all_pkg_classnums=0'. + '&all_tags=0'. + '&any_pkg_status=0'. + '&refnum=1'. + '&with_email=on'. + $classnum_url_part. + "\">Email a notice to these customers</a>"; +} </%init> diff --git a/httemplate/search/cust_main.html b/httemplate/search/cust_main.html index 30162506f..0a43a82dd 100755 --- a/httemplate/search/cust_main.html +++ b/httemplate/search/cust_main.html @@ -140,8 +140,14 @@ my $menubar = []; if ( $FS::CurrentUser::CurrentUser->access_right('Bulk send customer notices') ) { + # URI::query_from does not support hashref + # results in: ...&contacts=HASH(0x55e16cb81da8)&... + my %query_hash = %search_hash; + delete $query_hash{contacts} + if exists $query_hash{contacts} && ref $query_hash{contacts}; + my $uri = new URI; - $uri->query_form( \%search_hash ); + $uri->query_form( \%query_hash ); my $query = $uri->query; push @$menubar, emt('Email a notice to these customers') => diff --git a/httemplate/search/elements/grid-report.html b/httemplate/search/elements/grid-report.html index 98e81785f..b1e543012 100644 --- a/httemplate/search/elements/grid-report.html +++ b/httemplate/search/elements/grid-report.html @@ -161,7 +161,7 @@ as <A HREF="<% "$myself;_type=xls" %>">Excel spreadsheet</A><BR> .shaded { background-color: #c8c8c8; } .totalshaded { background-color: #bfc094; } </style> -<table class="report" width="100%" cellspacing=0> +<table class="<% $table_class %>" width="<% $table_width %>" cellspacing=0> % foreach my $rowinfo (@rows) { <tr<% $rowinfo->{class} ? ' class="'.$rowinfo->{class}.'"' : ''%>> % my $thisrow = shift @cells; @@ -172,7 +172,11 @@ as <A HREF="<% "$myself;_type=xls" %>">Excel spreadsheet</A><BR> % $style .= " rowspan=".$cell->{rowspan} if $cell->{rowspan} > 1; % $style .= " colspan=".$cell->{colspan} if $cell->{colspan} > 1; % $style .= ' class="' . $cell->{class} . '"' if $cell->{class}; +% if ($cell->{bypass_filter}) { + <<%$td%><%$style%>><% $cell->{value} %></<%$td%>> +% } else { <<%$td%><%$style%>><% $cell->{value} |h %></<%$td%>> +% } % } </tr> % } @@ -186,4 +190,6 @@ $title @cells $head => '' $foot => '' +$table_width => "100%" +$table_class => "report" </%args> diff --git a/httemplate/search/elements/search.html b/httemplate/search/elements/search.html index 5762afbe5..18b4c0ec5 100644 --- a/httemplate/search/elements/search.html +++ b/httemplate/search/elements/search.html @@ -119,7 +119,11 @@ Example: #(query needs to be a qsearch hashref and # header & fields need to be defined) - #handling agent virtualization + # Agent Virtualization parameters: + # In this context, only available if your selected table has agentnum. + # You must also include agentnum as a SELECT column in your SQL query, + # or experience non-obvious problems + # 'agent_virt' => 1, # set true if this search should be # agent-virtualized 'agent_null' => 1, # set true to view global records always diff --git a/httemplate/search/future_autobill.html b/httemplate/search/future_autobill.html new file mode 100644 index 000000000..711a25f82 --- /dev/null +++ b/httemplate/search/future_autobill.html @@ -0,0 +1,189 @@ +<%doc> + +Report listing upcoming auto-bill transactions + +Spec requested the ability to run this report with a longer date range, +and see which charges will process on which day. Checkbox multiple_billing_dates +enables this functionality. + +Performance: +This is a dynamically generated report. The time this report takes to run +will depends on the number of customers. Installations with a high number +of auto-bill customers may find themselves unable to run this report +because of browser timeout. Report could be implemented as a queued job if +necessary, to solve the performance problem. + +</%doc> +<& elements/grid-report.html, + title => 'Upcoming auto-bill transactions', + rows => \@rows, + cells => \@cells, + table_width => "", + table_class => 'gridreport', + head => ' + <style type="text/css"> + table.gridreport { margin: .5em; border: solid 1px #aaa; } + th.gridreport { background-color: #ccc; } + tr.gridreport:nth-child(even) { background-color: #eee; } + tr.gridreport:nth-child(odd) { background-color: #fff; } + td.gridreport { margin: 0 .2em; padding: 0 .4em; } + </style> + ', +&> + +<%init> + +use FS::UID qw( dbh myconnect ); + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Financial reports'); + + my $target_dt; + my @target_dates; + + # Work with all date/time operations @ 12 noon + my %noon = ( + hour => 12, + minute => 0, + second => 0 + ); + + my $now_dt = DateTime->now; + $now_dt = DateTime->new( + month => $now_dt->month, + day => $now_dt->day, + year => $now_dt->year, + %noon, + ); + + # Get target date from form + if ($cgi->param('target_date')) { + my ($mm, $dd, $yy) = split /[\-\/]/,$cgi->param('target_date'); + $target_dt = DateTime->new( + month => $mm, + day => $dd, + year => $yy, + %noon, + ) if $mm && $dd & $yy; + + # Catch a date from the past: time only travels in one direction + $target_dt = undef if $target_dt->epoch < $now_dt->epoch; + } + + # without a target date, default to tomorrow + unless ($target_dt) { + $target_dt = DateTime->from_epoch( epoch => time() + 86400) ; + $target_dt = DateTime->new( + month => $target_dt->month, + day => $target_dt->day, + year => $target_dt->year, + %noon + ); + } + + # If multiple_billing_dates checkbox selected, create a range of dates + # from today until the given report date. Otherwise, use target date only. + if ($cgi->param('multiple_billing_dates')) { + my $walking_dt = DateTime->from_epoch(epoch => $now_dt->epoch); + until ($walking_dt->epoch > $target_dt->epoch) { + push @target_dates, $walking_dt->epoch; + $walking_dt->add(days => 1); + } + } else { + push @target_dates, $target_dt->epoch; + } + + # List all customers with an auto-bill method + # + # my %cust_payby = map {$_->custnum => $_} qsearch({ + # table => 'cust_payby', + # hashref => { + # weight => { op => '>', value => '0' }, + # paydate => { op => '>', value => $target_dt->ymd }, + # }, + # order_by => " ORDER BY weight DESC ", + # }); + + # List all customers with an auto-bill method that's not expired + my %cust_payby = map {$_->custnum => $_} qsearch({ + table => 'cust_payby', + hashref => { + weight => { op => '>', value => '0' }, + }, + order_by => " ORDER BY weight DESC ", + extra_sql => " AND ( payby = 'CHEK' OR ( paydate > '".$target_dt->ymd."')) ", + }); + + my %abreport; + my @rows; + + local $@; + local $SIG{__DIE__}; + my $temp_dbh = myconnect(); + eval { # Creating sandbox dbh where all connections are to be rolled back + local $FS::UID::dbh = $temp_dbh; + local $FS::UID::AutoCommit = 0; + + # Generate report data into @rows + for my $custnum (keys %cust_payby) { + my $cust_main = qsearchs('cust_main', {custnum => $custnum}); + + # walk forward through billing dates + for my $query_epoch (@target_dates) { + my $return_bill = []; + + eval { # Don't let an error on one customer crash the report + my $error = $cust_main->bill( + time => $query_epoch, + return_bill => $return_bill, + no_usage_reset => 1, + ); + die "$error (simulating future billing)" if $error; + }; + warn ("$@: (future_autobill custnum:$custnum)"); + + if (@{$return_bill}) { + my $inv = $return_bill->[0]; + push @rows,{ + name => $cust_main->name, + _date => $inv->_date, + cells => [ + { class => 'gridreport', value => $custnum }, + { class => 'gridreport', + value => '<a href="/view/cust_main.cgi?"'.$custnum.'">'.$cust_main->name.'</a>', + bypass_filter => 1, + }, + { class => 'gridreport', value => $inv->charged, format => 'money' }, + { class => 'gridreport', value => DateTime->from_epoch(epoch=>$inv->_date)->ymd }, + { class => 'gridreport', value => ($cust_payby{$custnum}->payby || $cust_payby{$custnum}->paytype) }, + { class => 'gridreport', value => $cust_payby{$custnum}->paymask }, + ] + }; + } + + } + $temp_dbh->rollback; + } # /foreach $custnum + + }; # /eval + warn("$@") if $@; + + # Sort output by date, and format for output to grid-report.html + my @cells = [ + # header row + { class => 'gridreport', value => '#', header => 1 }, + { class => 'gridreport', value => 'Name', header => 1 }, + { class => 'gridreport', value => 'Amount', header => 1 }, + { class => 'gridreport', value => 'Date', header => 1 }, + { class => 'gridreport', value => 'Type', header => 1 }, + { class => 'gridreport', value => 'Account', header => 1 }, + ]; + push @cells, + map { $_->{cells} } + sort { $a->{_date} <=> $b->{_date} || $a->{name} cmp $b->{name} } + @rows; + + # grid-report.html requires a parallel @rows parameter to accompany @cells + @rows = map { {class => 'gridreport'} } 1..scalar(@cells); + +</%init> diff --git a/httemplate/search/report_contact.html b/httemplate/search/report_contact.html index 3583bb428..048fefb7a 100644 --- a/httemplate/search/report_contact.html +++ b/httemplate/search/report_contact.html @@ -10,17 +10,34 @@ 'disable_empty' => 0, &> +% # Selecting contacts and prospects at the same time has been sacrificed +% # for agent virtualization <& /elements/tr-select.html, - 'label' => 'Contact source', #??? not "type" - contacts have a type + 'label' => 'Contact source:', 'field' => 'link', - 'options' => [ 'prospect_main', 'cust_main', '' ], + 'options' => [ 'prospect_main', 'cust_main' ], 'labels' => { 'prospect_main' => 'Prospect contacts', 'cust_main' => 'Customer contacts', - '' => 'All contacts', }, 'curr_value' => scalar( $cgi->param('link') ), &> + <& /elements/tr-checkbox-multiple.html, + label => emt('Destinations').':', + field => 'dest', + options => [ 'message', 'invoice' ], + labels => { + invoice => 'Invoice recipients', + message => 'Message recipients', + }, + value => { message => 1 }, + &> + + <& /elements/tr-select-multiple-contact_class.html, + label => emt('Contact Type').':', + field => 'classnum', + &> + </FORM> </TABLE> diff --git a/httemplate/search/report_future_autobill.html b/httemplate/search/report_future_autobill.html new file mode 100644 index 000000000..1a0c9f48a --- /dev/null +++ b/httemplate/search/report_future_autobill.html @@ -0,0 +1,42 @@ +<%doc> + +Display date selector for the future_autobill.html report + +</%doc> +<% include('/elements/header.html', 'Future Auto-Bill Transactions' ) %> + + +<FORM ACTION="future_autobill.html" METHOD="GET"> +<TABLE> +<& /elements/tr-input-date-field.html, + { + name => 'target_date', + value => $target_date, + label => emt('Target billing date').': ', + required => 1 + } +&> + +<& /elements/tr-checkbox.html, + 'label' => emt('Multiple billing dates (slow)').': ', + 'field' => 'multiple_billing_dates', + 'value' => '1', +&> + +</TABLE> + +<BR> +<INPUT TYPE="submit" VALUE="<% mt('Get Report') |h %>"> + +</FORM> + +<% include('/elements/footer.html') %> + +<%init> + +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Financial reports'); + +my $target_date = DateTime->from_epoch(epoch=>(time()+86400))->mdy('/'); + +</%init> diff --git a/httemplate/view/cust_main/contacts_new.html b/httemplate/view/cust_main/contacts_new.html index 94488670d..fe412cc00 100644 --- a/httemplate/view/cust_main/contacts_new.html +++ b/httemplate/view/cust_main/contacts_new.html @@ -10,6 +10,7 @@ <%$th%>Contact</TH> <%$th%>Email</TH> <%$th%>Send invoices</TH> + <%$th%>Send messages</TH> <%$th%>Self-service</TH> % foreach my $phone_type (@phone_type) { <%$th%><% $phone_type->typename |h %></TH> @@ -32,6 +33,7 @@ % my @contact_email = $contact->contact_email; <%$td%><% join(', ', map $_->emailaddress, @contact_email) %></TD> <%$td%><% $cust_contact->invoice_dest eq 'Y' ? 'Yes' : 'No' %></TD> + <%$td%><% $cust_contact->message_dest eq 'Y' ? 'Yes' : 'No' %></TD> <%$td%> % if ( $cust_contact->selfservice_access ) { Enabled |