File Coverage

blib/lib/EB/Booking/IV.pm
Criterion Covered Total %
statement 21 201 10.4
branch 0 150 0.0
condition 0 85 0.0
subroutine 7 9 77.7
pod 0 2 0.0
total 28 447 6.2


line stmt bran cond sub pod time code
1             #! perl -- -*- coding: utf-8 -*-
2              
3 1     1   3254 use utf8;
  1         3  
  1         6  
4              
5             package main;
6              
7             our $cfg;
8             our $dbh;
9              
10             package EB::Booking::IV;
11              
12             # Author : Johan Vromans
13             # Created On : Thu Jul 7 14:50:41 2005
14             # Last Modified By: Johan Vromans
15             # Last Modified On: Tue Apr 24 12:25:35 2018
16             # Update Count : 371
17             # Status : Unknown, Use with caution!
18              
19             ################ Common stuff ################
20              
21 1     1   64 use strict;
  1         3  
  1         21  
22 1     1   4 use warnings;
  1         10  
  1         43  
23              
24             # Dagboek type 1: Inkoop
25             # Dagboek type 2: Verkoop
26              
27 1     1   6 use EB;
  1         2  
  1         250  
28 1     1   6 use EB::Format;
  1         2  
  1         139  
29 1     1   467 use EB::Report::Journal;
  1         3  
  1         36  
30 1     1   6 use base qw(EB::Booking);
  1         2  
  1         2497  
31              
32             my $trace_updates = $cfg->val(__PACKAGE__, "trace_updates", 0); # for debugging
33              
34             sub perform {
35 0     0 0   my ($self, $args, $opts) = @_;
36              
37 0 0         return unless $self->adm_open;
38              
39 0           my $dagboek = $opts->{dagboek};
40 0           my $dagboek_type = $opts->{dagboek_type};
41 0           my $bsk_ref = $opts->{ref};
42 0           my $bsk_att = $opts->{bijlage};
43              
44 0 0         if ( defined $bsk_att ) {
45 0 0         return unless $self->check_attachment($bsk_att);
46             }
47              
48 0 0 0       unless ( $dagboek_type == DBKTYPE_INKOOP || $dagboek_type == DBKTYPE_VERKOOP) {
49 0           warn("?".__x("Ongeldige operatie (IV) voor dagboek type {type}",
50             type => $dagboek_type)."\n");
51 0           return;
52             }
53              
54 0           my $iv = $dagboek_type == DBKTYPE_INKOOP;
55 0           my $totaal = $opts->{totaal};
56 0           my $does_btw = $dbh->does_btw;
57              
58 0   0       my $bky = $self->{bky} ||= $opts->{boekjaar} || $dbh->adm("bky");
      0        
59              
60 0 0         if ( defined($totaal) ) {
61 0           my $t = amount($totaal);
62 0 0         return "?".__x("Ongeldig totaal: {total}", total => $totaal)
63             unless defined $t;
64 0           $totaal = $t;
65             }
66              
67 0           my ($begin, $end);
68 0 0         return unless ($begin, $end) = $self->begindate;
69              
70 0           my $date;
71 0 0         if ( $date = parse_date($args->[0], substr($begin, 0, 4)) ) {
72 0           shift(@$args);
73             }
74             else {
75 0 0 0       return "?".__x("Onherkenbare datum: {date}",
76             date => $args->[0])."\n"
77             if ($args->[0]||"") =~ /^[[:digit:]]+-/;
78 0           $date = iso8601date();
79             }
80              
81 0 0         return "?"._T("Deze opdracht is onvolledig. Gebruik de \"help\" opdracht voor meer aanwijzingen.")."\n"
82             unless @$args >= 3;
83              
84 0 0         return unless $self->in_bky($date, $begin, $end);
85              
86 0 0 0       if ( $does_btw && $dbh->adm("btwbegin") && $date lt $dbh->adm("btwbegin") ) {
      0        
87 0           warn("?"._T("De boekingsdatum valt in de periode waarover al BTW aangifte is gedaan")."\n");
88 0           return;
89             }
90              
91 0           my $gdesc = "";
92 0           my $debcode;
93             my $rr;
94              
95 0 0         if ( $cfg->val(qw(general ivdesc), undef) ) {
96 0           $gdesc = shift(@$args);
97 0           my $arg = shift(@$args);
98 0           ( $debcode, $bsk_ref ) = relref($arg, $bsk_ref);
99 0 0 0       if ( defined $bsk_ref && $bsk_ref =~ /^\d+$/ ) {
100 0           warn("?".__x("Boekingsreferentie moet tenminste één niet-numeriek teken bevatten: {ref}", ref => $bsk_ref)."\n");
101 0           return;
102             }
103              
104 0 0         $rr = $dbh->do("SELECT rel_code, rel_acc_id, rel_btw_status FROM Relaties" .
105             " WHERE UPPER(rel_code) = ?" .
106             " AND " . ($iv ? "NOT " : "") . "rel_debcrd" .
107             " AND rel_ledger = ?",
108             uc($debcode), $dagboek);
109 0 0         unless ( defined($rr) ) {
110 0           unshift(@$args, $arg);
111 0           ( $debcode, $bsk_ref ) = relref($gdesc, $bsk_ref);
112 0           $gdesc = "";
113 0 0         $rr = $dbh->do("SELECT rel_code, rel_acc_id, rel_btw_status FROM Relaties" .
114             " WHERE UPPER(rel_code) = ?" .
115             " AND " . ($iv ? "NOT " : "") . "rel_debcrd" .
116             " AND rel_ledger = ?",
117             uc($debcode), $dagboek);
118 0 0         unless ( defined($rr) ) {
119 0 0         warn("?".__x("Onbekende {what}: {who}",
120             what => lc($iv ? _T("Crediteur") : _T("Debiteur")),
121             who => $debcode)."\n");
122 0           return;
123             }
124             }
125             }
126             else {
127 0           my $arg = shift(@$args);
128 0           ( $debcode, $bsk_ref ) = relref($arg, $bsk_ref);
129 0 0 0       if ( defined $bsk_ref && $bsk_ref =~ /^\d+$/ ) {
130 0           warn("?".__x("Boekingsreferentie moet tenminste één niet-numeriek teken bevatten: {ref}", ref => $bsk_ref)."\n");
131 0           return;
132             }
133              
134 0 0         $rr = $dbh->do("SELECT rel_code, rel_acc_id, rel_btw_status FROM Relaties" .
135             " WHERE UPPER(rel_code) = ?" .
136             " AND " . ($iv ? "NOT " : "") . "rel_debcrd" .
137             " AND rel_ledger = ?",
138             uc($debcode), $dagboek);
139 0 0         unless ( defined($rr) ) {
140 0           $gdesc = $arg;
141 0           ( $debcode, $bsk_ref ) = relref(shift(@$args), $bsk_ref);
142 0 0         $rr = $dbh->do("SELECT rel_code, rel_acc_id, rel_btw_status FROM Relaties" .
143             " WHERE UPPER(rel_code) = ?" .
144             " AND " . ($iv ? "NOT " : "") . "rel_debcrd" .
145             " AND rel_ledger = ?",
146             uc($debcode), $dagboek);
147 0 0         unless ( defined($rr) ) {
148 0 0         warn("?".__x("Onbekende {what}: {who}",
149             what => lc($iv ? _T("Crediteur") : _T("Debiteur")),
150             who => $debcode)."\n");
151 0           return;
152             }
153             }
154             }
155              
156 0           my ($rel_acc_id, $rel_btw);
157 0           ($debcode, $rel_acc_id, $rel_btw) = @$rr;
158              
159 0           my $btw_adapt = $cfg->val(qw(strategy btw_adapt), 0);
160 0           my $nr = 1;
161 0           my $bsk_id;
162             my $bsk_nr;
163 0           my $did = 0;
164              
165 0           while ( @$args ) {
166 0 0         return "?"._T("Deze opdracht is onvolledig. Gebruik de \"help\" opdracht voor meer aanwijzingen.")."\n"
167             unless @$args >= 2;
168 0           my ($desc, $amt, $acct) = splice(@$args, 0, 3);
169 0           my $bsr_ref;
170 0 0         $desc = $gdesc if $desc !~ /\S/;
171 0 0         $gdesc = $desc if $gdesc !~ /\S/;
172 0   0       $acct ||= $rel_acc_id;
173 0 0         if ( $opts->{verbose} ) {
174 0           my $t = $desc;
175 0 0         $t = '"' . $desc . '"' if $t =~ /\s/;
176 0           warn(" "._T("boekstuk").": $t $amt $acct\n");
177             }
178 0 0         unless ( $desc =~ /\S/ ) {
179 0           warn("?"._T("De omschrijving van de boekstukregel ontbreekt")."\n");
180 0           return;
181             }
182              
183 0 0         if ( $acct !~ /^\d+$/ ) {
184 0 0         if ( $acct =~ /^(\d*)([cd])/i ) {
185 0           warn("?"._T("De \"D\" of \"C\" toevoeging aan het rekeningnummer is hier niet toegestaan")."\n");
186 0           return;
187             }
188 0           warn("?".__x("Ongeldig grootboekrekeningnummer: {acct}", acct => $acct )."\n");
189 0           return;
190             }
191 0           my $rr = $dbh->do("SELECT acc_desc,acc_balres,acc_kstomz,acc_debcrd,acc_btw".
192             " FROM Accounts".
193             " WHERE acc_id = ?", $acct);
194 0 0         unless ( $rr ) {
195 0           warn("?".__x("Onbekende grootboekrekening: {acct}",
196             acct => $acct)."\n");
197 0 0         $dbh->rollback if $dbh->in_transaction;
198 0           return;
199             }
200 0           my ($adesc, $balres, $kstomz, $debcrd, $btw_id) = @$rr;
201 0 0         if ( $balres ) {
202 0           warn("!".__x("Grootboekrekening {acct} ({desc}) is een balansrekening",
203             acct => $acct, desc => $adesc)."\n") if 0;
204             #$dbh->rollback;
205             #return;
206             }
207 0 0 0       if ( $btw_id && !$does_btw ) {
208 0           croak("INTERNAL ERROR: ".
209             __x("Grootboekrekening {acct} heeft BTW in een BTW-vrije administratie",
210             acct => $acct));
211             }
212              
213 0 0         if ( $nr == 1 ) {
214 0           $bsk_nr = $self->bsk_nr($opts);
215 0 0         return unless defined($bsk_nr);
216 0           $bsk_id = $dbh->get_sequence("boekstukken_bsk_id_seq");
217 0 0 0       if ( $bsk_ref and $dbh->do("SELECT count(*)".
218             " FROM Boekstukken, Boekstukregels".
219             " WHERE bsk_id = bsr_bsk_id".
220             " AND upper(bsk_ref) = ?".
221             " AND upper(bsr_rel_code) = ?".
222             " AND bsk_bky = ?",
223             uc($bsk_ref), uc($debcode), $bky)->[0] ) {
224 0           warn("?".__x("Referentie {ref} bestaat al voor relatie {rel}",
225             rel => $debcode, ref => $bsk_ref)."\n");
226 0           return;
227             }
228              
229              
230 0           $dbh->begin_work;
231 0           $dbh->sql_insert("Boekstukken",
232             [qw(bsk_id bsk_nr bsk_ref bsk_desc bsk_dbk_id bsk_date bsk_bky)],
233             $bsk_id, $bsk_nr, $bsk_ref, $gdesc, $dagboek, $date, $bky);
234             }
235              
236             # Amount can override BTW id with @X postfix.
237 0 0         my ($namt, $btw_spec, $btw_explicit) =
238             $does_btw ? $self->amount_with_btw($amt, $btw_id) : amount($amt);
239 0 0         unless ( defined($namt) ) {
240 0           warn("?".__x("Ongeldig bedrag: {amt}", amt => $amt)."\n");
241 0           return;
242             }
243              
244 0 0         $amt = $iv ? $namt : -$namt;
245              
246 0 0         if ( $does_btw ) {
247 0           ($btw_id, $kstomz) = $self->parse_btw_spec($btw_spec, $btw_id, $kstomz);
248 0 0         unless ( defined($btw_id) ) {
249 0           warn("?".__x("Ongeldige BTW-specificatie: {spec}", spec => $btw_spec)."\n");
250 0           return;
251             }
252             }
253              
254             # Bepalen van de BTW.
255             # Voor neutrale boekingen (@N, of op een neutrale rekening) wordt geen BTW
256             # toegepast. Op _alle_ andere wel. De BTW kan echter nul zijn, of void.
257             # Het eerste wordt bewerkstelligd door $btw_id op 0 te zetten, het tweede
258             # door $btw_acc geen waarde te geven.
259 0           my $btwclass = 0;
260 0           my $btw_acc;
261 0 0         if ( defined($kstomz) ) {
    0          
262             # BTW toepassen.
263 0 0         if ( $kstomz ? !$iv : $iv ) {
    0          
264             #warn("?".__x("U kunt geen {ko} boeken in een {iv} dagboek",
265 0 0         warn("!".__x("Pas op! U boekt {ko} in een {iv} dagboek",
    0          
266             ko => $kstomz ? _T("kosten") : _T("omzet"),
267             iv => $iv ? _T("inkoop") : _T("verkoop"),
268             )."\n");
269             #return;
270             }
271             # Void BTW voor non-EU en verlegd.
272 0 0 0       if ( $btw_id && ($rel_btw == BTWTYPE_NORMAAL || $rel_btw == BTWTYPE_INTRA) ) {
      0        
273              
274 0           my $res = $dbh->do( "SELECT btw_tariefgroep, btw_start, btw_end, btw_alias, btw_desc, btw_incl".
275             " FROM BTWTabel".
276             " WHERE btw_id = ?",
277             $btw_id );
278 0           my $incl = $res->[5];
279              
280 0 0 0       if ( $incl && $rel_btw == BTWTYPE_INTRA ) {
281 0 0         if ( $btw_explicit ) { # user specified -> warning
282 0           warn("!".__x("BTW code {code} is inclusief BTW maar relatie {rel} is intra-communautair",
283             code => $btw_id, rel => $debcode)."\n" );
284             }
285 0           warn("!".__x("Er wordt geen BTW berekend voor intra-relatie {rel}",
286             rel => $debcode)."\n" );
287             }
288              
289 0           my $tg;
290 0 0 0       unless ( defined($res) && defined( $tg = $res->[0] ) ) {
291 0           warn("?".__x("Onbekende BTW-code: {code}", code => $btw_id)."\n");
292 0           return;
293             }
294 0 0 0       if ( defined( $res->[1] ) && $res->[1] gt $date ) {
295 0           my $ok = 0;
296 0 0 0       if ( $btw_adapt && !$btw_explicit ) {
297 0 0         my $rr = $dbh->do( "SELECT btw_id, btw_desc".
298             " FROM BTWTabel".
299             " WHERE btw_tariefgroep = ?".
300             " AND btw_end >= ?".
301             " AND " . ( $incl ? "" : "NOT " ) . "btw_incl".
302             " ORDER BY btw_id",
303             $tg, $date );
304 0 0 0       if ( $rr && $rr->[0] ) {
305 0   0       warn("%".__x("BTW-code: {code} aangepast naar {new} i.v.m. de boekingsdatum",
      0        
306             code => $res->[3]||$res->[4]||$btw_id,
307             new => $rr->[1]||$rr->[0],
308             )."\n");
309 0           $btw_id = $rr->[0];
310 0           $ok++;
311             }
312             }
313 0 0         unless ( $ok ) {
314 0   0       warn("!".__x("BTW-code: {code} is nog niet geldig op de boekingsdatum",
315             code => $res->[3]||$res->[4]||$btw_id)."\n");
316             }
317             }
318 0 0 0       if ( defined( $res->[2] ) && $res->[2] lt $date ) {
319 0           my $ok = 0;
320 0 0 0       if ( $btw_adapt && !$btw_explicit ) {
321 0 0         my $rr = $dbh->do( "SELECT btw_id, btw_desc".
322             " FROM BTWTabel".
323             " WHERE btw_tariefgroep = ?".
324             " AND btw_start <= ?".
325             " AND " . ( $incl ? "" : "NOT " ) . "btw_incl".
326             " ORDER BY btw_id",
327             $tg, $date );
328 0 0 0       if ( $rr && $rr->[0] ) {
329 0   0       warn("%".__x("BTW-code: {code} aangepast naar {new} i.v.m. de boekingsdatum",
      0        
330             code => $res->[3]||$res->[4]||$btw_id,
331             new => $rr->[1]||$rr->[0],
332             )."\n");
333 0           $btw_id = $rr->[0];
334 0           $ok++;
335             }
336             }
337 0 0         unless ( $ok ) {
338 0   0       warn("!".__x("BTW-code: {code} is niet meer geldig op de boekingsdatum",
339             code => $res->[3]||$res->[4]||$btw_id)."\n");
340             }
341             }
342 0           my $tp = BTWTARIEVEN->[$tg];
343 0           my $t = qw(v i)[$iv] . lc(substr($tp, 0, 1));
344 0           $btw_acc = $dbh->std_acc("btw_$t");
345             }
346             }
347             elsif ( $btw_id ) {
348 0           warn("?"._T("BTW toepassen is niet mogelijk op een neutrale rekening")."\n");
349 0           return;
350             }
351             # ASSERT: $btw_id != 0 implies defined($kstomz).
352              
353 0 0         $dbh->sql_insert("Boekstukregels",
    0          
354             [qw(bsr_nr bsr_date bsr_bsk_id bsr_desc bsr_amount
355             bsr_btw_id bsr_btw_acc bsr_btw_class bsr_type bsr_acc_id
356             bsr_rel_code bsr_dbk_id bsr_ref)],
357             $nr++, $date, $bsk_id, $desc, $amt,
358             $btw_id, $btw_acc,
359             BTWKLASSE($does_btw ? defined($kstomz) : 0, $rel_btw, defined($kstomz) ? $kstomz : $iv),
360             0, $acct, $debcode, $dagboek, $bsr_ref);
361             }
362              
363 0           my $ret = $self->journalise($bsk_id, $iv, $totaal);
364             # $rr = [ @$ret ];
365             # shift(@$rr);
366             # $rr = [ sort { $a->[5] <=> $b->[5] } @$rr ];
367             # foreach my $r ( @$rr ) {
368             # my (undef, undef, undef, undef, $nr, $ac, $amt) = @$r;
369             # next unless $nr;
370             # warn("update $ac with ".numfmt($amt)."\n") if $trace_updates;
371             # $dbh->upd_account($ac, $amt);
372             # }
373 0           my $tot = $ret->[$#{$ret}]->[8]; # ERROR PRONE
  0            
374 0           $dbh->sql_exec("UPDATE Boekstukken SET bsk_amount = ?, bsk_open = ? WHERE bsk_id = ?",
375             $tot, $tot, $bsk_id)->finish;
376              
377 0           $dbh->store_journal($ret);
378              
379 0 0         $tot = -$tot if $iv;
380 0   0       my $fail = defined($totaal) && $tot != $totaal;
381 0 0         if ( $opts->{journal} ) {
382 0 0         warn("?"._T("Dit overzicht is ter referentie, de boeking is niet uitgevoerd!")."\n") if $fail;
383 0           EB::Report::Journal->new->journal
384             ({select => $bsk_id,
385             d_boekjaar => $bky,
386             detail => 1});
387             }
388              
389 0 0         if ( $fail ) {
390 0           $dbh->rollback;
391 0           return "?".__x("Boeking {bk} is niet uitgevoerd!",
392             bk => join(":", $dbh->lookup($dagboek, qw(Dagboeken dbk_id dbk_desc)), $bsk_nr))." ".
393             __x(" Boekstuk totaal is {act} in plaats van {exp}",
394             act => numfmt($tot), exp => numfmt($totaal)) . ".";
395             }
396              
397 0 0         $self->add_attachment( $bsk_att, $bsk_id ) if $bsk_att;
398 0           $dbh->commit;
399              
400             # TODO -- need this to get a current booking.
401 0 0 0       $opts->{verbose} || 1
402             ? join(":", $dbh->lookup($dagboek, qw(Dagboeken dbk_id dbk_desc)), $bsk_nr)
403             : "";
404             }
405              
406             sub relref {
407 0     0 0   my ( $rel, $ref ) = @_;
408 0 0         return ( $rel, $ref ) if defined $ref;
409 0 0         if ( $rel =~ /^(.+):(.+)/ ) {
410 0           ( $rel, $ref ) = ( $1, $2 );
411             }
412 0           return ( $rel, $ref );
413             }
414              
415             1;