$params{payment_type} = 'without_skonto' unless $params{payment_type};
validate_payment_type($params{payment_type});
- # check for required parameters
+ # check for required parameters and optional params depending on payment_type
Common::check_params(\%params, qw(chart_id transdate));
+ if ( $params{'payment_type'} eq 'without_skonto' && abs($params{'amount'}) < 0) {
+ croak "invalid amount for payment_type 'without_skonto': $params{'amount'}\n";
+ }
+ if ($params{'payment_type'} eq 'free_skonto') {
+ # we dont like too much automagic for this payment type.
+ # we force caller input for amount and skonto amount
+ Common::check_params(\%params, qw(amount skonto_amount));
+ # secondly we dont want to handle credit notes and purchase credit notes
+ croak("Cannot use 'free skonto' for credit or debit notes") if ($params{amount} <= 0 || $params{skonto_amount} <= 0);
+ # both amount have to be rounded
+ $params{skonto_amount} = _round($params{skonto_amount});
+ $params{amount} = _round($params{amount});
+ # lastly skonto_amount has to be smaller than the open invoice amount or payment amount ;-)
+ if ($params{skonto_amount} > abs($self->open_amount) || $params{skonto_amount} > $params{amount}) {
+ croak("Skonto amount higher than the payment or invoice amount");
+ }
+ }
my $transdate_obj;
if (ref($params{transdate} eq 'DateTime')) {
$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;
- };
-
# options with_skonto_pt and difference_as_skonto don't require the parameter
# amount, but if amount is passed, make sure it matches the expected value
if ( $params{'payment_type'} eq 'difference_as_skonto' ) {
my $new_acc_trans;
# all three payment type create 1 AR/AP booking (the paid part)
- # difference_as_skonto creates n skonto bookings (1 for each tax type)
- # with_skonto_pt creates 1 bank booking and n skonto bookings (1 for each tax type)
+ # difference_as_skonto creates n skonto bookings (1 for each buchungsgruppe type)
+ # with_skonto_pt creates 1 bank booking and n skonto bookings (1 for each buchungsgruppe type)
# without_skonto creates 1 bank booking
# as long as there is no automatic tax, payments are always booked with
# taxkey 0
unless ( $params{payment_type} eq 'difference_as_skonto' ) {
- # cases with_skonto_pt and without_skonto
+ # cases with_skonto_pt, free_skonto and without_skonto
# for case with_skonto_pt we need to know the corrected amount at this
# stage if we are going to use $params{amount}
tax_id => SL::DB::Manager::Tax->find_by(taxkey => 0)->id);
$new_acc_trans->save;
+ push @new_acc_ids, $new_acc_trans->acc_trans_id;
# deal with fxtransaction
if ( $self->currency_id != $::instance_conf->get_currency_id ) {
my $fxamount = _round($amount - ($amount * $exchangerate));
fx_transaction => 1,
tax_id => SL::DB::Manager::Tax->find_by(taxkey => 0)->id);
$new_acc_trans->save;
+ push @new_acc_ids, $new_acc_trans->acc_trans_id;
# if invoice exchangerate differs from exchangerate of payment
# deal with fxloss and fxamount
if ($self->exchangerate and $self->exchangerate != 1 and $self->exchangerate != $exchangerate) {
fx_transaction => 0,
tax_id => SL::DB::Manager::Tax->find_by(taxkey => 0)->id);
$new_acc_trans->save;
+ push @new_acc_ids, $new_acc_trans->acc_trans_id;
- };
- };
- };
-
- if ( $params{payment_type} eq 'difference_as_skonto' or $params{payment_type} eq 'with_skonto_pt' ) {
+ }
+ }
+ }
+ # better everything except without_skonto
+ if ($params{payment_type} eq 'difference_as_skonto' or $params{payment_type} eq 'with_skonto_pt'
+ or $params{payment_type} eq 'free_skonto' ) {
my $total_skonto_amount;
if ( $params{payment_type} eq 'with_skonto_pt' ) {
$total_skonto_amount = $self->skonto_amount;
} elsif ( $params{payment_type} eq 'difference_as_skonto' ) {
$total_skonto_amount = $self->open_amount;
- };
-
+ } elsif ( $params{payment_type} eq 'free_skonto') {
+ $total_skonto_amount = $params{skonto_amount};
+ }
my @skonto_bookings = $self->skonto_charts($total_skonto_amount);
# error checking:
# the acc_trans entries are saved individually, not added to $self and then saved all at once
$new_acc_trans->save;
+ push @new_acc_ids, $new_acc_trans->acc_trans_id;
$reference_amount -= abs($amount);
$paid_amount += -1 * $amount * $exchangerate;
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_amount = 0;
# with_skonto_pt for completely unpaid invoices we just use the value
# from the invoice
$arap_amount = $total_open_amount;
- };
+ } elsif ( $params{payment_type} eq 'free_skonto' ) {
+ # we forced positive values and forced rounding at the beginning
+ # therefore the above comment can be safely applied for this payment type
+ $arap_amount = $params{amount} + $params{skonto_amount};
+ }
# regardless of payment_type there is always only exactly one arap booking
# TODO: compare $arap_amount to running total
taxkey => 0,
tax_id => SL::DB::Manager::Tax->find_by(taxkey => 0)->id);
$arap_booking->save;
+ push @new_acc_ids, $arap_booking->acc_trans_id;
$fx_gain_loss_amount *= -1 if $self->is_sales;
$self->paid($self->paid + _round($paid_amount) + $fx_gain_loss_amount) if $paid_amount;
}
}
- push @new_acc_ids, ($new_acc_trans->acc_trans_id, $arap_booking->acc_trans_id);
1;
}) || die t8('error while paying invoice #1 : ', $self->invnumber) . $db->error . "\n";
sub amount_less_skonto {
# amount that has to be paid if skonto applies, always return positive rounded values
+ # no, rare case, but credit_notes and negative ap have negative amounts
+ # and therefore this comment may be misguiding
# the result is rounded so we can directly compare it with the user input
my $self = shift;
- my $is_sales = ref($self) eq 'SL::DB::Invoice';
-
my $percent_skonto = $self->percent_skonto || 0;
return _round($self->amount - ( $self->amount * $percent_skonto) );
sub get_payment_select_options_for_bank_transaction {
my ($self, $bt_id, %params) = @_;
- # no skonto date -> no select option
- return { payment_type => 'without_skonto', display => t8('without skonto') , selected => 1 } unless $self->skonto_date;
-
- my $bt = SL::DB::BankTransaction->new(id => $bt_id)->load;
+ # CAVEAT template code expects with_skonto_pt at position 1 for visual help
+ # due to skonto_charts, we cannot offer skonto for credit notes and neg ap
+ my $skontoable = $self->amount > 0 ? 1 : 0;
my @options;
-
+ if(!$self->skonto_date) {
+ push(@options, { payment_type => 'without_skonto', display => t8('without skonto'), selected => 1 });
+ # wrong call to presenter or not implemented? disabled option is ignored
+ # push(@options, { payment_type => 'with_skonto_pt', display => t8('with skonto acc. to pt'), disabled => 1 });
+ push(@options, { payment_type => 'free_skonto', display => t8('free skonto') }) if $skontoable;
+ return @options;
+ }
+ # valid skonto date, check if skonto is preferred
+ my $bt = SL::DB::BankTransaction->new(id => $bt_id)->load;
if ($self->skonto_date && $self->within_skonto_period($bt->transdate)) {
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 });
+ push(@options, { payment_type => 'with_skonto_pt', display => t8('with skonto acc. to pt'), selected => 1 }) if $skontoable;
} else {
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')});
+ push(@options, { payment_type => 'with_skonto_pt', display => t8('with skonto acc. to pt')}) if $skontoable;
}
+ push(@options, { payment_type => 'free_skonto', display => t8('free skonto') }) if $skontoable;
return @options;
}
sub validate_payment_type {
my $payment_type = shift;
- my %allowed_payment_types = map { $_ => 1 } qw(without_skonto with_skonto_pt difference_as_skonto);
+ my %allowed_payment_types = map { $_ => 1 } qw(without_skonto with_skonto_pt difference_as_skonto free_skonto);
croak "illegal payment type: $payment_type, must be one of: " . join(' ', keys %allowed_payment_types) unless $allowed_payment_types{ $payment_type };
return 1;
a configured bank account.
This function deals with all the acc_trans entries and also updates paid and datepaid.
+The params C<transdate> and C<chart_id> are mandantory.
+If the default payment ('without_skonto') is used the param amount is also
+mandantory.
+If the payment type ('free_skonto') is used the number params skonto_amount and amount
+are as well mandantory and need to be positive. Furthermore the skonto amount has
+to be lower than the payment or open invoice amount.
+
+Transdate can either be a date object or a date string.
+Chart_id is the id of the payment booking chart.
+Amount is either a postive or negative number, but never 0.
+
Example:
If successful the return value will be 1 in scalar context or in list context
the two ids (acc_trans_id) of the newly created bookings.
+
=item C<reference_account>
Returns a chart object which is the chart of the invoice with link AR or AP.
when looking at open amount, maybe consider that there may already be queued
amounts in SEPA Export
+=item * C<skonto_charts>
+
+Cannot handle negative skonto amounts, will always calculate the skonto amount
+for credit notes or negative ap transactions with a positive sign.
+
+
=back
=head1 AUTHOR