Revision 23b40897
Von Jan Büren vor fast 3 Jahren hinzugefügt
SL/DB/Helper/Payment.pm | ||
---|---|---|
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. |
Auch abrufbar als: Unified diff
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.