From 3ab9b93b970353bfddc44b65bbb79d3aa586ded7 Mon Sep 17 00:00:00 2001 From: mark Date: Thu, 8 Dec 2011 21:13:58 +0000 Subject: [PATCH] promised payment date for invoices, #13554 --- FS/FS/Conf.pm | 7 ++++ FS/FS/Schema.pm | 1 + FS/FS/cust_bill.pm | 11 +++++ FS/FS/cust_bill_ApplicationCommon.pm | 11 +++++ .../Condition/cust_bill_past_promised.pm | 48 ++++++++++++++++++++++ httemplate/misc/cust_bill-promised_date.html | 19 +++++++++ .../misc/process/cust_bill-promised_date.html | 19 +++++++++ httemplate/search/cust_bill.html | 21 +++++++++- httemplate/search/report_cust_bill.html | 19 +++++++++ httemplate/view/cust_bill.cgi | 21 ++++++++++ httemplate/view/cust_main/payment_history.html | 2 + .../view/cust_main/payment_history/invoice.html | 23 +++++++---- 12 files changed, 193 insertions(+), 9 deletions(-) create mode 100644 FS/FS/part_event/Condition/cust_bill_past_promised.pm create mode 100644 httemplate/misc/cust_bill-promised_date.html create mode 100644 httemplate/misc/process/cust_bill-promised_date.html diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm index 3987fac5a..fcdcd5766 100644 --- a/FS/FS/Conf.pm +++ b/FS/FS/Conf.pm @@ -4681,6 +4681,13 @@ and customer address. Include units.', 'description' => 'An alternate ordering of fields for the New Customer and Edit Customer screens.', 'type' => 'checkbox', }, + + { + 'key' => 'cust_bill-enable_promised_date', + 'section' => 'UI', + 'description' => 'Enable display/editing of the "promised payment date" field on invoices.', + 'type' => 'checkbox', + }, { 'key' => 'available-locales', diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm index 8c252031e..c15f393bb 100644 --- a/FS/FS/Schema.pm +++ b/FS/FS/Schema.pm @@ -532,6 +532,7 @@ sub tables_hashref { 'closed', 'char', 'NULL', 1, '', '', #not yet used much 'statementnum', 'int', 'NULL', '', '', '', #invoice aggregate statements 'agent_invid', 'int', 'NULL', '', '', '', #(varchar?) importing legacy + 'promised_date', @date_type, '', '', ], 'primary_key' => 'invnum', 'unique' => [ [ 'custnum', 'agent_invid' ] ], #agentnum? huh diff --git a/FS/FS/cust_bill.pm b/FS/FS/cust_bill.pm index ef6dc7bee..41ff6aaa9 100644 --- a/FS/FS/cust_bill.pm +++ b/FS/FS/cust_bill.pm @@ -144,6 +144,8 @@ Specific use cases =item agent_invid - legacy invoice number +=item promised_date - customer promised payment date, for collection + =back =head1 METHODS @@ -5622,6 +5624,15 @@ sub search_sql_where { } + #promised_date - also has an option to accept nulls + if ( $param->{promised_date} ) { + my($beginning, $ending, $null) = @{$param->{promised_date}}; + + push @search, "(( cust_bill.promised_date >= $beginning AND ". + "cust_bill.promised_date < $ending )" . + ($null ? ' OR cust_bill.promised_date IS NULL ) ' : ')'); + } + #agent virtualization my $curuser = $FS::CurrentUser::CurrentUser; if ( $curuser->username eq 'fs_queue' diff --git a/FS/FS/cust_bill_ApplicationCommon.pm b/FS/FS/cust_bill_ApplicationCommon.pm index afb90f40e..cadb8a796 100644 --- a/FS/FS/cust_bill_ApplicationCommon.pm +++ b/FS/FS/cust_bill_ApplicationCommon.pm @@ -435,6 +435,17 @@ sub apply_to_lineitems { } + # unset promised payment date if there is one + my $cust_bill = $self->cust_bill; + if ( $cust_bill->promised_date and $cust_bill->owed <= 0 ) { + $cust_bill->set('promised_date', ''); + my $error = $cust_bill->replace; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; + } + } + #everything should always be applied to line items in full now... sanity check $applied = sprintf('%.2f', $applied); unless ( $applied == $self->amount ) { diff --git a/FS/FS/part_event/Condition/cust_bill_past_promised.pm b/FS/FS/part_event/Condition/cust_bill_past_promised.pm new file mode 100644 index 000000000..e861cb408 --- /dev/null +++ b/FS/FS/part_event/Condition/cust_bill_past_promised.pm @@ -0,0 +1,48 @@ +package FS::part_event::Condition::cust_bill_past_promised; + +use strict; +use FS::cust_bill; +use Time::Local 'timelocal'; + +use base qw( FS::part_event::Condition ); + +sub description { + 'Promised payment date has passed', +} + +sub eventtable_hashref { + { 'cust_main' => 0, + 'cust_bill' => 1, + 'cust_pkg' => 0, + }; +} + +sub option_fields { + ( + 'delay' => { label => 'Delay additional days', + type => 'text', + value => '0', + }, + ); +} + +sub condition { + # always return true if there is no promised_date + my($self, $cust_bill, %opt) = @_; + + my $delay = $self->option('delay') || 0; + my ($sec,$min,$hour,$mday,$mon,$year) = (localtime($opt{'time'}))[0..5]; + my $as_of = timelocal(0,0,0,$mday,$mon,$year) - $delay * 86400; + $as_of >= ($cust_bill->promised_date || 0); +} + +sub condition_sql { + my( $class, $table, %opt ) = @_; + return 'true' if $opt{'driver_name'} ne 'Pg'; + my $delay = $class->condition_sql_option_integer('delay', 'Pg'); + my ($sec,$min,$hour,$mday,$mon,$year) = (localtime($opt{'time'}))[0..5]; + my $as_of = timelocal(0,0,0,$mday,$mon,$year) . " - ($delay * 86400)"; + "(cust_bill.promised_date IS NULL OR $as_of >= cust_bill.promised_date)"; +} + +1; diff --git a/httemplate/misc/cust_bill-promised_date.html b/httemplate/misc/cust_bill-promised_date.html new file mode 100644 index 000000000..7b7b9601d --- /dev/null +++ b/httemplate/misc/cust_bill-promised_date.html @@ -0,0 +1,19 @@ +<& /elements/header-popup.html, 'Edit promised date' &> +
+<% emt('Invoice #[_1]', $invnum) %>
+<% ntable('cccccc',2) %> + +<& /elements/tr-input-date-field.html, + 'promised_date', + $cust_bill->promised_date, + emt('Promised date'), +&> + + +
+<& /elements/footer.html &> +<%init> +my ($invnum) = $cgi->keywords; +$invnum =~ /^\d+$/ or die "Illegal invnum"; +my $cust_bill = qsearchs('cust_bill', { invnum => $invnum }); + diff --git a/httemplate/misc/process/cust_bill-promised_date.html b/httemplate/misc/process/cust_bill-promised_date.html new file mode 100644 index 000000000..298b13007 --- /dev/null +++ b/httemplate/misc/process/cust_bill-promised_date.html @@ -0,0 +1,19 @@ + +<%init> +# XXX ACL? + +$cgi->param('invnum') =~ /^(\d+)$/ + or die "Illegal invnum"; +my $invnum = $1; + +my $promised_date = ''; +if ( length($cgi->param('promised_date')) ) { + $promised_date = parse_datetime($cgi->param('promised_date')) + or die "Illegal promised_date"; +} + +my $cust_bill = qsearchs('cust_bill', { invnum => $invnum }); +$cust_bill->promised_date($promised_date); +my $error = $cust_bill->replace; +die $error if $error; # nothing fancy here + diff --git a/httemplate/search/cust_bill.html b/httemplate/search/cust_bill.html index 4117112f5..813f9b843 100755 --- a/httemplate/search/cust_bill.html +++ b/httemplate/search/cust_bill.html @@ -128,7 +128,26 @@ if ( $cgi->param('invnum') =~ /^\s*(FS-)?(\d+)\s*$/ ) { $search{'newest_percust'} = 1; $count_query = "SELECT COUNT(DISTINCT cust_bill.custnum), 'N/A', 'N/A'"; } - + + # promised date + my $start_of_day = timelocal(0, 0, 0, (localtime(time))[3,4,5]); + foreach ( $cgi->param('promised_date') ) { + # only if at least one box is checked + $search{promised_date} ||= [ $start_of_day, $start_of_day, 0 ]; + if ($_ eq 'past') { + # accept everything before today + $search{promised_date}[0] = 0; + } + elsif ( $_ eq 'future' ) { + # accept everything after today + $search{promised_date}[1] = 4294967295; + } + elsif ( $_ eq 'null' ) { + # accept nulls + $search{promised_date}[2] = 1; + } + } + my $payby_sql = ''; $payby_sql = ' AND (' . join(' OR ', map { "cust_main.payby = '$_'" } $cgi->param('payby') ) . diff --git a/httemplate/search/report_cust_bill.html b/httemplate/search/report_cust_bill.html index 0f0d91b14..51618fb24 100644 --- a/httemplate/search/report_cust_bill.html +++ b/httemplate/search/report_cust_bill.html @@ -37,7 +37,23 @@ &> % } +% if ( $conf->exists('cust_bill-enable_promised_date') ) { + + + <% emt('Promised payment date:') %> + + + <% emt('None') %>
+ + <% emt('In the past') %>
+ + <% emt('In the future') %>
+ + +% } + + <% mt('Show only open invoices') |h %> @@ -49,6 +65,7 @@ % } +
@@ -62,6 +79,8 @@ die "access denied" unless $FS::CurrentUser::CurrentUser->access_right('List invoices'); +my $conf = new FS::Conf; + my $title = 'Invoice Report'; #false laziness w/report_cust_pkg.html my @title_arg = (); diff --git a/httemplate/view/cust_bill.cgi b/httemplate/view/cust_bill.cgi index 2ce294e9a..a8b4ac15c 100755 --- a/httemplate/view/cust_bill.cgi +++ b/httemplate/view/cust_bill.cgi @@ -58,6 +58,27 @@ % } +% if ( $conf->exists('cust_bill-enable_promised_date') ) { +% my $onclick = include('/elements/popup_link_onclick.html', +% 'action' => $p.'misc/cust_bill-promised_date.html?'.$invnum, +% 'actionlabel' => emt('Set promised payment date'), +% 'width' => 320, +% 'height' => 240, +% ); +% $onclick = ''; +% if ( $cust_bill->promised_date ) { +% my $date_format = $conf->config('date_format') || '%b %o, %Y'; + <% mt('Payment promised by [_1]', + time2str($date_format, $cust_bill->promised_date) ) %> + ( <% $onclick %><% mt('change') |h %> ) +

+% } +% elsif ( $cust_bill->owed > 0 ) { + <% $onclick %><% mt('Set promised payment date' ) |h %> +

+% } +% } + % if ( $curuser->access_right('Resend invoices') ) { <% mt('Re-print this invoice') |h %> diff --git a/httemplate/view/cust_main/payment_history.html b/httemplate/view/cust_main/payment_history.html index 467c3bc0c..63708e63c 100644 --- a/httemplate/view/cust_main/payment_history.html +++ b/httemplate/view/cust_main/payment_history.html @@ -398,6 +398,8 @@ my %opt = ( ) ); +$opt{'date_format'} ||= '%m/%d/%Y'; + #legacy invoices foreach my $legacy_cust_bill ($cust_main->legacy_cust_bill) { push @history, { diff --git a/httemplate/view/cust_main/payment_history/invoice.html b/httemplate/view/cust_main/payment_history/invoice.html index d7ee0047a..3028f0f69 100644 --- a/httemplate/view/cust_main/payment_history/invoice.html +++ b/httemplate/view/cust_main/payment_history/invoice.html @@ -1,4 +1,4 @@ -<% $link %><% $invoice %><% $link ? '' : '' %><% $delete %><% $events %> +<% $link %><% $invoice %><% $link ? '' : '' %><% $delete %><% $under %> <%init> my( $cust_bill, %opt ) = @_; @@ -8,10 +8,17 @@ my $conf = new FS::Conf; my $curuser = $FS::CurrentUser::CurrentUser; my $invoice = emt("Invoice #[_1] (Balance [_2])",$cust_bill->display_invnum,$cust_bill->owed); -$invoice = '' . + +my $under = ''; +if ( $cust_bill->owed > 0 ) { + $invoice = '' . emt("Open Invoice #[_1] (Balance [_2])",$cust_bill->display_invnum,$cust_bill->owed) . - '' -if ( $cust_bill->owed > 0 ); + ''; + if ( $cust_bill->promised_date ) { + $under .= '
'. emt('Payment promised on [_1]', + time2str($opt{'date_format'}, $cust_bill->promised_date)); + } +} #if $cust_bill->owed my $invnum = $cust_bill->invnum; @@ -34,10 +41,10 @@ if ( $cust_bill->num_cust_event || $curuser->access_right('View customer billing events') ) ) { - $events = - qq!
( '.emt('View invoice events').' )'; + $under .= + qq!
( !. + emt('View invoice events').' )'; } -# +$under = ''.$under.'' if length($under); -- 2.11.0