diff options
-rw-r--r-- | FS/FS/Record.pm | 14 | ||||
-rw-r--r-- | FS/FS/cust_pay.pm | 87 | ||||
-rw-r--r-- | FS/FS/payby.pm | 22 | ||||
-rw-r--r-- | httemplate/elements/select-payby.html | 2 | ||||
-rw-r--r-- | httemplate/misc/cust_pay-import.cgi | 52 | ||||
-rw-r--r-- | httemplate/misc/process/cust_pay-import.cgi | 24 |
6 files changed, 163 insertions, 38 deletions
diff --git a/FS/FS/Record.pm b/FS/FS/Record.pm index a1c87769e..88d452ae9 100644 --- a/FS/FS/Record.pm +++ b/FS/FS/Record.pm @@ -1686,6 +1686,7 @@ sub process_batch_import { format_xml_formats => $opt->{format_xml_formats}, format_asn_formats => $opt->{format_asn_formats}, format_row_callbacks => $opt->{format_row_callbacks}, + format_hash_callbacks => $opt->{format_hash_callbacks}, #per-import job => $job, file => $file, @@ -1694,6 +1695,7 @@ sub process_batch_import { params => { map { $_ => $param->{$_} } @pass_params }, #? default_csv => $opt->{default_csv}, + preinsert_callback => $opt->{preinsert_callback}, postinsert_callback => $opt->{postinsert_callback}, insert_args_callback => $opt->{insert_args_callback}, ); @@ -1732,6 +1734,8 @@ Class method for batch imports. Available params: =item format_row_callbacks +=item format_hash_callbacks - After parsing, before object creation + =item fields - Alternate way to specify import, specifying import fields directly as a listref =item preinsert_callback @@ -1773,7 +1777,7 @@ sub batch_import { my( $type, $header, $sep_char, $fixedlength_format, $xml_format, $asn_format, - $parser_opt, $row_callback, @fields ); + $parser_opt, $row_callback, $hash_callback, @fields ); my $postinsert_callback = ''; $postinsert_callback = $param->{'postinsert_callback'} @@ -1829,6 +1833,11 @@ sub batch_import { ? $param->{'format_row_callbacks'}{ $param->{'format'} } : ''; + $hash_callback = + $param->{'format_hash_callbacks'} + ? $param->{'format_hash_callbacks'}{ $param->{'format'} } + : ''; + @fields = @{ $formats->{ $format } }; } elsif ( $param->{'fields'} ) { @@ -1838,6 +1847,7 @@ sub batch_import { $sep_char = ','; $fixedlength_format = ''; $row_callback = ''; + $hash_callback = ''; @fields = @{ $param->{'fields'} }; } else { @@ -2063,6 +2073,8 @@ sub batch_import { $hash{custnum} = $2; } + %hash = &{$hash_callback}(%hash) if $hash_callback; + #my $table = $param->{table}; my $class = "FS::$table"; diff --git a/FS/FS/cust_pay.pm b/FS/FS/cust_pay.pm index c2e0955f7..7edf6f173 100644 --- a/FS/FS/cust_pay.pm +++ b/FS/FS/cust_pay.pm @@ -227,6 +227,12 @@ sub insert { $dbh->rollback if $oldAutoCommit; return "Unknown cust_bill.invnum: ". $self->invnum; }; + if ($self->custnum && ($cust_bill->custnum ne $self->custnum)) { + $dbh->rollback if $oldAutoCommit; + return "Invoice custnum ".$cust_bill->custnum + ." does not match specified custnum ".$self->custnum + ." for invoice ".$self->invnum; + } $self->custnum($cust_bill->custnum ); } @@ -1166,6 +1172,87 @@ sub process_upgrade_paybatch { =over 4 +=item process_batch_import + +=cut + +sub process_batch_import { + my $job = shift; + + #agent_custid isn't a cust_pay field, see hash callback + my $format = [ qw(custnum agent_custid paid payinfo invnum) ]; + my $hashcb = sub { + my %hash = @_; + my $custnum = $hash{'custnum'}; + my $agent_custid = $hash{'agent_custid'}; + #standardize date + $hash{'_date'} = parse_datetime($hash{'_date'}) + if $hash{'_date'} && $hash{'_date'} =~ /\D/; + # translate agent_custid into regular custnum + if ($custnum && $agent_custid) { + die "can't specify both custnum and agent_custid\n"; + } elsif ($agent_custid) { + # here is the agent virtualization + my $extra_sql = ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql; + my $agentnum = $hash{'agentnum'}; + my %search = ( + 'agent_custid' => $agent_custid, + 'agentnum' => $agentnum, + ); + my $cust_main = qsearchs({ + 'table' => 'cust_main', + 'hashref' => \%search, + 'extra_sql' => $extra_sql, + }); + die "can't find customer with agent_custid $agent_custid\n" + unless $cust_main; + $custnum = $cust_main->custnum; + } + #remove custnum_prefix + my $custnum_prefix = $conf->config('cust_main-custnum-display_prefix'); + my $custnum_length = $conf->config('cust_main-custnum-display_length') || 8; + if ( + $custnum_prefix + && $custnum =~ /^$custnum_prefix(0*([1-9]\d*))$/ + && length($1) == $custnum_length + ) { + $custnum = $2; + } + $hash{'custnum'} = $custnum; + delete($hash{'agent_custid'}); + return %hash; + }; + + my $opt = { 'table' => 'cust_pay', + 'params' => [ '_date', 'agentnum', 'payby', 'paybatch' ], + 'formats' => { + 'simple-csv' => $format, + 'simple-xls' => $format, + }, + 'format_types' => { + 'simple-csv' => 'csv', + 'simple-xls' => 'xls', + }, + 'default_csv' => 1, + 'format_hash_callbacks' => { + 'simple-csv' => $hashcb, + 'simple-xls' => $hashcb, + }, + 'postinsert_callback' => sub { + my $cust_pay = shift; + my $cust_main = $cust_pay->cust_main || + return "can't find customer to which payments apply"; + my $error = $cust_main->apply_payments_and_credits; + return $error + ? "can't apply payments to customer ".$cust_pay->custnum."$error" + : ''; + }, + }; + + FS::Record::process_batch_import( $job, $opt, @_ ); + +} + =item batch_import HASHREF Inserts new payments. diff --git a/FS/FS/payby.pm b/FS/FS/payby.pm index 2e2a664df..11bc184d9 100644 --- a/FS/FS/payby.pm +++ b/FS/FS/payby.pm @@ -239,6 +239,28 @@ sub cust_payby2longname { map { $_ => $hash{$_}->{longname} } $self->cust_payby; } +=item payment_payby + +Returns all values of payby that can be used by payments. + +=cut + +sub payment_payby { + my $self = shift; + grep { ! exists $hash{$_}->{cust_pay} } $self->payby; +} + +=item payment_payby2longname + +Returns hash, keys are L</payment_payby> types, values are payby longname. + +=cut + +sub payment_payby2longname { + my $self = shift; + map { $_ => $hash{$_}->{longname} } $self->payment_payby; +} + =back =head1 BUGS diff --git a/httemplate/elements/select-payby.html b/httemplate/elements/select-payby.html index b2d542190..2018874e7 100644 --- a/httemplate/elements/select-payby.html +++ b/httemplate/elements/select-payby.html @@ -3,7 +3,7 @@ <% $onchange %> > -% unless ( $opt{'multiple'} ) { +% unless ( $opt{'multiple'} || $opt{'no_all'} ) { <OPTION VALUE="" <% '' eq $value ? 'SELECTED' : '' %> ><% mt('all') |h %> % } diff --git a/httemplate/misc/cust_pay-import.cgi b/httemplate/misc/cust_pay-import.cgi index 05a6c4fd8..ee0154db5 100644 --- a/httemplate/misc/cust_pay-import.cgi +++ b/httemplate/misc/cust_pay-import.cgi @@ -1,22 +1,31 @@ <& /elements/header.html, 'Batch Payment Import' &> -Import a CSV file containing customer payments. +Import a file containing customer payments. <BR><BR> -<FORM ACTION="process/cust_pay-import.cgi" METHOD="post" ENCTYPE="multipart/form-data"> + +<% include( '/elements/form-file_upload.html', + 'name' => 'OneTrueForm', + 'action' => 'process/cust_pay-import.cgi', #progress-init target + 'fields' => [ 'agentnum', '_date', 'paybatch', 'format', 'payby' ], + 'num_files' => 1, + 'url' => popurl(2)."search/cust_pay.html?magic=paybatch;paybatch=$paybatch", + 'message' => 'Batch Payment Imported', + ) +%> <% &ntable("#cccccc", 2) %> +<INPUT TYPE="hidden" NAME="paybatch" VALUE="<% $paybatch | h %>"> + <& /elements/tr-select-agent.html, - #'curr_value' => '', #$agentnum, 'label' => "<B>Agent</B>", 'empty_label' => 'Select agent', &> <& /elements/tr-input-date-field.html, { 'name' => '_date', - #'value' => '', - 'label' => 'Date', + 'label' => '<B>Date</B>', } &> @@ -24,18 +33,26 @@ Import a CSV file containing customer payments. <TH ALIGN="right">Format</TH> <TD> <SELECT NAME="format"> - <OPTION VALUE="simple">Simple -<!-- <OPTION VALUE="extended" SELECTED>Extended --> + <OPTION VALUE="simple-csv">Comma-separated (.csv)</OPTION> + <OPTION VALUE="simple-xls">Excel (.xls)</OPTION> </SELECT> </TD> </TR> -<TR> - <TH ALIGN="right">CSV filename</TH> - <TD><INPUT TYPE="file" NAME="csvfile"></TD> -</TR> +<% include( '/elements/tr-select-payby.html', + 'paybys' => \%paybys, + 'no_all' => 1, + 'label' => '<B>Payment type</B>', + ) +%> + +<% include( '/elements/file-upload.html', + 'field' => 'file', + 'label' => 'Filename', + ) +%> -<TR><TD COLSPAN=2 ALIGN="center" STYLE="padding-top:6px"><INPUT TYPE="submit" VALUE="Import CSV file"></TD></TR> +<TR><TD COLSPAN=2 ALIGN="center" STYLE="padding-top:6px"><INPUT TYPE="submit" VALUE="Import file"></TD></TR> </TABLE> @@ -43,11 +60,10 @@ Import a CSV file containing customer payments. <BR> -Simple file format is CSV, with the following field order: <i>custnum, agent_custid, amount, checknum</i> +Simple file format is CSV or XLS, with the following field order: <i>custnum, agent_custid, amount, checknum, invnum</i> <BR><BR> -<!-- Extended file format is not yet defined</i> -<BR><BR> --> +<!-- Extended file format is not yet defined --> Field information: @@ -68,3 +84,9 @@ Field information: <BR> <& /elements/footer.html &> + +<%init> +my $paybatch = time2str('webbatch-%Y/%m/%d-%T'. "-$$-". rand() * 2**32, time); +my %paybys; +tie %paybys, 'Tie::IxHash', FS::payby->payment_payby2longname(); +</%init> diff --git a/httemplate/misc/process/cust_pay-import.cgi b/httemplate/misc/process/cust_pay-import.cgi index 7711773d8..2981da46d 100644 --- a/httemplate/misc/process/cust_pay-import.cgi +++ b/httemplate/misc/process/cust_pay-import.cgi @@ -1,23 +1,5 @@ -<% $cgi->redirect(popurl(3). "search/cust_pay.html?magic=paybatch;paybatch=$paybatch") %> +<% $server->process %> <%init> - -my $fh = $cgi->upload('csvfile'); - -# webbatch? I suppose -my $paybatch = time2str('webbatch-%Y/%m/%d-%T'. "-$$-". rand() * 2**32, time); - -my $error = defined($fh) - ? FS::cust_pay::batch_import( { - 'filehandle' => $fh, - 'format' => scalar($cgi->param('format')), - - 'agentnum' => scalar($cgi->param('agentnum')), - '_date' => scalar($cgi->param('_date')), - 'paybatch' => $paybatch, - } ) - : 'No file'; - -errorpage($error) - if ( $error ); - +my $server = new FS::UI::Web::JSRPC 'FS::cust_pay::process_batch_import', $cgi; </%init> + |