use parent qw(Exporter);
our @EXPORT = qw(pay_invoice);
-our @EXPORT_OK = qw(skonto_date skonto_charts amount_less_skonto within_skonto_period percent_skonto reference_account reference_amount transactions open_amount open_percent remaining_skonto_days skonto_amount check_skonto_configuration valid_skonto_amount get_payment_suggestions validate_payment_type open_sepa_transfer_amount get_payment_select_options_for_bank_transaction);
+our @EXPORT_OK = qw(skonto_date skonto_charts amount_less_skonto within_skonto_period percent_skonto reference_account reference_amount open_amount open_percent remaining_skonto_days skonto_amount check_skonto_configuration valid_skonto_amount get_payment_suggestions validate_payment_type open_sepa_transfer_amount get_payment_select_options_for_bank_transaction create_bank_transaction);
our %EXPORT_TAGS = (
"ALL" => [@EXPORT, @EXPORT_OK],
);
my $is_sales = ref($self) eq 'SL::DB::Invoice';
my $mult = $is_sales ? 1 : -1; # multiplier for getting the right sign depending on ar/ap
- my $paid_amount = 0; # the amount that will be later added to $self->paid
+ my $paid_amount = 0; # the amount that will be later added to $self->paid, should be in default currency
# default values if not set
$params{payment_type} = 'without_skonto' unless $params{payment_type};
# check for required parameters
Common::check_params(\%params, qw(chart_id transdate));
- my $transdate_obj = $::locale->parse_date_to_object($params{transdate});
+ my $transdate_obj;
+ if (ref($params{transdate} eq 'DateTime')) {
+ print "found transdate ref\n"; sleep 2;
+ $transdate_obj = $params{transdate};
+ } else {
+ $transdate_obj = $::locale->parse_date_to_object($params{transdate});
+ };
croak t8('Illegal date') unless ref $transdate_obj;
# check for closed period
croak t8('Cannot post transaction above the maximum future booking date!') if $transdate_obj > DateTime->now->add( days => $::instance_conf->get_max_future_booking_interval );
};
+ # currency is either passed or use the invoice currency if it differs from the default currency
+ my ($exchangerate,$currency);
+ if ($params{currency} || $params{currency_id} || $self->currency_id != $::instance_conf->get_currency_id) {
+ if ($params{currency} || $params{currency_id} ) { # currency was specified
+ $currency = SL::DB::Manager::Currency->find_by(name => $params{currency}) || SL::DB::Manager::Currency->find_by(id => $params{currency_id});
+ } else { # use invoice currency
+ $currency = SL::DB::Manager::Currency->find_by(id => $self->currency_id);
+ };
+ die "no currency" unless $currency;
+ if ($currency->id == $::instance_conf->get_currency_id) {
+ $exchangerate = 1;
+ } else {
+ my $rate = SL::DB::Manager::Exchangerate->find_by(currency_id => $currency->id,
+ transdate => $transdate_obj,
+ );
+ if ($rate) {
+ $exchangerate = $is_sales ? $rate->buy : $rate->sell;
+ } else {
+ die "No exchange rate for " . $transdate_obj->to_kivitendo;
+ };
+ };
+ } else { # no currency param given or currency is the same as default_currency
+ $exchangerate = 1;
+ };
+
# input checks:
if ( $params{'payment_type'} eq 'without_skonto' ) {
croak "invalid amount for payment_type 'without_skonto': $params{'amount'}\n" unless abs($params{'amount'}) > 0;
my $memo = $params{'memo'} || '';
my $source = $params{'source'} || '';
- my $rounded_params_amount = _round( $params{amount} );
+ my $rounded_params_amount = _round( $params{amount} ); # / $exchangerate);
my $db = $self->db;
$db->do_transaction(sub {
$pay_amount = $self->amount_less_skonto if $params{payment_type} eq 'with_skonto_pt';
# bank account and AR/AP
- $paid_amount += $pay_amount;
+ $paid_amount += $pay_amount * $exchangerate;
+
+ my $amount = (-1 * $pay_amount) * $mult;
+
# total amount against bank, do we already know this by now?
$new_acc_trans = SL::DB::AccTransaction->new(trans_id => $self->id,
chart_id => $account_bank->id,
chart_link => $account_bank->link,
- amount => (-1 * $pay_amount) * $mult,
+ amount => $amount,
transdate => $transdate_obj,
source => $source,
memo => $memo,
taxkey => 0,
tax_id => SL::DB::Manager::Tax->find_by(taxkey => 0)->id);
$new_acc_trans->save;
+
+ # deal with fxtransaction
+ if ( $self->currency_id != $::instance_conf->get_currency_id ) {
+ my $fxamount = _round($amount - ($amount * $exchangerate));
+ # print "amount: $amount, fxamount = $fxamount\n";
+ # print "amount - (amount * exchangerate) = " . $amount . " - (" . $amount . " - " . $exchangerate . ")\n";
+ $new_acc_trans = SL::DB::AccTransaction->new(trans_id => $self->id,
+ chart_id => $account_bank->id,
+ chart_link => $account_bank->link,
+ amount => $fxamount * -1,
+ transdate => $transdate_obj,
+ source => $source,
+ memo => $memo,
+ taxkey => 0,
+ fx_transaction => 1,
+ tax_id => SL::DB::Manager::Tax->find_by(taxkey => 0)->id);
+ $new_acc_trans->save;
+ };
};
if ( $params{payment_type} eq 'difference_as_skonto' or $params{payment_type} eq 'with_skonto_pt' ) {
my $amount = -1 * $skonto_booking->{skonto_amount};
$new_acc_trans = SL::DB::AccTransaction->new(trans_id => $self->id,
chart_id => $skonto_booking->{'chart_id'},
- chart_link => SL::DB::Manager::Chart->find_by(id => $skonto_booking->{'chart_id'})->{'link'},
+ chart_link => SL::DB::Manager::Chart->find_by(id => $skonto_booking->{'chart_id'})->link,
amount => $amount * $mult,
transdate => $transdate_obj,
source => $params{source},
taxkey => 0,
tax_id => SL::DB::Manager::Tax->find_by(taxkey => 0)->id);
+
+ # the acc_trans entries are saved individually, not added to $self and then saved all at once
$new_acc_trans->save;
$reference_amount -= abs($amount);
- $paid_amount += -1 * $amount;
+ $paid_amount += -1 * $amount * $exchangerate;
$skonto_amount_check -= $skonto_booking->{'skonto_amount'};
};
- die "difference_as_skonto calculated incorrectly, sum of calculated payments doesn't add up to open amount $total_open_amount, reference_amount = $reference_amount\n" unless _round($reference_amount) == 0;
+ if ( $params{payment_type} eq 'difference_as_skonto' ) {
+ die "difference_as_skonto calculated incorrectly, sum of calculated payments doesn't add up to open amount $total_open_amount, reference_amount = $reference_amount\n" unless _round($reference_amount) == 0;
+ }
};
my $arap_booking= SL::DB::AccTransaction->new(trans_id => $self->id,
chart_id => $reference_account->id,
chart_link => $reference_account->link,
- amount => $arap_amount * $mult,
+ amount => _round($arap_amount * $mult * $exchangerate),
transdate => $transdate_obj,
source => '', #$params{source},
taxkey => 0,
tax_id => SL::DB::Manager::Tax->find_by(taxkey => 0)->id);
$arap_booking->save;
- $self->paid($self->paid+$paid_amount) if $paid_amount;
+ $self->paid($self->paid + _round($paid_amount)) if $paid_amount;
$self->datepaid($transdate_obj);
$self->save;
+ # make sure transactions will be reloaded the next time $self->transactions
+ # is called, as pay_invoice saves the acc_trans objects individually rather
+ # than adding them to the transaction relation array.
+ $self->forget_related('transactions');
+
my $datev_check = 0;
if ( $is_sales ) {
if ( ( $self->invoice && $::instance_conf->get_datev_check_on_sales_invoice ) ||
my $is_sales = ref($self) eq 'SL::DB::Invoice';
- my $percent_skonto = $self->percent_skonto;
+ my $percent_skonto = $self->percent_skonto || 0;
return _round($self->amount - ( $self->amount * $percent_skonto) );
my $skonto_configured = 1; # default is assume skonto works
- my $transactions = $self->transactions;
- foreach my $transaction (@{ $transactions }) {
+ # my $transactions = $self->transactions;
+ foreach my $transaction (@{ $self->transactions }) {
# find all transactions with an AR_amount or AP_amount link
- my $tax = SL::DB::Manager::Tax->get_first( where => [taxkey => $transaction->{taxkey}]);
+ my $tax = SL::DB::Manager::Tax->get_first( where => [taxkey => $transaction->taxkey]);
croak "no tax for taxkey " . $transaction->{taxkey} unless ref $tax;
- $transaction->{chartlinks} = { map { $_ => 1 } split(m/:/, $transaction->{chart_link}) };
+ $transaction->{chartlinks} = { map { $_ => 1 } split(m/:/, $transaction->chart_link) };
if ( $is_sales && $transaction->{chartlinks}->{AR_amount} ) {
$skonto_configured = 0 unless $tax->skonto_sales_chart_id;
} elsif ( !$is_sales && $transaction->{chartlinks}->{AP_amount}) {
my $reference_ARAP_amount = 0;
- my $transactions = $self->transactions;
- foreach my $transaction (@{ $transactions }) {
+ # my $transactions = $self->transactions;
+ foreach my $transaction (@{ $self->transactions }) {
# find all transactions with an AR_amount or AP_amount link
$transaction->{chartlinks} = { map { $_ => 1 } split(m/:/, $transaction->{chart_link}) };
# second condition is that we can determine an automatic Skonto account for each AR_amount entry
push(@options, { payment_type => 'without_skonto', display => t8('without skonto') });
push(@options, { payment_type => 'with_skonto_pt', display => t8('with skonto acc. to pt'), selected => 1 });
} else {
- push(@options, { payment_type => 'without_skonto', display => t8('without skonto') }, selected => 1 );
+ push(@options, { payment_type => 'without_skonto', display => t8('without skonto') , selected => 1 });
push(@options, { payment_type => 'with_skonto_pt', display => t8('with skonto acc. to pt')});
};
};
return 1;
};
-sub transactions {
- my ($self) = @_;
-
- return unless $self->id;
-
- require SL::DB::AccTransaction;
- SL::DB::Manager::AccTransaction->get_all(query => [ trans_id => $self->id ]);
-}
-
sub validate_payment_type {
my $payment_type = shift;
return 1;
}
+sub create_bank_transaction {
+ my ($self, %params) = @_;
+
+ require SL::DB::Chart;
+ require SL::DB::BankAccount;
+
+ my $bank_chart;
+ if ( $params{chart_id} ) {
+ $bank_chart = SL::DB::Manager::Chart->find_by(chart_id => $params{chart_id}) or die "Can't find bank chart";
+ } elsif ( $::instance_conf->get_ar_paid_accno_id ) {
+ $bank_chart = SL::DB::Manager::Chart->find_by(id => $::instance_conf->get_ar_paid_accno_id);
+ } else {
+ $bank_chart = SL::DB::Manager::Chart->find_by(description => 'Bank') or die "Can't find bank chart";
+ };
+ my $bank_account = SL::DB::Manager::BankAccount->find_by(chart_id => $bank_chart->id) or die "Can't find bank account for chart";
+
+ my $multiplier = $self->is_sales ? 1 : -1;
+ my $amount = ($params{amount} || $self->amount) * $multiplier;
+
+ my $transdate = $params{transdate} || DateTime->today;
+
+ my $bt = SL::DB::BankTransaction->new(
+ local_bank_account_id => $bank_account->id,
+ remote_bank_code => $self->customervendor->bank_code,
+ remote_account_number => $self->customervendor->account_number,
+ transdate => $transdate,
+ valutadate => $transdate,
+ amount => $::form->round_amount($amount, 2),
+ currency => $self->currency->id,
+ remote_name => $self->customervendor->depositor,
+ purpose => $self->invnumber
+ )->save;
+};
+
+
sub _round {
my $value = shift;
my $num_dec = 2;
$ap->pay_invoice(chart_id => $bank->chart_id,
amount => $ap->open_amount,
transdate => DateTime->now->to_kivitendo,
- memo => 'foobar;
- source => 'barfoo;
+ memo => 'foobar',
+ source => 'barfoo',
payment_type => 'without_skonto', # default if not specified
);
$ap->pay_invoice(chart_id => $bank->chart_id,
amount => $ap->amount, # doesn't need to be specified
transdate => DateTime->now->to_kivitendo,
- memo => 'foobar;
- source => 'barfoo;
+ memo => 'foobar',
+ source => 'barfoo',
+ payment_type => 'with_skonto',
+ );
+
+or in a certain currency:
+ $ap->pay_invoice(chart_id => $bank->chart_id,
+ amount => 500,
+ currency => 'USD',
+ transdate => DateTime->now->to_kivitendo,
+ memo => 'foobar',
+ source => 'barfoo',
payment_type => 'with_skonto',
);
Skonto doesn't/shouldn't apply if the invoice contains credited items.
+If no amount is given the whole open amout is paid.
+
+If neither currency or currency_id are given as params, the currency of the
+invoice is assumed to be the payment currency.
+
=item C<reference_account>
Returns a chart object which is the chart of the invoice with link AR or AP.
might always want to pay quickly making use of skonto, while another company
might always want to pay as late as possible.
-=item C<transactions>
-
-Returns all acc_trans Objects of an ar/ap object.
-
-Example in console to print account numbers and booked amounts of an invoice:
- my $invoice = invoice(invnumber => '144');
- foreach my $acc_trans ( @{ $invoice->transactions } ) {
- print $acc_trans->chart->accno . " : " . $acc_trans->amount_as_number . "\n"
- };
- # 1200 : 226,00000
- # 1800 : -226,00000
- # 4300 : 100,00000
- # 3801 : 7,00000
- # 3806 : 19,00000
- # 4400 : 100,00000
- # 1200 : -226,00000
-
=item C<get_payment_select_options_for_bank_transaction $banktransaction_id %params>
Make suggestion for a skonto payment type by returning an HTML blob of the options
without_skonto and with_skonto_pt if payment date is within skonto_date,
preselect with_skonto_pt, otherwise preselect without skonto.
+=item C<create_bank_transaction %params>
+
+Method used for testing purposes, allows you to quickly create bank
+transactions from invoices to have something to test payments against.
+
+ my $ap = SL::DB::Manager::Invoice->find_by(id => 41);
+ $ap->create_bank_transaction(amount => $ap->amount/2, transdate => DateTime->today->add(days => 5));
+
+Amount is always relative to the absolute amount of the invoice, use positive
+values for sales and purchases.
+
=back
=head1 TODO AND CAVEATS