9 use Support::TestSetup;
11 use List::Util qw(sum);
13 use SL::DB::Buchungsgruppe;
15 use SL::DB::Exchangerate;
23 use SL::DB::BankAccount;
24 use SL::DB::PaymentTerm;
27 my ($customer, $vendor, $currency_id, @parts, $buchungsgruppe, $buchungsgruppe7, $unit, $employee, $tax, $tax7, $tax_9, $taxzone, $payment_terms, $bank_account);
28 my ($transdate, $transdate2, $currency, $exchangerate, $exchangerate2);
29 my ($ar_chart,$bank,$ar_amount_chart, $ap_chart, $ap_amount_chart);
33 my $reset_state_counter = 0;
35 my $purchase_invoice_counter = 0; # used for generating purchase invnumber
38 SL::DB::Manager::InvoiceItem->delete_all(all => 1);
39 SL::DB::Manager::Invoice->delete_all(all => 1);
40 SL::DB::Manager::PurchaseInvoice->delete_all(all => 1);
41 SL::DB::Manager::Part->delete_all(all => 1);
42 SL::DB::Manager::Customer->delete_all(all => 1);
43 SL::DB::Manager::Vendor->delete_all(all => 1);
44 SL::DB::Manager::BankAccount->delete_all(all => 1);
45 SL::DB::Manager::PaymentTerm->delete_all(all => 1);
46 SL::DB::Manager::Exchangerate->delete_all(all => 1);
47 SL::DB::Manager::Currency->delete_all(where => [ name => 'CUR' ]);
53 return if $reset_state_counter;
55 $params{$_} ||= {} for qw(buchungsgruppe unit customer part tax vendor);
59 $transdate = DateTime->today;
60 $transdate2 = DateTime->today->add(days => 1);
62 $buchungsgruppe = SL::DB::Manager::Buchungsgruppe->find_by(description => 'Standard 19%', %{ $params{buchungsgruppe} }) || croak "No accounting group";
63 $buchungsgruppe7 = SL::DB::Manager::Buchungsgruppe->find_by(description => 'Standard 7%') || croak "No accounting group for 7\%";
64 $unit = SL::DB::Manager::Unit->find_by(name => 'kg', %{ $params{unit} }) || croak "No unit";
65 $employee = SL::DB::Manager::Employee->current || croak "No employee";
66 $tax = SL::DB::Manager::Tax->find_by(taxkey => 3, rate => 0.19, %{ $params{tax} }) || croak "No tax";
67 $tax7 = SL::DB::Manager::Tax->find_by(taxkey => 2, rate => 0.07) || croak "No tax for 7\%";
68 $taxzone = SL::DB::Manager::TaxZone->find_by( description => 'Inland') || croak "No taxzone";
69 $tax_9 = SL::DB::Manager::Tax->find_by(taxkey => 9, rate => 0.19, %{ $params{tax} }) || croak "No tax";
70 # $tax7 = SL::DB::Manager::Tax->find_by(taxkey => 2, rate => 0.07) || croak "No tax for 7\%";
72 $currency_id = $::instance_conf->get_currency_id;
74 $currency = SL::DB::Currency->new(name => 'CUR')->save;
75 $exchangerate = SL::DB::Exchangerate->new(transdate => $transdate,
78 currency_id => $currency->id,
80 $exchangerate2 = SL::DB::Exchangerate->new(transdate => $transdate2,
83 currency_id => $currency->id,
86 $customer = SL::DB::Customer->new(
87 name => 'Test Customer',
88 currency_id => $currency_id,
89 taxzone_id => $taxzone->id,
90 %{ $params{customer} }
93 $bank_account = SL::DB::BankAccount->new(
94 account_number => '123',
99 chart_id => SL::DB::Manager::Chart->find_by( description => 'Bank' )->id,
100 name => SL::DB::Manager::Chart->find_by( description => 'Bank' )->description,
103 $payment_terms = SL::DB::PaymentTerm->new(
104 description => 'payment',
105 description_long => 'payment',
108 percent_skonto => '0.05',
109 auto_calculation => 1,
112 $vendor = SL::DB::Vendor->new(
113 name => 'Test Vendor',
114 currency_id => $currency_id,
115 taxzone_id => $taxzone->id,
116 payment_id => $payment_terms->id,
122 push @parts, SL::DB::Part->new(
123 partnumber => 'T4254',
124 description => 'Fourty-two fifty-four',
127 buchungsgruppen_id => $buchungsgruppe->id,
132 push @parts, SL::DB::Part->new(
133 partnumber => 'T0815',
134 description => 'Zero EIGHT fifteeN @ 7%',
137 buchungsgruppen_id => $buchungsgruppe7->id,
141 push @parts, SL::DB::Part->new(
143 description => 'Testware 19%',
146 buchungsgruppen_id => $buchungsgruppe->id,
150 push @parts, SL::DB::Part->new(
152 description => 'Testware 7%',
155 buchungsgruppen_id => $buchungsgruppe7->id,
160 $ar_chart = SL::DB::Manager::Chart->find_by( accno => '1400' ); # Forderungen
161 $ap_chart = SL::DB::Manager::Chart->find_by( accno => '1600' ); # Verbindlichkeiten
162 $bank = SL::DB::Manager::Chart->find_by( accno => '1200' ); # Bank
163 $ar_amount_chart = SL::DB::Manager::Chart->find_by( accno => '8400' ); # Erlöse
164 $ap_amount_chart = SL::DB::Manager::Chart->find_by( accno => '3400' ); # Wareneingang 19%
166 $reset_state_counter++;
172 return SL::DB::Invoice->new(
173 customer_id => $customer->id,
174 currency_id => $currency_id,
175 employee_id => $employee->id,
176 salesman_id => $employee->id,
177 gldate => DateTime->today_local->to_kivitendo,
178 taxzone_id => $taxzone->id,
179 transdate => DateTime->today_local->to_kivitendo,
187 sub new_purchase_invoice {
189 # manually create a Kreditorenbuchung from scratch, ap + acc_trans bookings, as no helper exists yet, like $invoice->post.
190 # arap-Booking must come last in the acc_trans order
191 $purchase_invoice_counter++;
193 my $purchase_invoice = SL::DB::PurchaseInvoice->new(
194 vendor_id => $vendor->id,
195 invnumber => 'newap ' . $purchase_invoice_counter ,
196 currency_id => $currency_id,
197 employee_id => $employee->id,
198 gldate => DateTime->today_local->to_kivitendo,
199 taxzone_id => $taxzone->id,
200 transdate => DateTime->today_local->to_kivitendo,
210 my $expense_chart = SL::DB::Manager::Chart->find_by(accno => '3400');
211 my $expense_chart_booking= SL::DB::AccTransaction->new(
212 trans_id => $purchase_invoice->id,
213 chart_id => $expense_chart->id,
214 chart_link => $expense_chart->link,
216 transdate => $transdate,
219 tax_id => SL::DB::Manager::Tax->find_by(taxkey => 9)->id);
220 $expense_chart_booking->save;
222 my $tax_chart = SL::DB::Manager::Chart->find_by(accno => '1576');
223 my $tax_chart_booking= SL::DB::AccTransaction->new(
224 trans_id => $purchase_invoice->id,
225 chart_id => $tax_chart->id,
226 chart_link => $tax_chart->link,
228 transdate => $transdate,
231 tax_id => SL::DB::Manager::Tax->find_by(taxkey => 9)->id);
232 $tax_chart_booking->save;
233 $expense_chart = SL::DB::Manager::Chart->find_by(accno => '3300');
234 $expense_chart_booking= SL::DB::AccTransaction->new(
235 trans_id => $purchase_invoice->id,
236 chart_id => $expense_chart->id,
237 chart_link => $expense_chart->link,
239 transdate => $transdate,
242 tax_id => SL::DB::Manager::Tax->find_by(taxkey => 8)->id);
243 $expense_chart_booking->save;
246 $tax_chart = SL::DB::Manager::Chart->find_by(accno => '1571');
247 $tax_chart_booking= SL::DB::AccTransaction->new(
248 trans_id => $purchase_invoice->id,
249 chart_id => $tax_chart->id,
250 chart_link => $tax_chart->link,
252 transdate => $transdate,
255 tax_id => SL::DB::Manager::Tax->find_by(taxkey => 8)->id);
256 $tax_chart_booking->save;
257 my $arap_chart = SL::DB::Manager::Chart->find_by(accno => '1600');
258 my $arap_booking= SL::DB::AccTransaction->new(trans_id => $purchase_invoice->id,
259 chart_id => $arap_chart->id,
260 chart_link => $arap_chart->link,
262 transdate => $transdate,
265 tax_id => SL::DB::Manager::Tax->find_by(taxkey => 0)->id);
268 return $purchase_invoice;
274 my $part = delete($params{part}) || $parts[0];
276 return SL::DB::InvoiceItem->new(
277 parts_id => $part->id,
278 lastcost => $part->lastcost,
279 sellprice => $part->sellprice,
280 description => $part->description,
286 sub number_of_payments {
289 my $number_of_payments;
291 foreach my $transaction ( @{ $invoice->transactions } ) {
292 if ( $transaction->chart_link =~ /(AR_paid|AP_paid)/ ) {
293 $paid_amount += $transaction->amount ;
294 $number_of_payments++;
297 return ($number_of_payments, $paid_amount);
303 my $total = sum map { $_->amount } @{ $invoice->transactions };
305 return $::form->round_amount($total, 5);
311 sub test_default_invoice_one_item_19_without_skonto() {
312 reset_state() if $ALWAYS_RESET;
314 my $item = new_item(qty => 2.5);
315 my $invoice = new_invoice(
317 invoiceitems => [ $item ],
318 payment_id => $payment_terms->id,
322 my $purchase_invoice = new_purchase_invoice();
326 my %params = ( chart_id => $bank_account->chart_id,
327 transdate => DateTime->today_local->to_kivitendo
330 $params{amount} = '6.96';
331 $params{payment_type} = 'without_skonto';
333 $invoice->pay_invoice( %params );
335 my ($number_of_payments, $paid_amount) = number_of_payments($invoice);
336 my $total = total_amount($invoice);
338 my $title = 'default invoice, one item, 19% tax, without_skonto';
340 is($invoice->netamount, 5.85, "${title}: netamount");
341 is($invoice->amount, 6.96, "${title}: amount");
342 is($paid_amount, -6.96, "${title}: paid amount");
343 is($number_of_payments, 1, "${title}: 1 AR_paid booking");
344 is($invoice->paid, 6.96, "${title}: paid");
345 is($total, 0, "${title}: even balance");
349 sub test_default_invoice_one_item_19_without_skonto_overpaid() {
350 reset_state() if $ALWAYS_RESET;
352 my $item = new_item(qty => 2.5);
353 my $invoice = new_invoice(
355 invoiceitems => [ $item ],
356 payment_id => $payment_terms->id,
360 my $purchase_invoice = new_purchase_invoice();
364 my %params = ( chart_id => $bank_account->chart_id,
365 transdate => DateTime->today_local->to_kivitendo
368 $params{amount} = '16.96';
369 $params{payment_type} = 'without_skonto';
370 $invoice->pay_invoice( %params );
372 $params{amount} = '-10.00';
373 $invoice->pay_invoice( %params );
375 my ($number_of_payments, $paid_amount) = number_of_payments($invoice);
376 my $total = total_amount($invoice);
378 my $title = 'default invoice, one item, 19% tax, without_skonto';
380 is($invoice->netamount, 5.85, "${title}: netamount");
381 is($invoice->amount, 6.96, "${title}: amount");
382 is($paid_amount, -6.96, "${title}: paid amount");
383 is($number_of_payments, 2, "${title}: 1 AR_paid booking");
384 is($invoice->paid, 6.96, "${title}: paid");
385 is($total, 0, "${title}: even balance");
391 sub test_default_invoice_two_items_19_7_tax_with_skonto() {
392 reset_state() if $ALWAYS_RESET;
394 my $item1 = new_item(qty => 2.5);
395 my $item2 = new_item(qty => 1.2, part => $parts[1]);
396 my $invoice = new_invoice(
398 invoiceitems => [ $item1, $item2 ],
399 payment_id => $payment_terms->id,
404 my %params = ( chart_id => $bank_account->chart_id,
405 transdate => DateTime->today_local->to_kivitendo
408 $params{payment_type} = 'with_skonto_pt';
409 $params{amount} = $invoice->amount_less_skonto;
411 $invoice->pay_invoice( %params );
413 my ($number_of_payments, $paid_amount) = number_of_payments($invoice);
414 my $total = total_amount($invoice);
416 my $title = 'default invoice, two items, 19/7% tax with_skonto_pt';
418 is($invoice->netamount, 5.85 + 11.66, "${title}: netamount");
419 is($invoice->amount, 6.96 + 12.48, "${title}: amount");
420 is($paid_amount, -19.44, "${title}: paid amount");
421 is($invoice->paid, 19.44, "${title}: paid");
422 is($number_of_payments, 3, "${title}: 3 AR_paid bookings");
423 is($total, 0, "${title}: even balance");
426 sub test_default_invoice_two_items_19_7_tax_with_skonto_tax_included() {
427 reset_state() if $ALWAYS_RESET;
429 my $item1 = new_item(qty => 2.5);
430 my $item2 = new_item(qty => 1.2, part => $parts[1]);
431 my $invoice = new_invoice(
433 invoiceitems => [ $item1, $item2 ],
434 payment_id => $payment_terms->id,
439 my %params = ( chart_id => $bank_account->chart_id,
440 transdate => DateTime->today_local->to_kivitendo
443 $params{payment_type} = 'with_skonto_pt';
444 $params{amount} = $invoice->amount_less_skonto;
446 $invoice->pay_invoice( %params );
448 my ($number_of_payments, $paid_amount) = number_of_payments($invoice);
449 my $total = total_amount($invoice);
451 my $title = 'default invoice, two items, 19/7% tax with_skonto_pt';
453 is($invoice->netamount, 15.82, "${title}: netamount");
454 is($invoice->amount, 17.51, "${title}: amount");
455 is($paid_amount, -17.51, "${title}: paid amount");
456 is($invoice->paid, 17.51, "${title}: paid");
457 is($number_of_payments, 3, "${title}: 3 AR_paid bookings");
458 { local $TODO = "currently this test fails because the code writing the invoice is buggy, the calculation of skonto is correct";
459 is($total, 0, "${title}: even balance");
463 # test 3 : two items, without skonto
464 sub test_default_invoice_two_items_19_7_without_skonto() {
465 reset_state() if $ALWAYS_RESET;
467 my $item1 = new_item(qty => 2.5);
468 my $item2 = new_item(qty => 1.2, part => $parts[1]);
469 my $invoice = new_invoice(
471 invoiceitems => [ $item1, $item2 ],
472 payment_id => $payment_terms->id,
477 my %params = ( chart_id => $bank_account->chart_id,
478 transdate => DateTime->today_local->to_kivitendo
481 $params{amount} = '19.44'; # pass full amount
482 $params{payment_type} = 'without_skonto';
484 $invoice->pay_invoice( %params );
486 my ($number_of_payments, $paid_amount) = number_of_payments($invoice);
487 my $total = total_amount($invoice);
489 my $title = 'default invoice, two items, 19/7% tax without skonto';
491 is($invoice->netamount, 5.85 + 11.66, "${title}: netamount");
492 is($invoice->amount, 6.96 + 12.48, "${title}: amount");
493 is($paid_amount, -19.44, "${title}: paid amount");
494 is($invoice->paid, 19.44, "${title}: paid");
495 is($number_of_payments, 1, "${title}: 1 AR_paid bookings");
496 is($total, 0, "${title}: even balance");
500 sub test_default_invoice_two_items_19_7_without_skonto_incomplete_payment() {
501 reset_state() if $ALWAYS_RESET;
503 my $item1 = new_item(qty => 2.5);
504 my $item2 = new_item(qty => 1.2, part => $parts[1]);
505 my $invoice = new_invoice(
507 invoiceitems => [ $item1, $item2 ],
508 payment_id => $payment_terms->id,
512 $invoice->pay_invoice( amount => '9.44',
513 payment_type => 'without_skonto',
514 chart_id => $bank_account->chart_id,
515 transdate => DateTime->today_local->to_kivitendo,
518 my ($number_of_payments, $paid_amount) = number_of_payments($invoice);
519 my $total = total_amount($invoice);
521 my $title = 'default invoice, two items, 19/7% tax without skonto incomplete payment';
523 is($invoice->netamount, 5.85 + 11.66, "${title}: netamount");
524 is($invoice->amount, 6.96 + 12.48, "${title}: amount");
525 is($paid_amount, -9.44, "${title}: paid amount");
526 is($invoice->paid, 9.44, "${title}: paid");
527 is($number_of_payments, 1, "${title}: 1 AR_paid bookings");
528 is($total, 0, "${title}: even balance");
532 sub test_default_invoice_two_items_19_7_tax_without_skonto_multiple_payments() {
533 reset_state() if $ALWAYS_RESET;
535 my $item1 = new_item(qty => 2.5);
536 my $item2 = new_item(qty => 1.2, part => $parts[1]);
537 my $invoice = new_invoice(
539 invoiceitems => [ $item1, $item2 ],
540 payment_id => $payment_terms->id,
544 $invoice->pay_invoice( amount => '9.44',
545 payment_type => 'without_skonto',
546 chart_id => $bank_account->chart_id,
547 transdate => DateTime->today_local->to_kivitendo
549 $invoice->pay_invoice( amount => '10.00',
550 chart_id => $bank_account->chart_id,
551 transdate => DateTime->today_local->to_kivitendo
554 my ($number_of_payments, $paid_amount) = number_of_payments($invoice);
555 my $total = total_amount($invoice);
557 my $title = 'default invoice, two items, 19/7% tax not included';
559 is($invoice->netamount, 5.85 + 11.66, "${title}: netamount");
560 is($invoice->amount, 6.96 + 12.48, "${title}: amount");
561 is($paid_amount, -19.44, "${title}: paid amount");
562 is($invoice->paid, 19.44, "${title}: paid");
563 is($number_of_payments, 2, "${title}: 2 AR_paid bookings");
564 is($total, 0, "${title}: even balance");
569 sub test_default_invoice_two_items_19_7_tax_without_skonto_multiple_payments_final_difference_as_skonto() {
570 reset_state() if $ALWAYS_RESET;
572 my $item1 = new_item(qty => 2.5);
573 my $item2 = new_item(qty => 1.2, part => $parts[1]);
574 my $invoice = new_invoice(
576 invoiceitems => [ $item1, $item2 ],
577 payment_id => $payment_terms->id,
581 $invoice->pay_invoice( amount => '9.44',
582 payment_type => 'without_skonto',
583 chart_id => $bank_account->chart_id,
584 transdate => DateTime->today_local->to_kivitendo
586 $invoice->pay_invoice( amount => '8.73',
587 payment_type => 'without_skonto',
588 chart_id => $bank_account->chart_id,
589 transdate => DateTime->today_local->to_kivitendo
591 $invoice->pay_invoice( amount => $invoice->open_amount,
592 payment_type => 'difference_as_skonto',
593 chart_id => $bank_account->chart_id,
594 transdate => DateTime->today_local->to_kivitendo
597 my ($number_of_payments, $paid_amount) = number_of_payments($invoice);
598 my $total = total_amount($invoice);
600 my $title = 'default invoice, two items, 19/7% tax not included';
602 is($invoice->netamount, 5.85 + 11.66, "${title}: netamount");
603 is($invoice->amount, 6.96 + 12.48, "${title}: amount");
604 is($paid_amount, -19.44, "${title}: paid amount");
605 is($invoice->paid, 19.44, "${title}: paid");
606 is($number_of_payments, 4, "${title}: 4 AR_paid bookings");
607 is($total, 0, "${title}: even balance");
611 sub test_default_invoice_two_items_19_7_tax_without_skonto_multiple_payments_final_difference_as_skonto_1cent() {
612 reset_state() if $ALWAYS_RESET;
614 # if there is only one cent left there can only be one skonto booking, the
615 # error handling should choose the highest amount, which is the 7% account
616 # (11.66) rather than the 19% account (5.85). The actual tax amount is
617 # higher for the 19% case, though (1.11 compared to 0.82)
619 my $item1 = new_item(qty => 2.5);
620 my $item2 = new_item(qty => 1.2, part => $parts[1]);
621 my $invoice = new_invoice(
623 invoiceitems => [ $item1, $item2 ],
624 payment_id => $payment_terms->id,
628 $invoice->pay_invoice( amount => '19.42',
629 payment_type => 'without_skonto',
630 chart_id => $bank_account->chart_id,
631 transdate => DateTime->today_local->to_kivitendo
633 $invoice->pay_invoice( amount => $invoice->open_amount,
634 payment_type => 'difference_as_skonto',
635 chart_id => $bank_account->chart_id,
636 transdate => DateTime->today_local->to_kivitendo
639 my ($number_of_payments, $paid_amount) = number_of_payments($invoice);
640 my $total = total_amount($invoice);
642 my $title = 'default invoice, two items, 19/7% tax not included';
644 is($invoice->netamount, 5.85 + 11.66, "${title}: netamount");
645 is($invoice->amount, 6.96 + 12.48, "${title}: amount");
646 is($paid_amount, -19.44, "${title}: paid amount");
647 is($invoice->paid, 19.44, "${title}: paid");
648 is($number_of_payments, 3, "${title}: 2 AR_paid bookings");
649 is($total, 0, "${title}: even balance");
653 sub test_default_invoice_two_items_19_7_tax_without_skonto_multiple_payments_final_difference_as_skonto_2cent() {
654 reset_state() if $ALWAYS_RESET;
656 # if there are two cents left there will be two skonto bookings, 1 cent each
657 my $item1 = new_item(qty => 2.5);
658 my $item2 = new_item(qty => 1.2, part => $parts[1]);
659 my $invoice = new_invoice(
661 invoiceitems => [ $item1, $item2 ],
662 payment_id => $payment_terms->id,
666 $invoice->pay_invoice( amount => '19.42',
667 payment_type => 'without_skonto',
668 chart_id => $bank_account->chart_id,
669 transdate => DateTime->today_local->to_kivitendo
671 $invoice->pay_invoice( amount => $invoice->open_amount,
672 payment_type => 'difference_as_skonto',
673 chart_id => $bank_account->chart_id,
674 transdate => DateTime->today_local->to_kivitendo
677 my ($number_of_payments, $paid_amount) = number_of_payments($invoice);
678 my $total = total_amount($invoice);
680 my $title = 'default invoice, two items, 19/7% tax not included';
682 is($invoice->netamount, 5.85 + 11.66, "${title}: netamount");
683 is($invoice->amount, 6.96 + 12.48, "${title}: amount");
684 is($paid_amount, -19.44, "${title}: paid amount");
685 is($invoice->paid, 19.44, "${title}: paid");
686 is($number_of_payments, 3, "${title}: 3 AR_paid bookings");
687 is($total, 0, "${title}: even balance");
691 sub test_default_invoice_one_item_19_multiple_payment_final_difference_as_skonto() {
692 reset_state() if $ALWAYS_RESET;
694 my $item = new_item(qty => 2.5);
695 my $invoice = new_invoice(
697 invoiceitems => [ $item ],
698 payment_id => $payment_terms->id,
703 my %params = ( chart_id => $bank_account->chart_id,
704 transdate => DateTime->today_local->to_kivitendo
707 $params{amount} = '2.32';
708 $params{payment_type} = 'without_skonto';
709 $invoice->pay_invoice( %params );
711 $params{amount} = '3.81';
712 $params{payment_type} = 'without_skonto';
713 $invoice->pay_invoice( %params );
715 $params{amount} = $invoice->open_amount; # set amount, otherwise previous 3.81 is used
716 $params{payment_type} = 'difference_as_skonto';
717 $invoice->pay_invoice( %params );
719 my ($number_of_payments, $paid_amount) = number_of_payments($invoice);
720 my $total = total_amount($invoice);
722 my $title = 'default invoice, one item, 19% tax, without_skonto';
724 is($invoice->netamount, 5.85, "${title}: netamount");
725 is($invoice->amount, 6.96, "${title}: amount");
726 is($paid_amount, -6.96, "${title}: paid amount");
727 is($number_of_payments, 3, "${title}: 3 AR_paid booking");
728 is($invoice->paid, 6.96, "${title}: paid");
729 is($total, 0, "${title}: even balance");
733 sub test_default_invoice_one_item_19_multiple_payment_final_difference_as_skonto_1cent() {
734 reset_state() if $ALWAYS_RESET;
736 my $item = new_item(qty => 2.5);
737 my $invoice = new_invoice(
739 invoiceitems => [ $item ],
740 payment_id => $payment_terms->id,
745 my %params = ( chart_id => $bank_account->chart_id,
746 transdate => DateTime->today_local->to_kivitendo
749 $params{amount} = '6.95';
750 $params{payment_type} = 'without_skonto';
751 $invoice->pay_invoice( %params );
753 $params{amount} = $invoice->open_amount; # set amount, otherwise previous value 6.95 is used
754 $params{payment_type} = 'difference_as_skonto';
755 $invoice->pay_invoice( %params );
757 my ($number_of_payments, $paid_amount) = number_of_payments($invoice);
758 my $total = total_amount($invoice);
760 my $title = 'default invoice, one item, 19% tax, without_skonto';
762 is($invoice->netamount, 5.85, "${title}: netamount");
763 is($invoice->amount, 6.96, "${title}: amount");
764 is($paid_amount, -6.96, "${title}: paid amount");
765 is($number_of_payments, 2, "${title}: 3 AR_paid booking");
766 is($invoice->paid, 6.96, "${title}: paid");
767 is($total, 0, "${title}: even balance");
771 # test 3 : two items, without skonto
772 sub test_default_purchase_invoice_two_charts_19_7_without_skonto() {
773 reset_state() if $ALWAYS_RESET;
775 my $purchase_invoice = new_purchase_invoice();
777 my %params = ( chart_id => $bank_account->chart_id,
778 transdate => DateTime->today_local->to_kivitendo
781 $params{amount} = '226'; # pass full amount
782 $params{payment_type} = 'without_skonto';
784 $purchase_invoice->pay_invoice( %params );
786 my ($number_of_payments, $paid_amount) = number_of_payments($purchase_invoice);
787 my $total = total_amount($purchase_invoice);
789 my $title = 'default invoice, two items, 19/7% tax without skonto';
791 is($paid_amount, 226, "${title}: paid amount");
792 is($number_of_payments, 1, "${title}: 1 AP_paid bookings");
793 is($total, 0, "${title}: even balance");
797 sub test_default_purchase_invoice_two_charts_19_7_with_skonto() {
798 reset_state() if $ALWAYS_RESET;
800 my $purchase_invoice = new_purchase_invoice();
802 my %params = ( chart_id => $bank_account->chart_id,
803 transdate => DateTime->today_local->to_kivitendo
806 # $params{amount} = '226'; # pass full amount
807 $params{payment_type} = 'with_skonto_pt';
809 $purchase_invoice->pay_invoice( %params );
811 my ($number_of_payments, $paid_amount) = number_of_payments($purchase_invoice);
812 my $total = total_amount($purchase_invoice);
814 my $title = 'default invoice, two items, 19/7% tax without skonto';
816 is($paid_amount, 226, "${title}: paid amount");
817 is($number_of_payments, 3, "${title}: 1 AP_paid bookings");
818 is($total, 0, "${title}: even balance");
822 sub test_default_purchase_invoice_two_charts_19_7_tax_partial_unrounded_payment_without_skonto() {
823 # check whether unrounded amounts passed via $params{amount} are rounded for without_skonto case
824 reset_state() if $ALWAYS_RESET;
825 my $purchase_invoice = new_purchase_invoice();
826 $purchase_invoice->pay_invoice(
827 amount => ( $purchase_invoice->amount / 3 * 2),
828 payment_type => 'without_skonto',
829 chart_id => $bank_account->chart_id,
830 transdate => DateTime->today_local->to_kivitendo
832 my ($number_of_payments, $paid_amount) = number_of_payments($purchase_invoice);
833 my $total = total_amount($purchase_invoice);
835 my $title = 'default purchase_invoice, two charts, 19/7% tax multiple payments with final difference as skonto';
837 is($paid_amount, 150.67, "${title}: paid amount");
838 is($number_of_payments, 1, "${title}: 1 AP_paid bookings");
839 is($total, 0, "${title}: even balance");
843 sub test_default_purchase_invoice_two_charts_19_7_tax_without_skonto_multiple_payments_final_difference_as_skonto() {
844 reset_state() if $ALWAYS_RESET;
846 my $purchase_invoice = new_purchase_invoice();
848 # pay 2/3 and 1/5, leaves 3.83% to be used as Skonto
849 $purchase_invoice->pay_invoice(
850 amount => ( $purchase_invoice->amount / 3 * 2),
851 payment_type => 'without_skonto',
852 chart_id => $bank_account->chart_id,
853 transdate => DateTime->today_local->to_kivitendo
855 $purchase_invoice->pay_invoice(
856 amount => ( $purchase_invoice->amount / 5 ),
857 payment_type => 'without_skonto',
858 chart_id => $bank_account->chart_id,
859 transdate => DateTime->today_local->to_kivitendo
861 $purchase_invoice->pay_invoice(
862 payment_type => 'difference_as_skonto',
863 chart_id => $bank_account->chart_id,
864 transdate => DateTime->today_local->to_kivitendo
867 my ($number_of_payments, $paid_amount) = number_of_payments($purchase_invoice);
868 my $total = total_amount($purchase_invoice);
870 my $title = 'default purchase_invoice, two charts, 19/7% tax multiple payments with final difference as skonto';
872 is($paid_amount, 226, "${title}: paid amount");
873 is($number_of_payments, 4, "${title}: 1 AP_paid bookings");
874 is($total, 0, "${title}: even balance");
879 sub test_default_invoice_two_items_19_7_tax_with_skonto_50_50() {
880 reset_state() if $ALWAYS_RESET;
882 my $item1 = new_item(qty => 1, part => $parts[2]);
883 my $item2 = new_item(qty => 1, part => $parts[3]);
884 my $invoice = new_invoice(
886 invoiceitems => [ $item1, $item2 ],
887 payment_id => $payment_terms->id,
892 my %params = ( chart_id => $bank_account->chart_id,
893 transdate => DateTime->today_local->to_kivitendo
896 $params{amount} = $invoice->amount_less_skonto;
897 $params{payment_type} = 'with_skonto_pt';
899 $invoice->pay_invoice( %params );
901 my ($number_of_payments, $paid_amount) = number_of_payments($invoice);
902 my $total = total_amount($invoice);
904 my $title = 'default invoice, two items, 19/7% tax with_skonto_pt 50/50';
906 is($invoice->netamount, 100, "${title}: netamount");
907 is($invoice->amount, 113, "${title}: amount");
908 is($paid_amount, -113, "${title}: paid amount");
909 is($invoice->paid, 113, "${title}: paid");
910 is($number_of_payments, 3, "${title}: 3 AR_paid bookings");
911 is($total, 0, "${title}: even balance");
915 sub test_default_invoice_four_items_19_7_tax_with_skonto_4x_25() {
916 reset_state() if $ALWAYS_RESET;
918 my $item1 = new_item(qty => 0.5, part => $parts[2]);
919 my $item2 = new_item(qty => 0.5, part => $parts[3]);
920 my $item3 = new_item(qty => 0.5, part => $parts[2]);
921 my $item4 = new_item(qty => 0.5, part => $parts[3]);
922 my $invoice = new_invoice(
924 invoiceitems => [ $item1, $item2, $item3, $item4 ],
925 payment_id => $payment_terms->id,
930 my %params = ( chart_id => $bank_account->chart_id,
931 transdate => DateTime->today_local->to_kivitendo
934 $params{amount} = $invoice->amount_less_skonto;
935 $params{payment_type} = 'with_skonto_pt';
937 $invoice->pay_invoice( %params );
939 my ($number_of_payments, $paid_amount) = number_of_payments($invoice);
940 my $total = total_amount($invoice);
942 my $title = 'default invoice, four items, 19/7% tax with_skonto_pt 4x25';
944 is($invoice->netamount , 100 , "${title}: netamount");
945 is($invoice->amount , 113 , "${title}: amount");
946 is($paid_amount , -113 , "${title}: paid amount");
947 is($invoice->paid , 113 , "${title}: paid");
948 is($number_of_payments , 3 , "${title}: 3 AR_paid bookings");
949 is($total , 0 , "${title}: even balance");
952 sub test_default_invoice_four_items_19_7_tax_with_skonto_4x_25_tax_included() {
953 reset_state() if $ALWAYS_RESET;
955 my $item1 = new_item(qty => 0.5, part => $parts[2]);
956 my $item2 = new_item(qty => 0.5, part => $parts[3]);
957 my $item3 = new_item(qty => 0.5, part => $parts[2]);
958 my $item4 = new_item(qty => 0.5, part => $parts[3]);
959 my $invoice = new_invoice(
961 invoiceitems => [ $item1, $item2, $item3, $item4 ],
962 payment_id => $payment_terms->id,
967 my %params = ( chart_id => $bank_account->chart_id,
968 transdate => DateTime->today_local->to_kivitendo
971 $params{amount} = $invoice->amount_less_skonto;
972 $params{payment_type} = 'with_skonto_pt';
974 $invoice->pay_invoice( %params );
976 my ($number_of_payments, $paid_amount) = number_of_payments($invoice);
977 my $total = total_amount($invoice);
979 my $title = 'default invoice, four items, 19/7% tax with_skonto_pt 4x25';
981 is($invoice->netamount, 88.75, "${title}: netamount");
982 is($invoice->amount, 100, "${title}: amount");
983 is($paid_amount, -100, "${title}: paid amount");
984 is($invoice->paid, 100, "${title}: paid");
985 is($number_of_payments, 3, "${title}: 3 AR_paid bookings");
986 { local $TODO = "currently this test fails because the code writing the invoice is buggy, the calculation of skonto is correct";
987 is($total, 0, "${title}: even balance");
991 sub test_default_invoice_four_items_19_7_tax_with_skonto_4x_25_multiple() {
992 reset_state() if $ALWAYS_RESET;
994 my $item1 = new_item(qty => 0.5, part => $parts[2]);
995 my $item2 = new_item(qty => 0.5, part => $parts[3]);
996 my $item3 = new_item(qty => 0.5, part => $parts[2]);
997 my $item4 = new_item(qty => 0.5, part => $parts[3]);
998 my $invoice = new_invoice(
1000 invoiceitems => [ $item1, $item2, $item3, $item4 ],
1001 payment_id => $payment_terms->id,
1005 $invoice->pay_invoice( amount => '90',
1006 payment_type => 'without_skonto',
1007 chart_id => $bank_account->chart_id,
1008 transdate => DateTime->today_local->to_kivitendo
1010 $invoice->pay_invoice( payment_type => 'difference_as_skonto',
1011 chart_id => $bank_account->chart_id,
1012 transdate => DateTime->today_local->to_kivitendo
1015 my ($number_of_payments, $paid_amount) = number_of_payments($invoice);
1016 my $total = total_amount($invoice);
1018 my $title = 'default invoice, four items, 19/7% tax with_skonto_pt 4x25';
1020 is($invoice->netamount, 100, "${title}: netamount");
1021 is($invoice->amount, 113, "${title}: amount");
1022 is($paid_amount, -113, "${title}: paid amount");
1023 is($invoice->paid, 113, "${title}: paid");
1024 is($number_of_payments, 3, "${title}: 3 AR_paid bookings");
1025 is($total, 0, "${title}: even balance: this will fail due to rounding error in invoice post, not the skonto");
1028 sub test_ar_currency_tax_not_included_and_payment {
1029 my $netamount = $::form->round_amount(75 * $exchangerate->sell,2); # 75 in CUR, 100.00 in EUR
1030 my $amount = $::form->round_amount($netamount * 1.19,2); # 100 in CUR, 119.00 in EUR
1031 my $invoice = SL::DB::Invoice->new(
1034 netamount => $netamount,
1035 transdate => $transdate,
1037 customer_id => $customer->id,
1038 taxzone_id => $customer->taxzone_id,
1039 currency_id => $currency->id,
1041 notes => 'test_ar_currency_tax_not_included_and_payment',
1043 $invoice->add_ar_amount_row(
1044 amount => $invoice->netamount,
1045 chart => $ar_amount_chart,
1049 $invoice->create_ar_row(chart => $ar_chart);
1052 is(SL::DB::Manager::Invoice->get_all_count(where => [ invoice => 0 ]), 1, 'there is one ar transaction');
1053 is($invoice->currency_id , $currency->id , 'currency_id has been saved');
1054 is($invoice->netamount , 100 , 'ar amount has been converted');
1055 is($invoice->amount , 119 , 'ar amount has been converted');
1056 is($invoice->taxincluded , 0 , 'ar transaction doesn\'t have taxincluded');
1057 is(SL::DB::Manager::AccTransaction->find_by(chart_id => $ar_amount_chart->id, trans_id => $invoice->id)->amount, '100.00000', $ar_amount_chart->accno . ': has been converted for currency');
1058 is(SL::DB::Manager::AccTransaction->find_by(chart_id => $ar_chart->id, trans_id => $invoice->id)->amount, '-119.00000', $ar_chart->accno . ': has been converted for currency');
1060 $invoice->pay_invoice(chart_id => $bank->id,
1063 transdate => $transdate->to_kivitendo,
1065 $invoice->pay_invoice(chart_id => $bank->id,
1068 transdate => $transdate->to_kivitendo,
1070 # $invoice->pay_invoice(chart_id => $bank->id,
1072 # transdate => $transdate2->to_kivitendo,
1074 is(scalar @{$invoice->transactions}, 9, 'ar transaction has 9 transactions (incl. fxtransactions)');
1075 is($invoice->paid, $invoice->amount, 'ar transaction paid = amount in default currency');
1078 sub test_ar_currency_tax_included {
1079 # we want the acc_trans amount to be 100
1080 my $amount = $::form->round_amount(75 * $exchangerate->sell * 1.19);
1081 my $netamount = $::form->round_amount($amount / 1.19,2);
1082 my $invoice = SL::DB::Invoice->new(
1084 amount => 119, #$amount,
1085 netamount => 100, #$netamount,
1086 transdate => $transdate,
1088 customer_id => $customer->id,
1089 taxzone_id => $customer->taxzone_id,
1090 currency_id => $currency->id,
1091 notes => 'test_ar_currency_tax_included',
1094 $invoice->add_ar_amount_row( # should take care of taxincluded
1095 amount => $invoice->amount, # tax included in local currency
1096 chart => $ar_amount_chart,
1100 $invoice->create_ar_row( chart => $ar_chart );
1102 is(SL::DB::Manager::Invoice->get_all_count(where => [ invoice => 0 ]), 2, 'there are now two ar transactions');
1103 is($invoice->currency_id , $currency->id , 'currency_id has been saved');
1104 is($invoice->amount , $amount , 'amount ok');
1105 is($invoice->netamount , $netamount , 'netamount ok');
1106 is($invoice->taxincluded , 1 , 'ar transaction has taxincluded');
1107 is(SL::DB::Manager::AccTransaction->find_by(chart_id => $ar_amount_chart->id, trans_id => $invoice->id)->amount, '100.00000', $ar_amount_chart->accno . ': has been converted for currency');
1108 is(SL::DB::Manager::AccTransaction->find_by(chart_id => $ar_chart->id, trans_id => $invoice->id)->amount, '-119.00000', $ar_chart->accno . ': has been converted for currency');
1109 $invoice->pay_invoice(chart_id => $bank->id,
1112 transdate => $transdate->to_kivitendo,
1117 sub test_ap_currency_tax_not_included_and_payment {
1118 my $netamount = $::form->round_amount(75 * $exchangerate->sell,2); # 75 in CUR, 100.00 in EUR
1119 my $amount = $::form->round_amount($netamount * 1.19,2); # 100 in CUR, 119.00 in EUR
1120 my $invoice = SL::DB::PurchaseInvoice->new(
1122 invnumber => 'test_ap_currency_tax_not_included_and_payment',
1124 netamount => $netamount,
1125 transdate => $transdate,
1127 vendor_id => $vendor->id,
1128 taxzone_id => $vendor->taxzone_id,
1129 currency_id => $currency->id,
1131 notes => 'test_ap_currency_tax_not_included_and_payment',
1133 $invoice->add_ap_amount_row(
1134 amount => $invoice->netamount,
1135 chart => $ap_amount_chart,
1136 tax_id => $tax_9->id,
1139 $invoice->create_ap_row(chart => $ap_chart);
1142 is($invoice->currency_id, $currency->id, 'currency_id has been saved');
1143 is($invoice->netamount, 100, 'ap amount has been converted');
1144 is($invoice->amount, 119, 'ap amount has been converted');
1145 is($invoice->taxincluded, 0, 'ap transaction doesn\'t have taxincluded');
1146 is(SL::DB::Manager::AccTransaction->find_by(chart_id => $ap_amount_chart->id, trans_id => $invoice->id)->amount, '-100.00000', $ap_amount_chart->accno . ': has been converted for currency');
1147 is(SL::DB::Manager::AccTransaction->find_by(chart_id => $ap_chart->id, trans_id => $invoice->id)->amount, '119.00000', $ap_chart->accno . ': has been converted for currency');
1149 $invoice->pay_invoice(chart_id => $bank->id,
1152 transdate => $transdate->to_kivitendo,
1154 $invoice->pay_invoice(chart_id => $bank->id,
1157 transdate => $transdate->to_kivitendo,
1159 # $invoice->pay_invoice(chart_id => $bank->id,
1161 # transdate => $transdate2->to_kivitendo,
1163 is(scalar @{$invoice->transactions}, 9, 'ap transaction has 9 transactions (incl. fxtransactions)');
1164 is($invoice->paid, $invoice->amount, 'ap transaction paid = amount in default currency');
1167 sub test_ap_currency_tax_included {
1168 # we want the acc_trans amount to be 100
1169 my $amount = $::form->round_amount(75 * $exchangerate->sell * 1.19);
1170 my $netamount = $::form->round_amount($amount / 1.19,2);
1171 my $invoice = SL::DB::PurchaseInvoice->new(
1173 amount => 119, #$amount,
1174 netamount => 100, #$netamount,
1175 transdate => $transdate,
1177 vendor_id => $vendor->id,
1178 taxzone_id => $vendor->taxzone_id,
1179 currency_id => $currency->id,
1180 notes => 'test_ap_currency_tax_included',
1181 invnumber => 'test_ap_currency_tax_included',
1184 $invoice->add_ap_amount_row( # should take care of taxincluded
1185 amount => $invoice->amount, # tax included in local currency
1186 chart => $ap_amount_chart,
1187 tax_id => $tax_9->id,
1190 $invoice->create_ap_row( chart => $ap_chart );
1192 is($invoice->currency_id , $currency->id , 'currency_id has been saved');
1193 is($invoice->amount , $amount , 'amount ok');
1194 is($invoice->netamount , $netamount , 'netamount ok');
1195 is($invoice->taxincluded , 1 , 'ap transaction has taxincluded');
1196 is(SL::DB::Manager::AccTransaction->find_by(chart_id => $ap_amount_chart->id, trans_id => $invoice->id)->amount, '-100.00000', $ap_amount_chart->accno . ': has been converted for currency');
1197 is(SL::DB::Manager::AccTransaction->find_by(chart_id => $ap_chart->id, trans_id => $invoice->id)->amount, '119.00000', $ap_chart->accno . ': has been converted for currency');
1199 $invoice->pay_invoice(chart_id => $bank->id,
1202 transdate => $transdate->to_kivitendo,
1207 Support::TestSetup::login();
1210 # test cases: without_skonto
1211 test_default_invoice_one_item_19_without_skonto();
1212 test_default_invoice_two_items_19_7_tax_with_skonto();
1213 test_default_invoice_two_items_19_7_without_skonto();
1214 test_default_invoice_two_items_19_7_without_skonto_incomplete_payment();
1215 test_default_invoice_two_items_19_7_tax_without_skonto_multiple_payments();
1216 test_default_purchase_invoice_two_charts_19_7_without_skonto();
1217 test_default_purchase_invoice_two_charts_19_7_tax_partial_unrounded_payment_without_skonto();
1218 test_default_invoice_one_item_19_without_skonto_overpaid();
1220 # test cases: difference_as_skonto
1221 test_default_invoice_two_items_19_7_tax_without_skonto_multiple_payments_final_difference_as_skonto();
1222 test_default_invoice_two_items_19_7_tax_without_skonto_multiple_payments_final_difference_as_skonto_1cent();
1223 test_default_invoice_two_items_19_7_tax_without_skonto_multiple_payments_final_difference_as_skonto_2cent();
1224 test_default_invoice_one_item_19_multiple_payment_final_difference_as_skonto();
1225 test_default_invoice_one_item_19_multiple_payment_final_difference_as_skonto_1cent();
1226 test_default_purchase_invoice_two_charts_19_7_tax_without_skonto_multiple_payments_final_difference_as_skonto();
1228 # test cases: with_skonto_pt
1229 test_default_invoice_two_items_19_7_tax_with_skonto_50_50();
1230 test_default_invoice_four_items_19_7_tax_with_skonto_4x_25();
1231 test_default_invoice_four_items_19_7_tax_with_skonto_4x_25_multiple();
1232 test_default_purchase_invoice_two_charts_19_7_with_skonto();
1233 test_default_invoice_four_items_19_7_tax_with_skonto_4x_25_tax_included();
1234 test_default_invoice_two_items_19_7_tax_with_skonto_tax_included();
1236 # test payment of ar and ap transactions with currency and tax included/not included
1237 test_ar_currency_tax_not_included_and_payment();
1238 test_ar_currency_tax_included();
1239 test_ap_currency_tax_not_included_and_payment();
1240 test_ap_currency_tax_included();
1242 # remove all created data at end of test