use SL::DB::BankAccount;
use SL::DBUtils qw(like);
use SL::Presenter;
+
+use List::MoreUtils qw(any);
use List::Util qw(max);
use Rose::Object::MakeMethods::Generic
my $bank_transaction = $data{bank_transaction};
my $sign = $bank_transaction->amount < 0 ? -1 : 1;
my $amount_of_transaction = $sign * $bank_transaction->amount;
+ my $payment_received = $bank_transaction->amount > 0;
+ my $payment_sent = $bank_transaction->amount < 0;
- my @invoices;
foreach my $invoice_id (@{ $params{invoice_ids} }) {
my $invoice = SL::DB::Manager::Invoice->find_by(id => $invoice_id) || SL::DB::Manager::PurchaseInvoice->find_by(id => $invoice_id);
if (!$invoice) {
};
}
- push @invoices, $invoice;
+ push @{ $data{invoices} }, $invoice;
}
- $data{invoices} = \@invoices;
+ if ( $payment_received
+ && any { ( $_->is_sales && ($_->amount < 0))
+ || (!$_->is_sales && ($_->amount > 0))
+ } @{ $data{invoices} }) {
+ return {
+ %data,
+ result => 'error',
+ message => $::locale->text("Received payments can only be posted for sales invoices and purchase credit notes."),
+ };
+ }
- @invoices = sort { return 1 if ( $a->is_sales and $a->amount > 0);
- return 1 if (!$a->is_sales and $a->amount < 0);
- return -1;
- } @invoices if $bank_transaction->amount > 0;
- @invoices = sort { return -1 if ( $a->is_sales and $a->amount > 0);
- return -1 if (!$a->is_sales and $a->amount < 0);
- return 1;
- } @invoices if $bank_transaction->amount < 0;
+ if ( $payment_sent
+ && any { ( $_->is_sales && ($_->amount > 0))
+ || (!$_->is_sales && ($_->amount < 0))
+ } @{ $data{invoices} }) {
+ return {
+ %data,
+ result => 'error',
+ message => $::locale->text("Sent payments can only be posted for purchase invoices and sales credit notes."),
+ };
+ }
- my $max_invoices = scalar(@invoices);
+ my $max_invoices = scalar(@{ $data{invoices} });
my $n_invoices = 0;
- foreach my $invoice (@invoices) {
+ foreach my $invoice (@{ $data{invoices} }) {
$n_invoices++ ;
+
# Check if bank_transaction already has a link to the invoice, may only be linked once per invoice
# This might be caused by the user reloading a page and resending the form
if (_existing_record_link($bank_transaction, $invoice)) {
};
}
- if ($amount_of_transaction == 0) {
- push @warnings, {
+ if (!$amount_of_transaction && $invoice->open_amount) {
+ return {
%data,
- result => 'warning',
- message => $::locale->text('There are invoices which could not be paid by bank transaction #1 (Account number: #2, bank code: #3)!',
- $bank_transaction->purpose, $bank_transaction->remote_account_number, $bank_transaction->remote_bank_code),
+ result => 'error',
+ message => $::locale->text("A payment can only be posted for multiple invoices if the amount to post is equal to or bigger than the sum of the open amounts of the affected invoices."),
};
- last;
}
my $payment_type;
payment_type => $payment_type,
transdate => $bank_transaction->transdate->to_kivitendo);
} else { # use the whole amount of the bank transaction for the invoice, overpay the invoice if necessary
+ my $overpaid_amount = $amount_of_transaction - $invoice->open_amount;
$invoice->pay_invoice(chart_id => $bank_transaction->local_bank_account->chart_id,
trans_id => $invoice->id,
amount => $amount_of_transaction,
transdate => $bank_transaction->transdate->to_kivitendo);
$bank_transaction->invoice_amount($bank_transaction->amount);
$amount_of_transaction = 0;
+
+ if ($overpaid_amount >= 0.01) {
+ push @warnings, {
+ %data,
+ result => 'warning',
+ message => $::locale->text('Invoice #1 was overpaid by #2.', $invoice->invnumber, $::form->format_amount(\%::myconfig, $overpaid_amount, 2)),
+ };
+ }
}
# Record a record link from the bank transaction to the invoice
}
1;
+__END__
+
+=pod
+
+=encoding utf8
+
+=head1 NAME
+
+SL::Controller::BankTransaction - Posting payments to invoices from
+bank transactions imported earlier
+
+=head1 FUNCTIONS
+
+=over 4
+
+=item C<save_single_bank_transaction %params>
+
+Takes a bank transaction ID (as parameter C<bank_transaction_id> and
+tries to post its amount to a certain number of invoices (parameter
+C<invoice_ids>, an array ref of database IDs to purchase or sales
+invoice objects).
+
+The whole function is wrapped in a database transaction. If an
+exception occurs the bank transaction is not posted at all. The same
+is true if the code detects an error during the execution, e.g. a bank
+transaction that's already been posted earlier. In both cases the
+database transaction will be rolled back.
+
+If warnings but not errors occur the database transaction is still
+committed.
+
+The return value is an error object or C<undef> if the function
+succeeded. The calling function will collect all warnings and errors
+and display them in a nicely formatted table if any occurred.
+
+An error object is a hash reference containing the following members:
+
+=over 2
+
+=item * C<result> — can be either C<warning> or C<error>. Warnings are
+displayed slightly different than errors.
+
+=item * C<message> — a human-readable message included in the list of
+errors meant as the description of why the problem happened
+
+=item * C<bank_transaction_id>, C<invoice_ids> — the same parameters
+that the function was called with
+
+=item * C<bank_transaction> — the database object
+(C<SL::DB::BankTransaction>) corresponding to C<bank_transaction_id>
+
+=item * C<invoices> — an array ref of the database objects (either
+C<SL::DB::Invoice> or C<SL::DB::PurchaseInvoice>) corresponding to
+C<invoice_ids>
+
+=back
+
+=back
+
+=head1 AUTHOR
+
+Niclas Zimmermann E<lt>niclas@kivitendo-premium.deE<gt>,
+Geoffrey Richardson E<lt>information@richardson-bueren.deE<gt>
+
+=cut