summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--FS/FS/Conf.pm7
-rw-r--r--FS/FS/Schema.pm1
-rw-r--r--FS/FS/cust_bill.pm11
-rw-r--r--FS/FS/cust_bill_ApplicationCommon.pm11
-rw-r--r--FS/FS/part_event/Condition/cust_bill_past_promised.pm48
-rw-r--r--httemplate/misc/cust_bill-promised_date.html19
-rw-r--r--httemplate/misc/process/cust_bill-promised_date.html19
-rwxr-xr-xhttemplate/search/cust_bill.html21
-rw-r--r--httemplate/search/report_cust_bill.html19
-rwxr-xr-xhttemplate/view/cust_bill.cgi21
-rw-r--r--httemplate/view/cust_main/payment_history.html2
-rw-r--r--httemplate/view/cust_main/payment_history/invoice.html23
12 files changed, 193 insertions, 9 deletions
diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm
index 3987fac..fcdcd57 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 51daf44..18df708 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 ef6dc7b..41ff6aa 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 afb90f4..cadb8a7 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 0000000..e861cb4
--- /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 0000000..7b7b960
--- /dev/null
+++ b/httemplate/misc/cust_bill-promised_date.html
@@ -0,0 +1,19 @@
+<& /elements/header-popup.html, 'Edit promised date' &>
+<FORM method="POST" action="process/cust_bill-promised_date.html">
+<B><% emt('Invoice #[_1]', $invnum) %></B><BR>
+<% ntable('cccccc',2) %>
+<INPUT TYPE="hidden" NAME="invnum" VALUE="<%$invnum%>">
+<& /elements/tr-input-date-field.html,
+ 'promised_date',
+ $cust_bill->promised_date,
+ emt('Promised date'),
+&>
+</TABLE>
+<INPUT TYPE="submit" NAME="submit" VALUE="<% emt('Set date') %>">
+</FORM>
+<& /elements/footer.html &>
+<%init>
+my ($invnum) = $cgi->keywords;
+$invnum =~ /^\d+$/ or die "Illegal invnum";
+my $cust_bill = qsearchs('cust_bill', { invnum => $invnum });
+</%init>
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 0000000..298b130
--- /dev/null
+++ b/httemplate/misc/process/cust_bill-promised_date.html
@@ -0,0 +1,19 @@
+<SCRIPT TYPE="text/javascript">window.top.location.reload()</SCRIPT>
+<%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
+</%init>
diff --git a/httemplate/search/cust_bill.html b/httemplate/search/cust_bill.html
index 4117112..813f9b8 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 0f0d91b..51618fb 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') ) {
<TR>
+
+ <TD ALIGN="right" STYLE="vertical-align:text-top">
+ <% emt('Promised payment date:') %></TD>
+ <TD>
+ <INPUT TYPE="checkbox" NAME="promised_date" CHECKED VALUE="null">
+ <% emt('None') %> <BR>
+ <INPUT TYPE="checkbox" NAME="promised_date" CHECKED VALUE="past">
+ <% emt('In the past') %><BR>
+ <INPUT TYPE="checkbox" NAME="promised_date" CHECKED VALUE="future">
+ <% emt('In the future') %><BR>
+ </TD>
+ </TR>
+% }
+
+<TR>
<TD ALIGN="right"><INPUT TYPE="checkbox" NAME="open" VALUE="1" CHECKED></TD>
<TD><% mt('Show only open invoices') |h %></TD>
</TR>
@@ -49,6 +65,7 @@
</TR>
% }
+
</TABLE>
<BR>
@@ -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 2ce294e..a8b4ac1 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 = '<A HREF="#" onclick="'.$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) ) %>
+ (&nbsp;<% $onclick %><% mt('change') |h %></A>&nbsp;)
+ <BR><BR>
+% }
+% elsif ( $cust_bill->owed > 0 ) {
+ <% $onclick %><% mt('Set promised payment date' ) |h %></A>
+ <BR><BR>
+% }
+% }
+
% if ( $curuser->access_right('Resend invoices') ) {
<A HREF="<% $p %>misc/send-invoice.cgi?method=print;<% $link %>"><% mt('Re-print this invoice') |h %></A>
diff --git a/httemplate/view/cust_main/payment_history.html b/httemplate/view/cust_main/payment_history.html
index 467c3bc..63708e6 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 d7ee004..3028f0f 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 ? '</A>' : '' %><% $delete %><% $events %>
+<% $link %><% $invoice %><% $link ? '</A>' : '' %><% $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 = '<B><FONT SIZE="+1" COLOR="#FF0000">' .
+
+my $under = '';
+if ( $cust_bill->owed > 0 ) {
+ $invoice = '<B><FONT SIZE="+1" COLOR="#FF0000">' .
emt("Open Invoice #[_1] (Balance [_2])",$cust_bill->display_invnum,$cust_bill->owed) .
- '</FONT></B>'
-if ( $cust_bill->owed > 0 );
+ '</FONT></B>';
+ if ( $cust_bill->promised_date ) {
+ $under .= '<BR>'. 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!<BR><FONT SIZE="-1"><A HREF="${p}search/cust_event.html?invnum=$invnum!.
- '">( '.emt('View invoice events').' )</A></FONT>';
+ $under .=
+ qq!<BR><A HREF="${p}search/cust_event.html?invnum=$invnum">( !.
+ emt('View invoice events').' )</A>';
}
-#
+$under = '<FONT SIZE="-1">'.$under.'</FONT>' if length($under);
</%init>