OrderController: reorder mit Sort::Naturally damit nach Preis ordentlich
[kivitendo-erp.git] / t / ar / ar.t
1 use strict;
2 use Test::More tests => 6;
3
4 use lib 't';
5 use Support::TestSetup;
6 use Carp;
7 use Test::Exception;
8 use SL::DB::Customer;
9 use SL::DB::Employee;
10 use SL::DB::Invoice;
11 use SL::DATEV qw(:CONSTANTS);
12 use SL::Dev::CustomerVendor qw(new_customer);
13 use Data::Dumper;
14
15 my ($customer, $employee, $ar_tax_19, $ar_tax_7, $ar_tax_0);
16 my ($ar_chart, $bank, $ar_amount_chart);
17 my $config = {};
18 $config->{numberformat} = '1.000,00';
19
20 sub reset_state {
21   my %params = @_;
22
23   $params{$_} ||= {} for qw(buchungsgruppe customer);
24
25   clear_up();
26
27   $employee        = SL::DB::Manager::Employee->current                       || croak "No employee";
28   $ar_tax_19       = SL::DB::Manager::Tax->find_by(taxkey => 3, rate => 0.19) || croak "No 19% tax";
29   $ar_tax_7        = SL::DB::Manager::Tax->find_by(taxkey => 2, rate => 0.07) || croak "No 7% tax";
30   $ar_tax_0        = SL::DB::Manager::Tax->find_by(taxkey => 0, rate => 0.00) || croak "No 0% tax";
31
32   $customer = new_customer()->save; # new customer with default name "Testkunde"
33
34   $ar_chart        = SL::DB::Manager::Chart->find_by(accno => '1400') || croak "Can't find Forderungen";
35   $bank            = SL::DB::Manager::Chart->find_by(accno => '1200') || croak "Can't find Bank";
36   $ar_amount_chart = SL::DB::Manager::Chart->find_by(accno => '8590') || croak "Can't find verrechn., eigentlich Anzahlungen";
37
38 };
39
40 Support::TestSetup::login();
41
42 reset_state();
43
44 # check ar without tax
45 my $invoice = _ar(customer     => $customer,
46                   amount       => 100,
47                   with_payment => 1,
48                   notes        => 'ar without tax',
49                  );
50
51 # for testing load a fresh instance of the invoice from the database
52 my $inv = SL::DB::Invoice->new(id => $invoice->id)->load;
53 if ( $inv ) {
54   my $number_of_acc_trans = scalar @{ $inv->transactions };
55   is($::form->round_amount($inv->amount) , 100                           , "invoice_amount = 100");
56   is($number_of_acc_trans                , 5                             , "number of transactions");
57   is($inv->datepaid->to_kivitendo        , DateTime->today->to_kivitendo , "datepaid");
58   is($inv->amount - $inv->paid           , 0                             , "paid = amount ");
59 } else {
60   ok 0, "couldn't find first invoice";
61 }
62
63 # check ar with tax
64 my $invoice2 = _ar_with_tax(customer     => $customer,
65                             amount       => 200,
66                             with_payment => 1,
67                             notes        => 'ar with taxincluded',
68                            );
69 my $inv_with_tax = SL::DB::Invoice->new(id => $invoice2->id)->load;
70 if ( $inv_with_tax ) {
71   is(scalar @{ $inv_with_tax->transactions }, 7,  "number of transactions for inv_with_tax");
72 } else {
73   ok 0, "couldn't find second invoice";
74 }
75
76 # general checks
77 is(SL::DB::Manager::Invoice->get_all_count(), 2,  "total number of invoices created is 2");
78
79 done_testing;
80 clear_up();
81
82 1;
83
84 sub clear_up {
85   SL::DB::Manager::AccTransaction->delete_all(all => 1);
86   SL::DB::Manager::Invoice->delete_all(       all => 1);
87   SL::DB::Manager::Customer->delete_all(      all => 1);
88 };
89
90 sub _ar {
91   my %params = @_;
92
93   my $amount       = $params{amount}       || croak "ar needs param amount";
94   my $customer     = $params{customer}     || croak "ar needs param customer";
95   my $date         = $params{date}         || DateTime->today;
96   my $with_payment = $params{with_payment} || 0;
97
98   # SL::DB::Invoice has a _before_save_set_invnumber hook, so we don't need to pass invnumber
99   my $invoice = SL::DB::Invoice->new(
100       invoice          => 0,
101       amount           => $amount,
102       netamount        => $amount,
103       transdate        => $date,
104       taxincluded      => 'f',
105       customer_id      => $customer->id,
106       taxzone_id       => $customer->taxzone_id,
107       currency_id      => $customer->currency_id,
108       globalproject_id => $params{project},
109       notes            => $params{notes},
110       transactions     => [],
111   );
112
113   my $db = $invoice->db;
114
115   $db->with_transaction( sub {
116
117     $invoice->add_ar_amount_row(
118       amount     => $amount / 2,
119       chart      => $ar_amount_chart,
120       tax_id     => $ar_tax_0->id,
121     );
122     $invoice->add_ar_amount_row(
123       amount     => $amount / 2,
124       chart      => $ar_amount_chart,
125       tax_id     => $ar_tax_0->id,
126     );
127
128     $invoice->create_ar_row( chart => $ar_chart );
129
130     _save_and_pay_and_check(invoice     => $invoice,
131                             bank        => $bank,
132                             pay         => $with_payment,
133                             datev_check => 1,
134                            );
135
136     1;
137
138   }) || die "something went wrong: " . $db->error;
139   return $invoice;
140 };
141
142 sub _ar_with_tax {
143   my %params = @_;
144
145   my $amount       = $params{amount}       || croak "ar needs param amount";
146   my $customer     = $params{customer}     || croak "ar needs param customer";
147   my $date         = $params{date}         || DateTime->today;
148   my $with_payment = $params{with_payment} || 0;
149
150   my $invoice = SL::DB::Invoice->new(
151     invoice          => 0,
152     amount           => $amount,
153     netamount        => $amount,
154     transdate        => $date,
155     taxincluded      => 'f',
156     customer_id      => $customer->id,
157     taxzone_id       => $customer->taxzone_id,
158     currency_id      => $customer->currency_id,
159     globalproject_id => $params{project},
160     notes            => $params{notes},
161     transactions     => [],
162   );
163
164   my $db = $invoice->db;
165
166   $db->with_transaction( sub {
167
168     # TODO: check for currency and exchange rate
169
170     $invoice->add_ar_amount_row(
171       amount     => $amount / 2,
172       chart      => $ar_amount_chart,
173       tax_id     => $ar_tax_19->id,
174     );
175     $invoice->add_ar_amount_row(
176       amount     => $amount / 2,
177       chart      => $ar_amount_chart,
178       tax_id     => $ar_tax_19->id,
179     );
180
181     $invoice->create_ar_row( chart => $ar_chart );
182     _save_and_pay_and_check(invoice     => $invoice,
183                             bank        => $bank,
184                             pay         => $with_payment,
185                             datev_check => 1,
186                            );
187
188     1;
189   }) || die "something went wrong: " . $db->error;
190   return $invoice;
191 };
192
193 sub _save_and_pay_and_check {
194   my %params = @_;
195   my $invoice     = $params{invoice} // croak "invoice missing";
196   my $datev_check = $params{datev_check} // 1; # do datev check by default
197   croak "no bank" unless ref $params{bank} eq 'SL::DB::Chart';
198
199   # make sure invoice is saved before making payments
200   my $return = $invoice->save;
201
202   $invoice->pay_invoice(chart_id     => $params{bank}->id,
203                         amount       => $invoice->amount,
204                         transdate    => $invoice->transdate->to_kivitendo,
205                         payment_type => 'without_skonto',  # default if not specified
206                        ) if $params{pay};
207
208   if ($datev_check) {
209     my $datev = SL::DATEV->new(
210       dbh        => $invoice->db->dbh,
211       trans_id   => $invoice->id,
212     );
213
214     $datev->generate_datev_data;
215
216     # _save_and_pay_and_check should always be called inside a with_transaction block
217     if ($datev->errors) {
218       $invoice->db->dbh->rollback;
219       die join "\n", $::locale->text('DATEV check returned errors:'), $datev->errors;
220     }
221   };
222 };