'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',
'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
=item agent_invid - legacy invoice number
+=item promised_date - customer promised payment date, for collection
+
=back
=head1 METHODS
}
+ #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'
}
+ # 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 ) {
--- /dev/null
+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;
--- /dev/null
+<& /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>
--- /dev/null
+<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>
$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') ) .
&>
% }
+% 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>
</TR>
% }
+
</TABLE>
<BR>
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 = ();
% }
+% 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) ) %>
+ ( <% $onclick %><% mt('change') |h %></A> )
+ <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>
)
);
+$opt{'date_format'} ||= '%m/%d/%Y';
+
#legacy invoices
foreach my $legacy_cust_bill ($cust_main->legacy_cust_bill) {
push @history, {
-<% $link %><% $invoice %><% $link ? '</A>' : '' %><% $delete %><% $events %>
+<% $link %><% $invoice %><% $link ? '</A>' : '' %><% $delete %><% $under %>
<%init>
my( $cust_bill, %opt ) = @_;
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;
|| $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>