41 |
41 |
validate_payment_type($params{payment_type});
|
42 |
42 |
|
43 |
43 |
# check for required parameters and optional params depending on payment_type
|
44 |
|
Common::check_params(\%params, qw(chart_id transdate));
|
|
44 |
Common::check_params(\%params, qw(chart_id transdate amount));
|
45 |
45 |
Common::check_params(\%params, qw(bt_id)) unless $params{payment_type} eq 'without_skonto';
|
|
46 |
|
|
47 |
# three valid cases, test logical params in depth, before proceeding ...
|
46 |
48 |
if ( $params{'payment_type'} eq 'without_skonto' && abs($params{'amount'}) < 0) {
|
47 |
49 |
croak "invalid amount for payment_type 'without_skonto': $params{'amount'}\n";
|
48 |
|
}
|
49 |
|
if ($params{'payment_type'} eq 'free_skonto') {
|
|
50 |
} elsif ($params{'payment_type'} eq 'free_skonto') {
|
50 |
51 |
# we dont like too much automagic for this payment type.
|
51 |
52 |
# we force caller input for amount and skonto amount
|
52 |
|
Common::check_params(\%params, qw(amount skonto_amount));
|
|
53 |
Common::check_params(\%params, qw(skonto_amount));
|
53 |
54 |
# secondly we dont want to handle credit notes and purchase credit notes
|
54 |
55 |
croak("Cannot use 'free skonto' for credit or debit notes") if ($params{amount} < 0 || $params{skonto_amount} <= 0);
|
55 |
56 |
# both amount have to be rounded
|
... | ... | |
59 |
60 |
if ($params{skonto_amount} > _round($self->open_amount)) {
|
60 |
61 |
croak("Skonto amount:" . $params{skonto_amount} . " higher than the payment or open invoice amount:" . $self->open_amount);
|
61 |
62 |
}
|
|
63 |
} elsif ( $params{'payment_type'} eq 'with_skonto_pt' ) {
|
|
64 |
# options with_skonto_pt doesn't require the parameter
|
|
65 |
# amount, but if amount is passed, make sure it matches the expected value
|
|
66 |
# note: the parameter isn't used at all - amount_less_skonto will always be used
|
|
67 |
# partial skonto payments are therefore impossible to book
|
|
68 |
croak "amount $params{amount} doesn't match amount less skonto: " . $self->amount_less_skonto . "\n" if $params{amount} && abs($self->amount_less_skonto - $params{amount} ) > 0.0000001;
|
|
69 |
croak "payment type with_skonto_pt can't be used if payments have already been made" if $self->paid != 0;
|
62 |
70 |
}
|
63 |
71 |
|
|
72 |
|
64 |
73 |
my $transdate_obj;
|
65 |
74 |
if (ref($params{transdate}) eq 'DateTime') {
|
66 |
75 |
$transdate_obj = $params{transdate};
|
... | ... | |
106 |
115 |
$exchangerate = 1;
|
107 |
116 |
};
|
108 |
117 |
|
109 |
|
# options with_skonto_pt and difference_as_skonto don't require the parameter
|
110 |
|
# amount, but if amount is passed, make sure it matches the expected value
|
111 |
|
if ( $params{'payment_type'} eq 'difference_as_skonto' ) {
|
112 |
|
croak "amount $params{amount} doesn't match open amount " . $self->open_amount . ", diff = " . ($params{amount}-$self->open_amount) if $params{amount} && abs($self->open_amount - $params{amount} ) > 0.0000001;
|
113 |
|
} elsif ( $params{'payment_type'} eq 'with_skonto_pt' ) {
|
114 |
|
croak "amount $params{amount} doesn't match amount less skonto: " . $self->amount_less_skonto . "\n" if $params{amount} && abs($self->amount_less_skonto - $params{amount} ) > 0.0000001;
|
115 |
|
croak "payment type with_skonto_pt can't be used if payments have already been made" if $self->paid != 0;
|
116 |
|
};
|
117 |
|
|
118 |
118 |
# absolute skonto amount for invoice, use as reference sum to see if the
|
119 |
119 |
# calculated skontos add up
|
120 |
120 |
# only needed for payment_term "with_skonto_pt"
|
... | ... | |
140 |
140 |
my $new_acc_trans;
|
141 |
141 |
|
142 |
142 |
# all three payment type create 1 AR/AP booking (the paid part)
|
143 |
|
# difference_as_skonto creates n skonto bookings (1 for each buchungsgruppe type)
|
144 |
|
# with_skonto_pt creates 1 bank booking and n skonto bookings (1 for each buchungsgruppe type)
|
|
143 |
# with_skonto_pt creates 1 bank booking and n skonto bookings (1 for each tax type)
|
|
144 |
# and one tax correction as a gl booking
|
145 |
145 |
# without_skonto creates 1 bank booking
|
146 |
146 |
|
147 |
|
# as long as there is no automatic tax, payments are always booked with
|
148 |
|
# taxkey 0
|
149 |
|
|
150 |
|
unless ( $rounded_params_amount == 0 || $params{payment_type} eq 'difference_as_skonto' ) {
|
|
147 |
unless ( $rounded_params_amount == 0 ) {
|
151 |
148 |
# cases with_skonto_pt, free_skonto and without_skonto
|
152 |
149 |
|
153 |
150 |
# for case with_skonto_pt we need to know the corrected amount at this
|
154 |
|
# stage if we are going to use $params{amount}
|
|
151 |
# stage because we don't use $params{amount} ?!
|
155 |
152 |
|
156 |
153 |
my $pay_amount = $rounded_params_amount;
|
157 |
154 |
$pay_amount = $self->amount_less_skonto if $params{payment_type} eq 'with_skonto_pt';
|
... | ... | |
216 |
213 |
}
|
217 |
214 |
}
|
218 |
215 |
}
|
219 |
|
# better everything except without_skonto
|
220 |
|
if ($params{payment_type} eq 'difference_as_skonto' or $params{payment_type} eq 'with_skonto_pt'
|
221 |
|
or $params{payment_type} eq 'free_skonto' ) {
|
|
216 |
# skonto cases
|
|
217 |
if ($params{payment_type} eq 'with_skonto_pt' or $params{payment_type} eq 'free_skonto' ) {
|
222 |
218 |
|
223 |
219 |
my $total_skonto_amount;
|
224 |
220 |
if ( $params{payment_type} eq 'with_skonto_pt' ) {
|
225 |
221 |
$total_skonto_amount = $self->skonto_amount;
|
226 |
|
} elsif ( $params{payment_type} eq 'difference_as_skonto' ) {
|
227 |
|
# only used for tests. no real code calls this payment_type!
|
228 |
|
$total_skonto_amount = $self->open_amount;
|
229 |
222 |
} elsif ( $params{payment_type} eq 'free_skonto') {
|
230 |
223 |
$total_skonto_amount = $params{skonto_amount};
|
231 |
224 |
}
|
232 |
225 |
my @skonto_bookings = $self->_skonto_charts_and_tax_correction(amount => $total_skonto_amount, bt_id => $params{bt_id},
|
233 |
226 |
transdate_obj => $transdate_obj, memo => $params{memo},
|
234 |
227 |
source => $params{source});
|
235 |
|
# error checking:
|
236 |
|
if ( $params{payment_type} eq 'difference_as_skonto' ) {
|
237 |
|
my $calculated_skonto_sum = sum map { $_->{skonto_amount} } @skonto_bookings;
|
238 |
|
croak "calculated skonto for difference_as_skonto = $calculated_skonto_sum doesn't add up open amount: " . $self->open_amount unless _round($calculated_skonto_sum) == _round($self->open_amount);
|
239 |
|
};
|
240 |
|
|
241 |
228 |
my $reference_amount = $total_skonto_amount;
|
242 |
229 |
|
243 |
230 |
# create an acc_trans entry for each result of $self->skonto_charts
|
244 |
|
# TODO create internal sub _skonto_bookings
|
245 |
231 |
foreach my $skonto_booking ( @skonto_bookings ) {
|
246 |
232 |
next unless $skonto_booking->{'chart_id'};
|
247 |
233 |
next unless $skonto_booking->{'skonto_amount'} != 0;
|
... | ... | |
263 |
249 |
$paid_amount += -1 * $amount * $exchangerate;
|
264 |
250 |
$skonto_amount_check -= $skonto_booking->{'skonto_amount'};
|
265 |
251 |
}
|
266 |
|
if ( $params{payment_type} eq 'difference_as_skonto' ) {
|
267 |
|
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;
|
268 |
|
}
|
269 |
252 |
}
|
270 |
|
|
271 |
253 |
my $arap_amount = 0;
|
272 |
254 |
|
273 |
|
if ( $params{payment_type} eq 'difference_as_skonto' ) {
|
274 |
|
$arap_amount = $total_open_amount;
|
275 |
|
} elsif ( $params{payment_type} eq 'without_skonto' ) {
|
|
255 |
if ( $params{payment_type} eq 'without_skonto' ) {
|
276 |
256 |
$arap_amount = $rounded_params_amount;
|
277 |
257 |
} elsif ( $params{payment_type} eq 'with_skonto_pt' ) {
|
278 |
258 |
# this should be amount + sum(amount+skonto), but while we only allow
|
... | ... | |
797 |
777 |
$self->{invoice_amount_suggestion} = $open_amount;
|
798 |
778 |
# difference_as_skonto doesn't make any sense for SEPA transfer, as this doesn't cause any actual payment
|
799 |
779 |
if ( $self->valid_skonto_amount($self->open_amount) && not $params{sepa} ) {
|
|
780 |
# probably also dead code
|
|
781 |
die "This case is as dead as the dead cat. Go to start, don't pick 2,000 \$";
|
800 |
782 |
push(@{$self->{payment_select_options}} , { payment_type => 'difference_as_skonto', display => t8('difference as skonto') , selected => 0 });
|
801 |
783 |
};
|
802 |
784 |
};
|
... | ... | |
807 |
789 |
#
|
808 |
790 |
# $main::locale->text('without_skonto')
|
809 |
791 |
# $main::locale->text('with_skonto_pt')
|
810 |
|
# $main::locale->text('difference_as_skonto')
|
811 |
792 |
#
|
812 |
793 |
|
813 |
794 |
sub validate_payment_type {
|
814 |
795 |
my $payment_type = shift;
|
815 |
796 |
|
816 |
|
my %allowed_payment_types = map { $_ => 1 } qw(without_skonto with_skonto_pt difference_as_skonto free_skonto);
|
|
797 |
my %allowed_payment_types = map { $_ => 1 } qw(without_skonto with_skonto_pt free_skonto);
|
817 |
798 |
croak "illegal payment type: $payment_type, must be one of: " . join(' ', keys %allowed_payment_types) unless $allowed_payment_types{ $payment_type };
|
818 |
799 |
|
819 |
800 |
return 1;
|
... | ... | |
860 |
841 |
a configured bank account.
|
861 |
842 |
|
862 |
843 |
This function deals with all the acc_trans entries and also updates paid and datepaid.
|
863 |
|
The params C<transdate> and C<chart_id> are mandantory.
|
864 |
|
If the default payment ('without_skonto') is used the param amount is also
|
865 |
|
mandantory.
|
866 |
|
If the payment type ('free_skonto') is used the number params skonto_amount and amount
|
867 |
|
are as well mandantory and need to be positive. Furthermore the skonto amount has
|
868 |
|
to be lower than the payment or open invoice amount.
|
|
844 |
The params C<transdate>, C<amount> and C<chart_id> are mandantory.
|
|
845 |
|
|
846 |
For all valid skonto types ('free_skonto' or 'with_skonto_pt') the source of
|
|
847 |
the bank_transaction is needed, therefore pay_invoice expects the param
|
|
848 |
C<bt_id> with a valid bank_transactions.id.
|
|
849 |
|
|
850 |
If the payment type ('free_skonto') is used the number param skonto_amount is
|
|
851 |
as well mandantory and needs to be positive. Furthermore the skonto amount has
|
|
852 |
to be lower or equal than the open invoice amount.
|
|
853 |
Payments with only skonto and zero bank transaction amount are possible.
|
869 |
854 |
|
870 |
855 |
Transdate can either be a date object or a date string.
|
871 |
856 |
Chart_id is the id of the payment booking chart.
|
872 |
|
Amount is either a positive or negative number, but never 0.
|
|
857 |
Amount is either a positive or negative number, and for the case 'free_skonto' might be zero.
|
873 |
858 |
|
874 |
859 |
CAVEAT! The helper tries to get the sign right and all calls from BankTransaction are
|
875 |
860 |
positive (abs($value)) values.
|
... | ... | |
908 |
893 |
);
|
909 |
894 |
|
910 |
895 |
Allowed payment types are:
|
911 |
|
without_skonto with_skonto_pt difference_as_skonto
|
|
896 |
without_skonto with_skonto_pt
|
912 |
897 |
|
913 |
898 |
The option C<payment_type> allows for a basic skonto mechanism.
|
914 |
899 |
|
... | ... | |
922 |
907 |
tax key. If an amount is passed it is ignored and the actual configured skonto
|
923 |
908 |
amount is used.
|
924 |
909 |
|
925 |
|
C<difference_as_skonto> can only be used after partial payments have been made,
|
926 |
|
the whole specified amount is booked according to the skonto charts configured
|
927 |
|
in the tax settings for each tax key.
|
928 |
|
|
929 |
|
So passing amount doesn't have any effect for the cases C<with_skonto_pt> and
|
930 |
|
C<difference_as_skonto>, as all necessary values are taken from the stored
|
931 |
|
invoice.
|
|
910 |
So passing amount doesn't have any effect for the case C<with_skonto_pt>.
|
932 |
911 |
|
933 |
912 |
The skonto modes automatically calculate the relative amounts for a mix of
|
934 |
913 |
taxes, e.g. items with 7% and 19% in one invoice. There is a helper method
|
935 |
|
skonto_charts, which calculates the relative percentages according to the
|
936 |
|
amounts in acc_trans (which are grouped by tax).
|
|
914 |
_skonto_charts_and_tax_correction, which calculates the relative percentages
|
|
915 |
according to the amounts in acc_trans grouped by different tax rates.
|
|
916 |
|
|
917 |
The helper method also generates the tax correction for the skonto booking
|
|
918 |
and links this to the original bank transaction and the selected record.
|
937 |
919 |
|
938 |
920 |
There is currently no way of excluding certain items in an invoice from having
|
939 |
921 |
skonto applied to them. If this feature was added to parts the calculation
|
940 |
922 |
method of relative skonto would have to be completely rewritten using the
|
941 |
923 |
invoice items rather than acc_trans.
|
942 |
924 |
|
943 |
|
The skonto modes also still don't automatically correct the tax, this still has
|
944 |
|
to be done manually. Therefore all payments generated by pay_invoice have
|
945 |
|
taxkey 0.
|
946 |
|
|
947 |
|
There is currently no way to directly pay an invoice via this method if the
|
948 |
|
effective skonto differs from the skonto according to the payment terms
|
949 |
|
configured for the invoice/vendor.
|
950 |
|
|
951 |
|
In this case one has to pay in two steps: first the actual paid amount via
|
952 |
|
"without skonto", and then the remainder via "difference_as_skonto". The user
|
953 |
|
has to there actively decide whether to accept the differing skonto.
|
954 |
|
|
955 |
925 |
Because of the way skonto_charts works the calculation doesn't work if there
|
956 |
926 |
are negative values in acc_trans. E.g. one invoice with a positive value for
|
957 |
927 |
19% tax and a negative value for the acc_trans line with 7%
|
... | ... | |
964 |
934 |
invoice is assumed to be the payment currency.
|
965 |
935 |
|
966 |
936 |
If successful the return value will be 1 in scalar context or in list context
|
967 |
|
the two ids (acc_trans_id) of the newly created bookings.
|
|
937 |
the two or more (gl transaction for skonto tax correction) ids (acc_trans_id)
|
|
938 |
of the newly created bookings.
|
|
939 |
|
968 |
940 |
|
969 |
941 |
=item C<reference_account>
|
970 |
942 |
|
... | ... | |
1020 |
992 |
# ... do something
|
1021 |
993 |
}
|
1022 |
994 |
|
1023 |
|
=item C<skonto_charts [$amount]>
|
|
995 |
=item C<_skonto_charts_and_tax_correction [amount => $amount, bt_id => $bank_transaction.id, transdate_ojb => DateTime]>
|
|
996 |
|
|
997 |
Needs a valid bank_transaction id and the transdate of the bank_transaction as
|
|
998 |
a DateTime object.
|
|
999 |
If no amout is passed, the currently open invoice amount will be used.
|
1024 |
1000 |
|
1025 |
1001 |
Returns a list of chart_ids and some calculated numbers that can be used for
|
1026 |
1002 |
paying the invoice with skonto. This function will automatically calculate the
|
... | ... | |
1029 |
1005 |
|
1030 |
1006 |
Example usage:
|
1031 |
1007 |
my $invoice = SL::DB::Manager::Invoice->find_by(invnumber => '211');
|
1032 |
|
my @skonto_charts = $invoice->skonto_charts;
|
|
1008 |
my @skonto_charts = $invoice->_skonto_charts_and_tax_correction(bt_id => $bt_id,
|
|
1009 |
transdate_obj => $transdate_obj);
|
1033 |
1010 |
|
1034 |
1011 |
or with the total skonto amount as an argument:
|
1035 |
|
my @skonto_charts = $invoice->skonto_charts($invoice->open_amount);
|
|
1012 |
my @skonto_charts = $invoice->_skonto_charts_and_tax_correction(amount => $invoice->open_amount,
|
|
1013 |
bt_id => $bt_id,
|
|
1014 |
transdate_obj => $transdate_obj);
|
1036 |
1015 |
|
1037 |
1016 |
The following values are generated for each chart:
|
1038 |
1017 |
|
... | ... | |
1046 |
1025 |
|
1047 |
1026 |
The total amount to be paid to the account
|
1048 |
1027 |
|
1049 |
|
=item C<skonto_percent>
|
1050 |
|
|
1051 |
|
The relative percentage of that skonto chart. This can be useful if the actual
|
1052 |
|
ekonto that is paid deviates from the granted skonto, e.g. customer effectively
|
1053 |
|
pays 2.6% skonto instead of 2%, and we accept this. Then we can still calculate
|
1054 |
|
the relative skonto amounts for different taxes based on the absolute
|
1055 |
|
percentages. Used for case C<difference_as_skonto>.
|
1056 |
|
|
1057 |
|
=item C<skonto_percent_abs>
|
1058 |
|
|
1059 |
|
The absolute percentage of that skonto chart in relation to the total amount.
|
1060 |
|
Used to calculate skonto_amount for case C<with_skonto_pt>.
|
1061 |
|
|
1062 |
1028 |
=back
|
1063 |
1029 |
|
1064 |
1030 |
If the invoice contains several types of taxes then skonto_charts can be used
|
1065 |
1031 |
to calculate the relative amounts.
|
1066 |
1032 |
|
1067 |
|
Example in console of an invoice with 100 Euro at 7% and 100 Euro at 19% with
|
1068 |
|
tax not included:
|
1069 |
|
|
1070 |
|
my $invoice = invoice(invnumber => '144');
|
1071 |
|
$invoice->amount
|
1072 |
|
226.00000
|
1073 |
|
$invoice->payment_terms->percent_skonto
|
1074 |
|
0.02
|
1075 |
|
$invoice->skonto_charts
|
1076 |
|
pp $invoice->skonto_charts
|
1077 |
|
# $VAR1 = {
|
1078 |
|
# 'chart_id' => 128,
|
1079 |
|
# 'skonto_amount' => '2.14',
|
1080 |
|
# 'skonto_percent' => '47.3451327433627'
|
1081 |
|
# };
|
1082 |
|
# $VAR2 = {
|
1083 |
|
# 'chart_id' => 130,
|
1084 |
|
# 'skonto_amount' => '2.38',
|
1085 |
|
# 'skonto_percent' => '52.654867256637'
|
1086 |
|
# };
|
1087 |
|
|
1088 |
|
C<skonto_charts> always returns positive values (abs) for C<skonto_amount> and
|
1089 |
|
C<skonto_percent>.
|
1090 |
|
|
1091 |
|
C<skonto_charts> generates one entry for each acc_trans entry. ar and ap
|
1092 |
|
bookings only have one acc_trans entry for each taxkey (e.g. 7% and 19%). This
|
1093 |
|
is because all the items are grouped according to the Buchungsgruppen mechanism
|
1094 |
|
and the totals are written to acc_trans. For is and ir it is possible to have
|
1095 |
|
several acc_trans entries with the same tax. In this case skonto_charts
|
1096 |
|
generates a skonto booking for each acc_trans income/expense entry.
|
1097 |
|
|
1098 |
|
In the future this function may also be used to calculate the corrections for
|
1099 |
|
the income tax.
|
|
1033 |
C<_skonto_charts_and_tax_correction> generates one entry for each tax type entry.
|
1100 |
1034 |
|
1101 |
1035 |
=item C<open_amount>
|
1102 |
1036 |
|
... | ... | |
1120 |
1054 |
|
1121 |
1055 |
Creates data intended for an L.select_tag dropdown that can be used in a
|
1122 |
1056 |
template. Depending on the rules it will choose from the options
|
1123 |
|
without_skonto, with_skonto_pt and difference_as_skonto, and select the most
|
|
1057 |
without_skonto and with_skonto_pt and select the most
|
1124 |
1058 |
likely one.
|
1125 |
1059 |
|
1126 |
1060 |
If the parameter "sepa" is passed, the SEPA export payments that haven't been
|
... | ... | |
1134 |
1068 |
|
1135 |
1069 |
=item * with_skonto_pt is only offered if there haven't been any payments yet and the current date is within the skonto period.
|
1136 |
1070 |
|
1137 |
|
=item * difference_as_skonto is only offered if there have already been payments made and the open amount is smaller than 10% of the total amount.
|
1138 |
|
|
1139 |
1071 |
with_skonto_pt will only be offered, if all the AR_amount/AP_amount have a
|
1140 |
1072 |
taxkey with a configured skonto chart
|
1141 |
1073 |
|
... | ... | |
1182 |
1114 |
This is a helper function for BankTransaction/ajax_payment_suggestion and
|
1183 |
1115 |
template/webpages/bank_transactions/invoices.html
|
1184 |
1116 |
|
1185 |
|
We are working with an existing payment, so difference_as_skonto never makes sense.
|
|
1117 |
We are working with an existing payment, so (deprecated) difference_as_skonto never makes sense.
|
1186 |
1118 |
|
1187 |
1119 |
If skonto is not possible (skonto_date does not exists) simply return
|
1188 |
1120 |
the single 'no skonto' option as a visual hint.
|
... | ... | |
1215 |
1147 |
when looking at open amount, maybe consider that there may already be queued
|
1216 |
1148 |
amounts in SEPA Export
|
1217 |
1149 |
|
1218 |
|
=item * C<skonto_charts>
|
|
1150 |
=item * C<_skonto_charts_and_tax_correction>
|
1219 |
1151 |
|
1220 |
1152 |
Cannot handle negative skonto amounts, will always calculate the skonto amount
|
1221 |
1153 |
for credit notes or negative ap transactions with a positive sign.
|
Payment-Helper pay_invoice case 'difference_as_skonto' entfernt
'difference_as_skonto' lässt sich über 'free_skonto' abbilden.
Ein Fall weniger der die Methode etwas wartungsfreundlicher macht.
POD und Testfall angepasst.