4 use Test::More tests => 18;
10 use Support::TestSetup;
12 use SL::DBUtils qw(selectall_hashref_query);
14 use SL::DB::BankAccount;
17 use SL::DB::PurchaseInvoice;
19 use SL::Dev::Record qw(create_ar_transaction create_ap_transaction create_gl_transaction);
21 use SL::Controller::YearEndTransactions;
23 Support::TestSetup::login();
29 # * in the default test client the tax accounts are configured as I/E rather than A/L
30 # * also the default test client has the accounting method "cash" rather than "accrual"
31 # (Ist-versteuerung, rather than Soll-versteuerung)
33 # hardcode for 2019, as this will break in 2020 due to change in tax (19/16 and 7/5) because we check for account sums
34 # can be changed back to current year in 2021
35 my $year = 2019; # DateTime->today->year;
36 my $start_of_year = DateTime->new(year => $year, month => 01, day => 01);
37 my $booking_date = DateTime->new(year => $year, month => 12, day => 22);
39 note('configuring accounts');
40 my $bank_account = SL::DB::BankAccount->new(
41 account_number => '123',
46 chart_id => SL::DB::Manager::Chart->find_by(description => 'Bank')->id,
47 name => SL::DB::Manager::Chart->find_by(description => 'Bank')->description,
50 my $profit_account = SL::DB::Manager::Chart->find_by(accno => '0890') //
53 description => 'Gewinnvortrag vor Verwendung',
58 datevautomatik => 'f',
61 my $loss_account = SL::DB::Manager::Chart->find_by(accno => '0868') //
64 description => 'Verlustvortrag vor Verwendung',
69 datevautomatik => 'f',
72 my $carry_over_chart = SL::DB::Manager::Chart->find_by(accno => 9000);
73 my $income_chart = SL::DB::Manager::Chart->find_by(accno => '8400'); # income 19%, taxkey 3
74 my $bank = SL::DB::Manager::Chart->find_by(description => 'Bank');
75 my $cash = SL::DB::Manager::Chart->find_by(description => 'Kasse');
76 my $privateinlagen = SL::DB::Manager::Chart->find_by(description => 'Privateinlagen');
77 my $betriebsbedarf = SL::DB::Manager::Chart->find_by(description => 'Betriebsbedarf');
79 my $dbh = SL::DB->client->dbh;
80 $dbh->do('UPDATE defaults SET carry_over_account_chart_id = ' . $carry_over_chart->id);
81 $dbh->do('UPDATE defaults SET profit_carried_forward_chart_id = ' . $profit_account->id);
82 $dbh->do('UPDATE defaults SET loss_carried_forward_chart_id = ' . $loss_account->id);
85 note('creating transactions');
86 my $ar_transaction = create_ar_transaction(
88 transdate => $booking_date,
91 chart => $income_chart, # income 19%, taxkey 3
97 $ar_transaction->pay_invoice(
98 chart_id => $bank_account->chart_id,
99 amount => $ar_transaction->amount,
100 transdate => $booking_date->to_kivitendo,
101 payment_type => 'without_skonto',
104 my $ar_transaction2 = create_ar_transaction(
106 transdate => $booking_date,
109 chart => $income_chart, # income 19%, taxkey 3
115 my $ap_transaction = create_ap_transaction(
117 transdate => $booking_date,
120 chart => SL::DB::Manager::Chart->find_by( accno => '3400' ), # Wareneingang 19%, taxkey 9
126 gl_booking(40, $start_of_year, 'foo', 'bar', $bank, $privateinlagen, 1, 0);
128 is(SL::DB::Manager::AccTransaction->get_all_count( ), 13, 'acc_trans transactions created ok');
129 is(SL::DB::Manager::AccTransaction->get_all_count(where => [ ob_transaction => 1 ]), 2, 'acc_trans ob_transactions created ok');
130 is(SL::DB::Manager::AccTransaction->get_all_count(where => [ cb_transaction => 1 ]), 0, 'no cb_transactions created ok');
132 is_deeply( &get_account_balances,
136 'account_type' => 'asset_account',
137 'sum' => '-206.60000'
141 'account_type' => 'asset_account',
142 'sum' => '-166.60000'
146 'account_type' => 'asset_account',
151 'account_type' => 'asset_account',
156 'account_type' => 'profit_loss_account',
161 'account_type' => 'profit_loss_account',
166 'account_type' => 'profit_loss_account',
167 'sum' => '-100.00000'
171 'account_type' => 'profit_loss_account',
175 'account balances before year_end bookings ok',
178 # accno | account_type | sum
179 # -------+---------------------+------------
180 # 1200 | asset_account | -206.60000
181 # 1400 | asset_account | -166.60000
182 # 1600 | asset_account | 119.00000
183 # 1890 | asset_account | 40.00000
184 # 1576 | profit_loss_account | -19.00000
185 # 1776 | profit_loss_account | 53.20000
186 # 3400 | profit_loss_account | -100.00000
187 # 8400 | profit_loss_account | 280.00000
190 note('running year-end transactions');
191 my $start_date = DateTime->new(year => $year, month => 1, day => 1);
192 my $cb_date = DateTime->new(year => $year, month => 12, day => 31);
193 my $ob_date = $cb_date->clone->add(days => 1);
195 SL::Controller::YearEndTransactions::_year_end_bookings( start_date => $start_date,
199 is(SL::DB::Manager::AccTransaction->get_all_count(where => [ cb_transaction => 1 ]), 14, 'acc_trans cb_transactions created ok');
200 is(SL::DB::Manager::AccTransaction->get_all_count(where => [ ob_transaction => 1 ]), 10, 'acc_trans ob_transactions created ok');
201 is(SL::DB::Manager::GLTransaction->get_all_count( where => [ cb_transaction => 1 ]), 5, 'GL cb_transactions created ok');
202 is(SL::DB::Manager::GLTransaction->get_all_count( where => [ ob_transaction => 1 ]), 4, 'GL ob_transactions created ok');
204 my $final_account_balances = [
208 'amount_with_cb' => '0.00000',
210 'cb_amount' => '0.00000',
211 'ob_amount' => undef,
212 'ob_next_year' => '214.20000',
214 'year_end_amount' => undef
218 'amount' => '-166.60000',
219 'amount_with_cb' => '0.00000',
221 'cb_amount' => '-206.60000',
222 'ob_amount' => '-40.00000',
223 'ob_next_year' => '-206.60000',
225 'year_end_amount' => '-206.60000'
229 'amount' => '-166.60000',
230 'amount_with_cb' => '0.00000',
232 'cb_amount' => '-166.60000',
233 'ob_amount' => undef,
234 'ob_next_year' => '-166.60000',
236 'year_end_amount' => '-166.60000'
240 'amount' => '119.00000',
241 'amount_with_cb' => '0.00000',
243 'cb_amount' => '119.00000',
244 'ob_amount' => undef,
245 'ob_next_year' => '119.00000',
247 'year_end_amount' => '119.00000'
252 'amount_with_cb' => '0.00000',
254 'cb_amount' => '40.00000',
255 'ob_amount' => '40.00000',
256 'ob_next_year' => '40.00000',
258 'year_end_amount' => '40.00000'
263 'amount_with_cb' => '0.00000',
265 'cb_amount' => '0.00000',
266 'ob_amount' => undef,
267 'ob_next_year' => '0.00000',
269 'year_end_amount' => undef
273 'amount' => '-19.00000',
274 'amount_with_cb' => '0.00000',
276 'cb_amount' => '-19.00000',
277 'ob_amount' => undef,
278 'ob_next_year' => undef,
280 'year_end_amount' => '-19.00000'
284 'amount' => '53.20000',
285 'amount_with_cb' => '0.00000',
287 'cb_amount' => '53.20000',
288 'ob_amount' => undef,
289 'ob_next_year' => undef,
291 'year_end_amount' => '53.20000'
295 'amount' => '-100.00000',
296 'amount_with_cb' => '0.00000',
298 'cb_amount' => '-100.00000',
299 'ob_amount' => undef,
300 'ob_next_year' => undef,
302 'year_end_amount' => '-100.00000'
306 'amount' => '280.00000',
307 'amount_with_cb' => '0.00000',
309 'cb_amount' => '280.00000',
310 'ob_amount' => undef,
311 'ob_next_year' => undef,
313 'year_end_amount' => '280.00000'
317 # running _year_end_bookings several times shouldn't change the anything, the
318 # second and third run should be no-ops, at least while no further bookings where
321 SL::Controller::YearEndTransactions::_year_end_bookings( start_date => $start_date,
325 is(SL::DB::Manager::AccTransaction->get_all_count(where => [ cb_transaction => 1 ]), 14, 'acc_trans cb_transactions created ok');
326 is(SL::DB::Manager::AccTransaction->get_all_count(where => [ ob_transaction => 1 ]), 10, 'acc_trans ob_transactions created ok');
327 is(SL::DB::Manager::GLTransaction->get_all_count( where => [ cb_transaction => 1 ]), 5, 'GL cb_transactions created ok');
328 is(SL::DB::Manager::GLTransaction->get_all_count( where => [ ob_transaction => 1 ]), 4, 'GL ob_transactions created ok');
331 # all asset accounts should be the same, except 0890, which should be the sum of p/l-accounts
332 # all p/l account should be 0
334 # accno | account_type | sum
335 # -------+---------------------+------------
336 # 0890 | asset_account | 214.20000
337 # 1200 | asset_account | -206.60000
338 # 1400 | asset_account | -166.60000
339 # 1600 | asset_account | 119.00000
340 # 1890 | asset_account | 40.00000
341 # 9000 | asset_account | 0.00000
342 # 1576 | profit_loss_account | 0.00000
343 # 1776 | profit_loss_account | 0.00000
344 # 3400 | profit_loss_account | 0.00000
345 # 8400 | profit_loss_account | 0.00000
348 is_deeply( &get_final_balances,
349 $final_account_balances,
350 'balances after second year_end ok (nothing changed)');
356 # sum(a.amount ) filter (where ob_transaction is true and a.transdate < '2020-01-01') as ob_amount,
357 # sum(a.amount ) filter (where cb_transaction is false and ob_transaction is false and a.transdate < '2020-01-01') as amount,
358 # sum(a.amount ) filter (where cb_transaction is false and a.transdate < '2020-01-01') as year_end_amount,
359 # sum(a.amount ) filter (where a.transdate < '2020-01-01') as amount_with_cb,
360 # sum(a.amount * -1) filter (where cb_transaction is true and a.transdate < '2020-01-01') as cb_amount,
361 # sum(a.amount ) filter (where ob_transaction is true and a.transdate >= '2020-01-01') as ob_next_year,
362 # case when c.category = ANY( '{I,E}' ) then 'pl'
363 # when c.category = ANY( '{A,C,L,Q}' ) then 'asset'
367 # inner join chart c on (c.id = a.chart_id)
368 # where a.transdate >= '2019-01-01'
369 # and a.transdate <= '2020-01-01'
370 # group by c.id, c.accno, c.category
371 # order by type, c.accno;
372 # accno | description | cat | ob_amount | amount | year_end_amount | amount_with_cb | cb_amount | ob_next_year | type
373 # -------+-------------------------------------+-----+-----------+------------+-----------------+----------------+------------+--------------+-------
374 # 0890 | Gewinnvortrag vor Verwendung | Q | | | | 0.00000 | 0.00000 | 214.20000 | asset
375 # 1200 | Bank | A | -40.00000 | -166.60000 | -206.60000 | 0.00000 | -206.60000 | -206.60000 | asset
376 # 1400 | Ford. a.Lieferungen und Leistungen | A | | -166.60000 | -166.60000 | 0.00000 | -166.60000 | -166.60000 | asset
377 # 1600 | Verbindlichkeiten aus Lief.u.Leist. | L | | 119.00000 | 119.00000 | 0.00000 | 119.00000 | 119.00000 | asset
378 # 1890 | Privateinlagen | Q | 40.00000 | | 40.00000 | 0.00000 | 40.00000 | 40.00000 | asset
379 # 9000 | Saldenvorträge,Sachkonten | A | | | | 0.00000 | 0.00000 | 0.00000 | asset
380 # 1576 | Abziehbare Vorsteuer 19 % | E | | -19.00000 | -19.00000 | 0.00000 | -19.00000 | | pl
381 # 1776 | Umsatzsteuer 19 % | I | | 53.20000 | 53.20000 | 0.00000 | 53.20000 | | pl
382 # 3400 | Wareneingang 16%/19% Vorsteuer | E | | -100.00000 | -100.00000 | 0.00000 | -100.00000 | | pl
383 # 8400 | Erlöse 16%/19% USt. | I | | 280.00000 | 280.00000 | 0.00000 | 280.00000 | | pl
386 # ob_amount + amount = year_end_amount
387 # amount_with_cb should be 0 after year-end transactions
388 # year_end_amount and cb_amount should be the same (will be true with amount_with_cb = 0)
389 # cb_amount should match ob_next_year for asset accounts, except for profit-carried-forward
390 # ob_next_year should be empty for profit-loss-accounts
392 # Oops, we forgot some bookings, lets quickly add them and run
393 #_year_end_bookings again.
395 # Just these new bookings by themselves will lead to a loss, so the loss account
396 # will be booked rather than the profit account.
397 # It would probably be better to check the total profit/loss so far, and
398 # adjust that profit-loss-carry-over # chart, rather than creating a new entry
401 gl_booking(10, $booking_date, 'foo', 'bar', $cash, $bank, 0, 0);
402 gl_booking(5, $booking_date, 'foo', 'bar', $betriebsbedarf, $cash, 0, 0);
404 SL::Controller::YearEndTransactions::_year_end_bookings( start_date => $start_date,
408 is(SL::DB::Manager::AccTransaction->get_all_count(where => [ cb_transaction => 1 ]), 23, 'acc_trans cb_transactions created ok');
409 is(SL::DB::Manager::AccTransaction->get_all_count(where => [ ob_transaction => 1 ]), 16, 'acc_trans ob_transactions created ok');
410 is(SL::DB::Manager::GLTransaction->get_all_count( where => [ cb_transaction => 1 ]), 9, 'GL cb_transactions created ok');
411 is(SL::DB::Manager::GLTransaction->get_all_count( where => [ ob_transaction => 1 ]), 7, 'GL ob_transactions created ok');
413 is_deeply( &get_final_balances,
418 'amount_with_cb' => '0.00000',
420 'cb_amount' => '0.00000',
421 'ob_amount' => undef,
422 'ob_next_year' => '-5.00000',
424 'year_end_amount' => undef
429 'amount_with_cb' => '0.00000',
431 'cb_amount' => '0.00000',
432 'ob_amount' => undef,
433 'ob_next_year' => '214.20000',
435 'year_end_amount' => undef
439 'amount' => '-5.00000',
440 'amount_with_cb' => '0.00000',
442 'cb_amount' => '-5.00000',
443 'ob_amount' => undef,
444 'ob_next_year' => '-5.00000',
446 'year_end_amount' => '-5.00000'
450 'amount' => '-156.60000',
451 'amount_with_cb' => '0.00000',
453 'cb_amount' => '-196.60000',
454 'ob_amount' => '-40.00000',
455 'ob_next_year' => '-196.60000',
457 'year_end_amount' => '-196.60000'
461 'amount' => '-166.60000',
462 'amount_with_cb' => '0.00000',
464 'cb_amount' => '-166.60000',
465 'ob_amount' => undef,
466 'ob_next_year' => '-166.60000',
468 'year_end_amount' => '-166.60000'
472 'amount' => '119.00000',
473 'amount_with_cb' => '0.00000',
475 'cb_amount' => '119.00000',
476 'ob_amount' => undef,
477 'ob_next_year' => '119.00000',
479 'year_end_amount' => '119.00000'
484 'amount_with_cb' => '0.00000',
486 'cb_amount' => '40.00000',
487 'ob_amount' => '40.00000',
488 'ob_next_year' => '40.00000',
490 'year_end_amount' => '40.00000'
495 'amount_with_cb' => '0.00000',
497 'cb_amount' => '0.00000',
498 'ob_amount' => undef,
499 'ob_next_year' => '0.00000',
501 'year_end_amount' => undef
505 'amount' => '-19.80000',
506 'amount_with_cb' => '0.00000',
508 'cb_amount' => '-19.80000',
509 'ob_amount' => undef,
510 'ob_next_year' => undef,
512 'year_end_amount' => '-19.80000'
516 'amount' => '53.20000',
517 'amount_with_cb' => '0.00000',
519 'cb_amount' => '53.20000',
520 'ob_amount' => undef,
521 'ob_next_year' => undef,
523 'year_end_amount' => '53.20000'
527 'amount' => '-100.00000',
528 'amount_with_cb' => '0.00000',
530 'cb_amount' => '-100.00000',
531 'ob_amount' => undef,
532 'ob_next_year' => undef,
534 'year_end_amount' => '-100.00000'
538 'amount' => '-4.20000',
539 'amount_with_cb' => '0.00000',
541 'cb_amount' => '-4.20000',
542 'ob_amount' => undef,
543 'ob_next_year' => undef,
545 'year_end_amount' => '-4.20000'
549 'amount' => '280.00000',
550 'amount_with_cb' => '0.00000',
552 'cb_amount' => '280.00000',
553 'ob_amount' => undef,
554 'ob_next_year' => undef,
556 'year_end_amount' => '280.00000'
559 'balances after third year_end ok');
567 foreach (qw(BankAccount
577 "SL::DB::Manager::${_}"->delete_all(all => 1);
581 sub get_account_balances {
584 case when c.category = ANY( '{I,E}' ) then 'profit_loss_account'
585 when c.category = ANY( '{A,C,L,Q}' ) then 'asset_account'
590 left join chart c on (c.id = a.chart_id)
591 group by c.accno, account_type
592 order by account_type, c.accno;
595 my $result = selectall_hashref_query($::form, $dbh, $query);
599 sub get_final_balances {
603 sum(a.amount ) filter (where ob_transaction is true and a.transdate < ?) as ob_amount,
604 sum(a.amount ) filter (where cb_transaction is false and ob_transaction is false and a.transdate < ?) as amount,
605 sum(a.amount ) filter (where cb_transaction is false and a.transdate < ?) as year_end_amount,
606 sum(a.amount ) filter (where a.transdate < ?) as amount_with_cb,
607 sum(a.amount * -1) filter (where cb_transaction is true and a.transdate < ?) as cb_amount,
608 sum(a.amount ) filter (where ob_transaction is true and a.transdate = ?) as ob_next_year,
609 case when c.category = ANY( '{I,E}' ) then 'pl'
610 when c.category = ANY( '{A,C,L,Q}' ) then 'asset'
614 inner join chart c on (c.id = a.chart_id)
615 where a.transdate >= ?
617 group by c.id, c.accno, c.category
618 order by type, c.accno
621 my $result = selectall_hashref_query($::form, $dbh, $query, $ob_date, $ob_date, $ob_date, $ob_date, $ob_date, $ob_date, $start_date, $ob_date);
626 # wrapper around SL::Dev::Record::create_gl_transaction for quickly creating transactions
627 my ($amount, $date, $reference, $description, $gegenkonto, $konto, $ob, $cb) = @_;
629 # my $transdate = $::locale->parse_date_to_object($date);
631 return create_gl_transaction(
632 ob_transaction => $ob,
633 cb_transaction => $cb,
635 reference => $reference,
636 description => $description,
643 chart => $gegenkonto,