X-Git-Url: http://wagnertech.de/gitweb/gitweb.cgi/mfinanz.git/blobdiff_plain/4b7f17c8d92bff2e9f495d476a650dc3897c3097..a87694dc525b4eabff11029f1e5401621e2bd3bc:/t/db_helper/payment.t diff --git a/t/db_helper/payment.t b/t/db_helper/payment.t new file mode 100644 index 000000000..18da821f2 --- /dev/null +++ b/t/db_helper/payment.t @@ -0,0 +1,1031 @@ +use Test::More; + +use strict; + +use lib 't'; +use utf8; + +use Carp; +use Support::TestSetup; +use Test::Exception; +use List::Util qw(sum); + +use SL::DB::Buchungsgruppe; +use SL::DB::Currency; +use SL::DB::Customer; +use SL::DB::Vendor; +use SL::DB::Employee; +use SL::DB::Invoice; +use SL::DB::Part; +use SL::DB::Unit; +use SL::DB::TaxZone; +use SL::DB::BankAccount; +use SL::DB::PaymentTerm; + +my ($customer, $vendor, $currency_id, @parts, $buchungsgruppe, $buchungsgruppe7, $unit, $employee, $tax, $tax7, $taxzone, $payment_terms, $bank_account); + +my $ALWAYS_RESET = 1; + +my $reset_state_counter = 0; + +my $purchase_invoice_counter = 0; # used for generating purchase invnumber + +sub clear_up { + SL::DB::Manager::InvoiceItem->delete_all(all => 1); + SL::DB::Manager::Invoice->delete_all(all => 1); + SL::DB::Manager::PurchaseInvoice->delete_all(all => 1); + SL::DB::Manager::Part->delete_all(all => 1); + SL::DB::Manager::Customer->delete_all(all => 1); + SL::DB::Manager::Vendor->delete_all(all => 1); + SL::DB::Manager::BankAccount->delete_all(all => 1); + SL::DB::Manager::PaymentTerm->delete_all(all => 1); +}; + +sub reset_state { + my %params = @_; + + return if $reset_state_counter; + + $params{$_} ||= {} for qw(buchungsgruppe unit customer part tax vendor); + + clear_up(); + + + $buchungsgruppe = SL::DB::Manager::Buchungsgruppe->find_by(description => 'Standard 19%', %{ $params{buchungsgruppe} }) || croak "No accounting group"; + $buchungsgruppe7 = SL::DB::Manager::Buchungsgruppe->find_by(description => 'Standard 7%') || croak "No accounting group for 7\%"; + $unit = SL::DB::Manager::Unit->find_by(name => 'kg', %{ $params{unit} }) || croak "No unit"; + $employee = SL::DB::Manager::Employee->current || croak "No employee"; + $tax = SL::DB::Manager::Tax->find_by(taxkey => 3, rate => 0.19, %{ $params{tax} }) || croak "No tax"; + $tax7 = SL::DB::Manager::Tax->find_by(taxkey => 2, rate => 0.07) || croak "No tax for 7\%"; + $taxzone = SL::DB::Manager::TaxZone->find_by( description => 'Inland') || croak "No taxzone"; + + $currency_id = $::instance_conf->get_currency_id; + + $customer = SL::DB::Customer->new( + name => 'Test Customer', + currency_id => $currency_id, + taxzone_id => $taxzone->id, + %{ $params{customer} } + )->save; + + $bank_account = SL::DB::BankAccount->new( + account_number => '123', + bank_code => '123', + iban => '123', + bic => '123', + bank => '123', + chart_id => SL::DB::Manager::Chart->find_by( description => 'Bank' )->id, + name => SL::DB::Manager::Chart->find_by( description => 'Bank' )->description, + )->save; + + $payment_terms = SL::DB::PaymentTerm->new( + description => 'payment', + description_long => 'payment', + terms_netto => '30', + terms_skonto => '5', + percent_skonto => '0.05' + )->save; + + $vendor = SL::DB::Vendor->new( + name => 'Test Vendor', + currency_id => $currency_id, + taxzone_id => $taxzone->id, + payment_id => $payment_terms->id, + %{ $params{vendor} } + )->save; + + + @parts = (); + push @parts, SL::DB::Part->new( + partnumber => 'T4254', + description => 'Fourty-two fifty-four', + lastcost => 1.93, + sellprice => 2.34, + buchungsgruppen_id => $buchungsgruppe->id, + unit => $unit->name, + %{ $params{part1} } + )->save; + + push @parts, SL::DB::Part->new( + partnumber => 'T0815', + description => 'Zero EIGHT fifteeN @ 7%', + lastcost => 5.473, + sellprice => 9.714, + buchungsgruppen_id => $buchungsgruppe7->id, + unit => $unit->name, + %{ $params{part2} } + )->save; + push @parts, SL::DB::Part->new( + partnumber => '19%', + description => 'Testware 19%', + lastcost => 0, + sellprice => 50, + buchungsgruppen_id => $buchungsgruppe->id, + unit => $unit->name, + %{ $params{part3} } + )->save; + push @parts, SL::DB::Part->new( + partnumber => '7%', + description => 'Testware 7%', + lastcost => 0, + sellprice => 50, + buchungsgruppen_id => $buchungsgruppe7->id, + unit => $unit->name, + %{ $params{part4} } + )->save; + + $reset_state_counter++; +} + +sub new_invoice { + my %params = @_; + + return SL::DB::Invoice->new( + customer_id => $customer->id, + currency_id => $currency_id, + employee_id => $employee->id, + salesman_id => $employee->id, + gldate => DateTime->today_local->to_kivitendo, + taxzone_id => $taxzone->id, + transdate => DateTime->today_local->to_kivitendo, + invoice => 1, + type => 'invoice', + %params, + ); + +} + +sub new_purchase_invoice { + # my %params = @_; + # manually create a Kreditorenbuchung from scratch, ap + acc_trans bookings, as no helper exists yet, like $invoice->post. + # arap-Booking must come last in the acc_trans order + $purchase_invoice_counter++; + + my $purchase_invoice = SL::DB::PurchaseInvoice->new( + vendor_id => $vendor->id, + invnumber => 'newap ' . $purchase_invoice_counter , + currency_id => $currency_id, + employee_id => $employee->id, + gldate => DateTime->today_local->to_kivitendo, + taxzone_id => $taxzone->id, + transdate => DateTime->today_local->to_kivitendo, + invoice => 0, + type => 'invoice', + taxincluded => 0, + amount => '226', + netamount => '200', + paid => '0', + # %params, + )->save; + + my $today = DateTime->today_local->to_kivitendo; + my $expense_chart = SL::DB::Manager::Chart->find_by(accno => '3400'); + my $expense_chart_booking= SL::DB::AccTransaction->new( + trans_id => $purchase_invoice->id, + chart_id => $expense_chart->id, + chart_link => $expense_chart->link, + amount => '-100', + transdate => $today, + source => '', + taxkey => 9, + tax_id => SL::DB::Manager::Tax->find_by(taxkey => 9)->id); + $expense_chart_booking->save; + + my $tax_chart = SL::DB::Manager::Chart->find_by(accno => '1576'); + my $tax_chart_booking= SL::DB::AccTransaction->new( + trans_id => $purchase_invoice->id, + chart_id => $tax_chart->id, + chart_link => $tax_chart->link, + amount => '-19', + transdate => $today, + source => '', + taxkey => 0, + tax_id => SL::DB::Manager::Tax->find_by(taxkey => 9)->id); + $tax_chart_booking->save; + $expense_chart = SL::DB::Manager::Chart->find_by(accno => '3300'); + $expense_chart_booking= SL::DB::AccTransaction->new( + trans_id => $purchase_invoice->id, + chart_id => $expense_chart->id, + chart_link => $expense_chart->link, + amount => '-100', + transdate => $today, + source => '', + taxkey => 8, + tax_id => SL::DB::Manager::Tax->find_by(taxkey => 8)->id); + $expense_chart_booking->save; + + + $tax_chart = SL::DB::Manager::Chart->find_by(accno => '1571'); + $tax_chart_booking= SL::DB::AccTransaction->new( + trans_id => $purchase_invoice->id, + chart_id => $tax_chart->id, + chart_link => $tax_chart->link, + amount => '-7', + transdate => $today, + source => '', + taxkey => 0, + tax_id => SL::DB::Manager::Tax->find_by(taxkey => 8)->id); + $tax_chart_booking->save; + my $arap_chart = SL::DB::Manager::Chart->find_by(accno => '1600'); + my $arap_booking= SL::DB::AccTransaction->new(trans_id => $purchase_invoice->id, + chart_id => $arap_chart->id, + chart_link => $arap_chart->link, + amount => '226', + transdate => $today, + source => '', + taxkey => 0, + tax_id => SL::DB::Manager::Tax->find_by(taxkey => 0)->id); + $arap_booking->save; + + return $purchase_invoice; +} + +sub new_item { + my (%params) = @_; + + my $part = delete($params{part}) || $parts[0]; + + return SL::DB::InvoiceItem->new( + parts_id => $part->id, + lastcost => $part->lastcost, + sellprice => $part->sellprice, + description => $part->description, + unit => $part->unit, + %params, + ); +} + +sub number_of_payments { + my $transactions = shift; + + my $number_of_payments; + my $paid_amount; + foreach my $transaction ( @$transactions ) { + if ( $transaction->chart_link =~ /(AR_paid|AP_paid)/ ) { + $paid_amount += $transaction->amount ; + $number_of_payments++; + }; + }; + return ($number_of_payments, $paid_amount); +}; + +sub total_amount { + my $transactions = shift; + + my $total = sum map { $_->amount } @$transactions; + + return $::form->round_amount($total, 5); + +}; + + +# test 1 +sub test_default_invoice_one_item_19_without_skonto() { + reset_state() if $ALWAYS_RESET; + + my $item = new_item(qty => 2.5); + my $invoice = new_invoice( + taxincluded => 0, + invoiceitems => [ $item ], + payment_id => $payment_terms->id, + ); + $invoice->post; + + my $purchase_invoice = new_purchase_invoice(); + + + # default values + my %params = ( chart_id => $bank_account->chart_id, + transdate => DateTime->today_local->to_kivitendo + ); + + $params{amount} = '6.96'; + $params{payment_type} = 'without_skonto'; + + $invoice->pay_invoice( %params ); + + my ($number_of_payments, $paid_amount) = number_of_payments($invoice->transactions); + my $total = total_amount($invoice->transactions); + + my $title = 'default invoice, one item, 19% tax, without_skonto'; + + is($invoice->netamount, 5.85, "${title}: netamount"); + is($invoice->amount, 6.96, "${title}: amount"); + is($paid_amount, -6.96, "${title}: paid amount"); + is($number_of_payments, 1, "${title}: 1 AR_paid booking"); + is($invoice->paid, 6.96, "${title}: paid"); + is($total, 0, "${title}: even balance"); + +} + +sub test_default_invoice_one_item_19_without_skonto_overpaid() { + reset_state() if $ALWAYS_RESET; + + my $item = new_item(qty => 2.5); + my $invoice = new_invoice( + taxincluded => 0, + invoiceitems => [ $item ], + payment_id => $payment_terms->id, + ); + $invoice->post; + + my $purchase_invoice = new_purchase_invoice(); + + + # default values + my %params = ( chart_id => $bank_account->chart_id, + transdate => DateTime->today_local->to_kivitendo + ); + + $params{amount} = '16.96'; + $params{payment_type} = 'without_skonto'; + $invoice->pay_invoice( %params ); + + $params{amount} = '-10.00'; + $invoice->pay_invoice( %params ); + + my ($number_of_payments, $paid_amount) = number_of_payments($invoice->transactions); + my $total = total_amount($invoice->transactions); + + my $title = 'default invoice, one item, 19% tax, without_skonto'; + + is($invoice->netamount, 5.85, "${title}: netamount"); + is($invoice->amount, 6.96, "${title}: amount"); + is($paid_amount, -6.96, "${title}: paid amount"); + is($number_of_payments, 2, "${title}: 1 AR_paid booking"); + is($invoice->paid, 6.96, "${title}: paid"); + is($total, 0, "${title}: even balance"); + +} + + +# test 2 +sub test_default_invoice_two_items_19_7_tax_with_skonto() { + reset_state() if $ALWAYS_RESET; + + my $item1 = new_item(qty => 2.5); + my $item2 = new_item(qty => 1.2, part => $parts[1]); + my $invoice = new_invoice( + taxincluded => 0, + invoiceitems => [ $item1, $item2 ], + payment_id => $payment_terms->id, + ); + $invoice->post; + + # default values + my %params = ( chart_id => $bank_account->chart_id, + transdate => DateTime->today_local->to_kivitendo + ); + + $params{payment_type} = 'with_skonto_pt'; + $params{amount} = $invoice->amount_less_skonto; + + $invoice->pay_invoice( %params ); + + my ($number_of_payments, $paid_amount) = number_of_payments($invoice->transactions); + my $total = total_amount($invoice->transactions); + + my $title = 'default invoice, two items, 19/7% tax with_skonto_pt'; + + is($invoice->netamount, 5.85 + 11.66, "${title}: netamount"); + is($invoice->amount, 6.96 + 12.48, "${title}: amount"); + is($paid_amount, -19.44, "${title}: paid amount"); + is($invoice->paid, 19.44, "${title}: paid"); + is($number_of_payments, 3, "${title}: 3 AR_paid bookings"); + is($total, 0, "${title}: even balance"); +} + +sub test_default_invoice_two_items_19_7_tax_with_skonto_tax_included() { + reset_state() if $ALWAYS_RESET; + + my $item1 = new_item(qty => 2.5); + my $item2 = new_item(qty => 1.2, part => $parts[1]); + my $invoice = new_invoice( + taxincluded => 1, + invoiceitems => [ $item1, $item2 ], + payment_id => $payment_terms->id, + ); + $invoice->post; + + # default values + my %params = ( chart_id => $bank_account->chart_id, + transdate => DateTime->today_local->to_kivitendo + ); + + $params{payment_type} = 'with_skonto_pt'; + $params{amount} = $invoice->amount_less_skonto; + + $invoice->pay_invoice( %params ); + + my ($number_of_payments, $paid_amount) = number_of_payments($invoice->transactions); + my $total = total_amount($invoice->transactions); + + my $title = 'default invoice, two items, 19/7% tax with_skonto_pt'; + + is($invoice->netamount, 15.82, "${title}: netamount"); + is($invoice->amount, 17.51, "${title}: amount"); + is($paid_amount, -17.51, "${title}: paid amount"); + is($invoice->paid, 17.51, "${title}: paid"); + is($number_of_payments, 3, "${title}: 3 AR_paid bookings"); + is($total, 0, "${title}: even balance"); +} + +# test 3 : two items, without skonto +sub test_default_invoice_two_items_19_7_without_skonto() { + reset_state() if $ALWAYS_RESET; + + my $item1 = new_item(qty => 2.5); + my $item2 = new_item(qty => 1.2, part => $parts[1]); + my $invoice = new_invoice( + taxincluded => 0, + invoiceitems => [ $item1, $item2 ], + payment_id => $payment_terms->id, + ); + $invoice->post; + + # default values + my %params = ( chart_id => $bank_account->chart_id, + transdate => DateTime->today_local->to_kivitendo + ); + + $params{amount} = '19.44'; # pass full amount + $params{payment_type} = 'without_skonto'; + + $invoice->pay_invoice( %params ); + + my ($number_of_payments, $paid_amount) = number_of_payments($invoice->transactions); + my $total = total_amount($invoice->transactions); + + my $title = 'default invoice, two items, 19/7% tax without skonto'; + + is($invoice->netamount, 5.85 + 11.66, "${title}: netamount"); + is($invoice->amount, 6.96 + 12.48, "${title}: amount"); + is($paid_amount, -19.44, "${title}: paid amount"); + is($invoice->paid, 19.44, "${title}: paid"); + is($number_of_payments, 1, "${title}: 1 AR_paid bookings"); + is($total, 0, "${title}: even balance"); +} + +# test 4 +sub test_default_invoice_two_items_19_7_without_skonto_incomplete_payment() { + reset_state() if $ALWAYS_RESET; + + my $item1 = new_item(qty => 2.5); + my $item2 = new_item(qty => 1.2, part => $parts[1]); + my $invoice = new_invoice( + taxincluded => 0, + invoiceitems => [ $item1, $item2 ], + payment_id => $payment_terms->id, + ); + $invoice->post; + + $invoice->pay_invoice( amount => '9.44', + payment_type => 'without_skonto', + chart_id => $bank_account->chart_id, + transdate => DateTime->today_local->to_kivitendo, + ); + + my ($number_of_payments, $paid_amount) = number_of_payments($invoice->transactions); + my $total = total_amount($invoice->transactions); + + my $title = 'default invoice, two items, 19/7% tax without skonto incomplete payment'; + + is($invoice->netamount, 5.85 + 11.66, "${title}: netamount"); + is($invoice->amount, 6.96 + 12.48, "${title}: amount"); + is($paid_amount, -9.44, "${title}: paid amount"); + is($invoice->paid, 9.44, "${title}: paid"); + is($number_of_payments, 1, "${title}: 1 AR_paid bookings"); + is($total, 0, "${title}: even balance"); +} + +# test 5 +sub test_default_invoice_two_items_19_7_tax_without_skonto_multiple_payments() { + reset_state() if $ALWAYS_RESET; + + my $item1 = new_item(qty => 2.5); + my $item2 = new_item(qty => 1.2, part => $parts[1]); + my $invoice = new_invoice( + taxincluded => 0, + invoiceitems => [ $item1, $item2 ], + payment_id => $payment_terms->id, + ); + $invoice->post; + + $invoice->pay_invoice( amount => '9.44', + payment_type => 'without_skonto', + chart_id => $bank_account->chart_id, + transdate => DateTime->today_local->to_kivitendo + ); + $invoice->pay_invoice( amount => '10.00', + chart_id => $bank_account->chart_id, + transdate => DateTime->today_local->to_kivitendo + ); + + my ($number_of_payments, $paid_amount) = number_of_payments($invoice->transactions); + my $total = total_amount($invoice->transactions); + + my $title = 'default invoice, two items, 19/7% tax not included'; + + is($invoice->netamount, 5.85 + 11.66, "${title}: netamount"); + is($invoice->amount, 6.96 + 12.48, "${title}: amount"); + is($paid_amount, -19.44, "${title}: paid amount"); + is($invoice->paid, 19.44, "${title}: paid"); + is($number_of_payments, 2, "${title}: 2 AR_paid bookings"); + is($total, 0, "${title}: even balance"); + +} + +# test 6 +sub test_default_invoice_two_items_19_7_tax_without_skonto_multiple_payments_final_difference_as_skonto() { + reset_state() if $ALWAYS_RESET; + + my $item1 = new_item(qty => 2.5); + my $item2 = new_item(qty => 1.2, part => $parts[1]); + my $invoice = new_invoice( + taxincluded => 0, + invoiceitems => [ $item1, $item2 ], + payment_id => $payment_terms->id, + ); + $invoice->post; + + $invoice->pay_invoice( amount => '9.44', + payment_type => 'without_skonto', + chart_id => $bank_account->chart_id, + transdate => DateTime->today_local->to_kivitendo + ); + $invoice->pay_invoice( amount => '8.73', + payment_type => 'without_skonto', + chart_id => $bank_account->chart_id, + transdate => DateTime->today_local->to_kivitendo + ); + $invoice->pay_invoice( amount => $invoice->open_amount, + payment_type => 'difference_as_skonto', + chart_id => $bank_account->chart_id, + transdate => DateTime->today_local->to_kivitendo + ); + + my ($number_of_payments, $paid_amount) = number_of_payments($invoice->transactions); + my $total = total_amount($invoice->transactions); + + my $title = 'default invoice, two items, 19/7% tax not included'; + + is($invoice->netamount, 5.85 + 11.66, "${title}: netamount"); + is($invoice->amount, 6.96 + 12.48, "${title}: amount"); + is($paid_amount, -19.44, "${title}: paid amount"); + is($invoice->paid, 19.44, "${title}: paid"); + is($number_of_payments, 4, "${title}: 4 AR_paid bookings"); + is($total, 0, "${title}: even balance"); + +} + +sub test_default_invoice_two_items_19_7_tax_without_skonto_multiple_payments_final_difference_as_skonto_1cent() { + reset_state() if $ALWAYS_RESET; + + # if there is only one cent left there can only be one skonto booking, the + # error handling should choose the highest amount, which is the 7% account + # (11.66) rather than the 19% account (5.85). The actual tax amount is + # higher for the 19% case, though (1.11 compared to 0.82) + + my $item1 = new_item(qty => 2.5); + my $item2 = new_item(qty => 1.2, part => $parts[1]); + my $invoice = new_invoice( + taxincluded => 0, + invoiceitems => [ $item1, $item2 ], + payment_id => $payment_terms->id, + ); + $invoice->post; + + $invoice->pay_invoice( amount => '19.42', + payment_type => 'without_skonto', + chart_id => $bank_account->chart_id, + transdate => DateTime->today_local->to_kivitendo + ); + $invoice->pay_invoice( amount => $invoice->open_amount, + payment_type => 'difference_as_skonto', + chart_id => $bank_account->chart_id, + transdate => DateTime->today_local->to_kivitendo + ); + + my ($number_of_payments, $paid_amount) = number_of_payments($invoice->transactions); + my $total = total_amount($invoice->transactions); + + my $title = 'default invoice, two items, 19/7% tax not included'; + + is($invoice->netamount, 5.85 + 11.66, "${title}: netamount"); + is($invoice->amount, 6.96 + 12.48, "${title}: amount"); + is($paid_amount, -19.44, "${title}: paid amount"); + is($invoice->paid, 19.44, "${title}: paid"); + is($number_of_payments, 3, "${title}: 2 AR_paid bookings"); + is($total, 0, "${title}: even balance"); + +} + +sub test_default_invoice_two_items_19_7_tax_without_skonto_multiple_payments_final_difference_as_skonto_2cent() { + reset_state() if $ALWAYS_RESET; + + # if there are two cents left there will be two skonto bookings, 1 cent each + my $item1 = new_item(qty => 2.5); + my $item2 = new_item(qty => 1.2, part => $parts[1]); + my $invoice = new_invoice( + taxincluded => 0, + invoiceitems => [ $item1, $item2 ], + payment_id => $payment_terms->id, + ); + $invoice->post; + + $invoice->pay_invoice( amount => '19.42', + payment_type => 'without_skonto', + chart_id => $bank_account->chart_id, + transdate => DateTime->today_local->to_kivitendo + ); + $invoice->pay_invoice( amount => $invoice->open_amount, + payment_type => 'difference_as_skonto', + chart_id => $bank_account->chart_id, + transdate => DateTime->today_local->to_kivitendo + ); + + my ($number_of_payments, $paid_amount) = number_of_payments($invoice->transactions); + my $total = total_amount($invoice->transactions); + + my $title = 'default invoice, two items, 19/7% tax not included'; + + is($invoice->netamount, 5.85 + 11.66, "${title}: netamount"); + is($invoice->amount, 6.96 + 12.48, "${title}: amount"); + is($paid_amount, -19.44, "${title}: paid amount"); + is($invoice->paid, 19.44, "${title}: paid"); + is($number_of_payments, 3, "${title}: 3 AR_paid bookings"); + is($total, 0, "${title}: even balance"); + +} + +sub test_default_invoice_one_item_19_multiple_payment_final_difference_as_skonto() { + reset_state() if $ALWAYS_RESET; + + my $item = new_item(qty => 2.5); + my $invoice = new_invoice( + taxincluded => 0, + invoiceitems => [ $item ], + payment_id => $payment_terms->id, + ); + $invoice->post; + + # default values + my %params = ( chart_id => $bank_account->chart_id, + transdate => DateTime->today_local->to_kivitendo + ); + + $params{amount} = '2.32'; + $params{payment_type} = 'without_skonto'; + $invoice->pay_invoice( %params ); + + $params{amount} = '3.81'; + $params{payment_type} = 'without_skonto'; + $invoice->pay_invoice( %params ); + + $params{amount} = $invoice->open_amount; # set amount, otherwise previous 3.81 is used + $params{payment_type} = 'difference_as_skonto'; + $invoice->pay_invoice( %params ); + + my ($number_of_payments, $paid_amount) = number_of_payments($invoice->transactions); + my $total = total_amount($invoice->transactions); + + my $title = 'default invoice, one item, 19% tax, without_skonto'; + + is($invoice->netamount, 5.85, "${title}: netamount"); + is($invoice->amount, 6.96, "${title}: amount"); + is($paid_amount, -6.96, "${title}: paid amount"); + is($number_of_payments, 3, "${title}: 3 AR_paid booking"); + is($invoice->paid, 6.96, "${title}: paid"); + is($total, 0, "${title}: even balance"); + +} + +sub test_default_invoice_one_item_19_multiple_payment_final_difference_as_skonto_1cent() { + reset_state() if $ALWAYS_RESET; + + my $item = new_item(qty => 2.5); + my $invoice = new_invoice( + taxincluded => 0, + invoiceitems => [ $item ], + payment_id => $payment_terms->id, + ); + $invoice->post; + + # default values + my %params = ( chart_id => $bank_account->chart_id, + transdate => DateTime->today_local->to_kivitendo + ); + + $params{amount} = '6.95'; + $params{payment_type} = 'without_skonto'; + $invoice->pay_invoice( %params ); + + $params{amount} = $invoice->open_amount; # set amount, otherwise previous value 6.95 is used + $params{payment_type} = 'difference_as_skonto'; + $invoice->pay_invoice( %params ); + + my ($number_of_payments, $paid_amount) = number_of_payments($invoice->transactions); + my $total = total_amount($invoice->transactions); + + my $title = 'default invoice, one item, 19% tax, without_skonto'; + + is($invoice->netamount, 5.85, "${title}: netamount"); + is($invoice->amount, 6.96, "${title}: amount"); + is($paid_amount, -6.96, "${title}: paid amount"); + is($number_of_payments, 2, "${title}: 3 AR_paid booking"); + is($invoice->paid, 6.96, "${title}: paid"); + is($total, 0, "${title}: even balance"); + +} + +# test 3 : two items, without skonto +sub test_default_purchase_invoice_two_charts_19_7_without_skonto() { + reset_state() if $ALWAYS_RESET; + + my $purchase_invoice = new_purchase_invoice(); + + my %params = ( chart_id => $bank_account->chart_id, + transdate => DateTime->today_local->to_kivitendo + ); + + $params{amount} = '226'; # pass full amount + $params{payment_type} = 'without_skonto'; + + $purchase_invoice->pay_invoice( %params ); + + my ($number_of_payments, $paid_amount) = number_of_payments($purchase_invoice->transactions); + my $total = total_amount($purchase_invoice->transactions); + + my $title = 'default invoice, two items, 19/7% tax without skonto'; + + is($paid_amount, 226, "${title}: paid amount"); + is($number_of_payments, 1, "${title}: 1 AP_paid bookings"); + is($total, 0, "${title}: even balance"); + +} + +sub test_default_purchase_invoice_two_charts_19_7_with_skonto() { + reset_state() if $ALWAYS_RESET; + + my $purchase_invoice = new_purchase_invoice(); + + my %params = ( chart_id => $bank_account->chart_id, + transdate => DateTime->today_local->to_kivitendo + ); + + # $params{amount} = '226'; # pass full amount + $params{payment_type} = 'with_skonto_pt'; + + $purchase_invoice->pay_invoice( %params ); + + my ($number_of_payments, $paid_amount) = number_of_payments($purchase_invoice->transactions); + my $total = total_amount($purchase_invoice->transactions); + + my $title = 'default invoice, two items, 19/7% tax without skonto'; + + is($paid_amount, 226, "${title}: paid amount"); + is($number_of_payments, 3, "${title}: 1 AP_paid bookings"); + is($total, 0, "${title}: even balance"); + +} + +sub test_default_purchase_invoice_two_charts_19_7_tax_partial_unrounded_payment_without_skonto() { + # check whether unrounded amounts passed via $params{amount} are rounded for without_skonto case + reset_state() if $ALWAYS_RESET; + my $purchase_invoice = new_purchase_invoice(); + $purchase_invoice->pay_invoice( + amount => ( $purchase_invoice->amount / 3 * 2), + payment_type => 'without_skonto', + chart_id => $bank_account->chart_id, + transdate => DateTime->today_local->to_kivitendo + ); + my ($number_of_payments, $paid_amount) = number_of_payments($purchase_invoice->transactions); + my $total = total_amount($purchase_invoice->transactions); + + my $title = 'default purchase_invoice, two charts, 19/7% tax multiple payments with final difference as skonto'; + + is($paid_amount, 150.67, "${title}: paid amount"); + is($number_of_payments, 1, "${title}: 1 AP_paid bookings"); + is($total, 0, "${title}: even balance"); +}; + + +sub test_default_purchase_invoice_two_charts_19_7_tax_without_skonto_multiple_payments_final_difference_as_skonto() { + reset_state() if $ALWAYS_RESET; + + my $purchase_invoice = new_purchase_invoice(); + + # pay 2/3 and 1/5, leaves 3.83% to be used as Skonto + $purchase_invoice->pay_invoice( + amount => ( $purchase_invoice->amount / 3 * 2), + payment_type => 'without_skonto', + chart_id => $bank_account->chart_id, + transdate => DateTime->today_local->to_kivitendo + ); + $purchase_invoice->pay_invoice( + amount => ( $purchase_invoice->amount / 5 ), + payment_type => 'without_skonto', + chart_id => $bank_account->chart_id, + transdate => DateTime->today_local->to_kivitendo + ); + $purchase_invoice->pay_invoice( + payment_type => 'difference_as_skonto', + chart_id => $bank_account->chart_id, + transdate => DateTime->today_local->to_kivitendo + ); + + my ($number_of_payments, $paid_amount) = number_of_payments($purchase_invoice->transactions); + my $total = total_amount($purchase_invoice->transactions); + + my $title = 'default purchase_invoice, two charts, 19/7% tax multiple payments with final difference as skonto'; + + is($paid_amount, 226, "${title}: paid amount"); + is($number_of_payments, 4, "${title}: 1 AP_paid bookings"); + is($total, 0, "${title}: even balance"); + +} + +# test +sub test_default_invoice_two_items_19_7_tax_with_skonto_50_50() { + reset_state() if $ALWAYS_RESET; + + my $item1 = new_item(qty => 1, part => $parts[2]); + my $item2 = new_item(qty => 1, part => $parts[3]); + my $invoice = new_invoice( + taxincluded => 0, + invoiceitems => [ $item1, $item2 ], + payment_id => $payment_terms->id, + ); + $invoice->post; + + # default values + my %params = ( chart_id => $bank_account->chart_id, + transdate => DateTime->today_local->to_kivitendo + ); + + $params{amount} = $invoice->amount_less_skonto; + $params{payment_type} = 'with_skonto_pt'; + + $invoice->pay_invoice( %params ); + + my ($number_of_payments, $paid_amount) = number_of_payments($invoice->transactions); + my $total = total_amount($invoice->transactions); + + my $title = 'default invoice, two items, 19/7% tax with_skonto_pt 50/50'; + + is($invoice->netamount, 100, "${title}: netamount"); + is($invoice->amount, 113, "${title}: amount"); + is($paid_amount, -113, "${title}: paid amount"); + is($invoice->paid, 113, "${title}: paid"); + is($number_of_payments, 3, "${title}: 3 AR_paid bookings"); + is($total, 0, "${title}: even balance"); +} + +# test +sub test_default_invoice_four_items_19_7_tax_with_skonto_4x_25() { + reset_state() if $ALWAYS_RESET; + + my $item1 = new_item(qty => 0.5, part => $parts[2]); + my $item2 = new_item(qty => 0.5, part => $parts[3]); + my $item3 = new_item(qty => 0.5, part => $parts[2]); + my $item4 = new_item(qty => 0.5, part => $parts[3]); + my $invoice = new_invoice( + taxincluded => 0, + invoiceitems => [ $item1, $item2, $item3, $item4 ], + payment_id => $payment_terms->id, + ); + $invoice->post; + + # default values + my %params = ( chart_id => $bank_account->chart_id, + transdate => DateTime->today_local->to_kivitendo + ); + + $params{amount} = $invoice->amount_less_skonto; + $params{payment_type} = 'with_skonto_pt'; + + $invoice->pay_invoice( %params ); + + my ($number_of_payments, $paid_amount) = number_of_payments($invoice->transactions); + my $total = total_amount($invoice->transactions); + + my $title = 'default invoice, four items, 19/7% tax with_skonto_pt 4x25'; + + is($invoice->netamount , 100 , "${title}: netamount"); + is($invoice->amount , 113 , "${title}: amount"); + is($paid_amount , -113 , "${title}: paid amount"); + is($invoice->paid , 113 , "${title}: paid"); + is($number_of_payments , 3 , "${title}: 3 AR_paid bookings"); + is($total , 0 , "${title}: even balance"); +} + +sub test_default_invoice_four_items_19_7_tax_with_skonto_4x_25_tax_included() { + reset_state() if $ALWAYS_RESET; + + my $item1 = new_item(qty => 0.5, part => $parts[2]); + my $item2 = new_item(qty => 0.5, part => $parts[3]); + my $item3 = new_item(qty => 0.5, part => $parts[2]); + my $item4 = new_item(qty => 0.5, part => $parts[3]); + my $invoice = new_invoice( + taxincluded => 1, + invoiceitems => [ $item1, $item2, $item3, $item4 ], + payment_id => $payment_terms->id, + ); + $invoice->post; + + # default values + my %params = ( chart_id => $bank_account->chart_id, + transdate => DateTime->today_local->to_kivitendo + ); + + $params{amount} = $invoice->amount_less_skonto; + $params{payment_type} = 'with_skonto_pt'; + + $invoice->pay_invoice( %params ); + + my ($number_of_payments, $paid_amount) = number_of_payments($invoice->transactions); + my $total = total_amount($invoice->transactions); + + my $title = 'default invoice, four items, 19/7% tax with_skonto_pt 4x25'; + + is($invoice->netamount, 88.75, "${title}: netamount"); + is($invoice->amount, 100, "${title}: amount"); + is($paid_amount, -100, "${title}: paid amount"); + is($invoice->paid, 100, "${title}: paid"); + is($number_of_payments, 3, "${title}: 3 AR_paid bookings"); +# currently this test fails because the code writing the invoice is buggy, the calculation of skonto is correct + is($total, 0, "${title}: even balance: this will fail due to rounding error in invoice post, not the skonto"); +} + +sub test_default_invoice_four_items_19_7_tax_with_skonto_4x_25_multiple() { + reset_state() if $ALWAYS_RESET; + + my $item1 = new_item(qty => 0.5, part => $parts[2]); + my $item2 = new_item(qty => 0.5, part => $parts[3]); + my $item3 = new_item(qty => 0.5, part => $parts[2]); + my $item4 = new_item(qty => 0.5, part => $parts[3]); + my $invoice = new_invoice( + taxincluded => 0, + invoiceitems => [ $item1, $item2, $item3, $item4 ], + payment_id => $payment_terms->id, + ); + $invoice->post; + + $invoice->pay_invoice( amount => '90', + payment_type => 'without_skonto', + chart_id => $bank_account->chart_id, + transdate => DateTime->today_local->to_kivitendo + ); + $invoice->pay_invoice( payment_type => 'difference_as_skonto', + chart_id => $bank_account->chart_id, + transdate => DateTime->today_local->to_kivitendo + ); + + my ($number_of_payments, $paid_amount) = number_of_payments($invoice->transactions); + my $total = total_amount($invoice->transactions); + + my $title = 'default invoice, four items, 19/7% tax with_skonto_pt 4x25'; + + is($invoice->netamount, 100, "${title}: netamount"); + is($invoice->amount, 113, "${title}: amount"); + is($paid_amount, -113, "${title}: paid amount"); + is($invoice->paid, 113, "${title}: paid"); + is($number_of_payments, 3, "${title}: 3 AR_paid bookings"); + is($total, 0, "${title}: even balance: this will fail due to rounding error in invoice post, not the skonto"); +} + +Support::TestSetup::login(); + # die; + +# test cases: without_skonto + test_default_invoice_one_item_19_without_skonto(); + test_default_invoice_two_items_19_7_tax_with_skonto(); + test_default_invoice_two_items_19_7_without_skonto(); + test_default_invoice_two_items_19_7_without_skonto_incomplete_payment(); + test_default_invoice_two_items_19_7_tax_without_skonto_multiple_payments(); + test_default_purchase_invoice_two_charts_19_7_without_skonto(); + test_default_purchase_invoice_two_charts_19_7_tax_partial_unrounded_payment_without_skonto(); + test_default_invoice_one_item_19_without_skonto_overpaid(); + +# test cases: difference_as_skonto + test_default_invoice_two_items_19_7_tax_without_skonto_multiple_payments_final_difference_as_skonto(); + test_default_invoice_two_items_19_7_tax_without_skonto_multiple_payments_final_difference_as_skonto_1cent(); + test_default_invoice_two_items_19_7_tax_without_skonto_multiple_payments_final_difference_as_skonto_2cent(); + test_default_invoice_one_item_19_multiple_payment_final_difference_as_skonto(); + test_default_invoice_one_item_19_multiple_payment_final_difference_as_skonto_1cent(); + test_default_purchase_invoice_two_charts_19_7_tax_without_skonto_multiple_payments_final_difference_as_skonto(); + +# test cases: with_skonto_pt + test_default_invoice_two_items_19_7_tax_with_skonto_50_50(); + test_default_invoice_four_items_19_7_tax_with_skonto_4x_25(); + test_default_invoice_four_items_19_7_tax_with_skonto_4x_25_multiple(); + test_default_purchase_invoice_two_charts_19_7_with_skonto(); + test_default_invoice_four_items_19_7_tax_with_skonto_4x_25_tax_included(); + test_default_invoice_two_items_19_7_tax_with_skonto_tax_included(); + +# remove all created data at end of test +clear_up(); + +done_testing(); + +1;