File Coverage

blib/lib/Finance/Bank/DE/DTA/Create.pm
Criterion Covered Total %
statement 18 54 33.3
branch 0 14 0.0
condition 0 18 0.0
subroutine 6 9 66.6
pod n/a
total 24 95 25.2


line stmt bran cond sub pod time code
1             ###############################################################################
2             package Finance::Bank::DE::DTA::Create;
3             #
4             ###############################################################################
5             # Dta class provides functions to create and handle with DTA files
6             # used in Germany to exchange informations about money transactions with
7             # banks or online banking programs.
8             ###############################################################################
9              
10 1     1   33824 use strict;
  1         3  
  1         45  
11 1     1   7 use warnings;
  1         2  
  1         33  
12 1     1   6 use Carp;
  1         6  
  1         89  
13 1     1   1403 use POSIX qw(strftime);
  1         21698  
  1         11  
14 1     1   4208 use Time::Local;
  1         3128  
  1         93  
15 1     1   9 use vars qw($VERSION);
  1         3  
  1         1052  
16             $VERSION = 1.03;
17              
18             sub new {
19 0     0     my $that = shift;
20 0   0       $that = ref($that) || $that;
21 0           my $self = {
22             items => 0,
23             amount => 0,
24             sum_accounts => 0,
25             sum_bank_codes => 0,
26             };
27              
28 0           bless $self, $that;
29 0           return $self->_initialize(shift);
30             }
31              
32             #****s* _initialize
33             #
34             # DESCRIPTION
35             # Initialisierung des DTA Objekts, wir von new() aufgerufen.
36             #
37             # PARAMETER
38             # %file optional: Accountfile des Kontoinhabers
39             #
40             # RETURN
41             # Objekt im Erfolgsfall, 0 wenn Fehlgeschlagen
42             #
43             #******************************************************************************
44             sub _initialize {
45 0     0     my $self = shift;
46 0           my $file = shift; # Account file sender
47 0           $self->{timestamp} = time();
48 0           $self->{exchanges} = [];
49 0           foreach my $i (
50             32, 36, 37, 38, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55,
51             56, 57, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80,
52             81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 196, 214, 220, 223
53             )
54             {
55 0           $self->{_validChars}{"$i"} = 1;
56             }
57 0 0 0       if ( $file->{type} && $file->{type} eq "credit" ) {
    0 0        
58 0           $self->{type} = "0";
59             }
60             elsif ( $file->{type} && $file->{type} eq "debit" ) {
61 0           $self->{type} = "1";
62             }
63             else {
64 0           $self->{type} = "2";
65 0           carp "You did not choose the type of the DTA file.";
66 0           return 0;
67             }
68 0 0 0       if ( $file && !$self->_setAccount($file) ) {
69 0           carp "Setting up sender account failed.";
70 0           return 0; # Setup acoount file sender failed!!
71             }
72 0           return $self;
73             }
74              
75             #****s* _setAccount
76             #
77             # DESCRIPTION
78             # Speichern der Accountdaten des Kontoinhabers im Objekt
79             #
80             # PARAMETER
81             # %file optional: Accountfile des Kontoinhabers
82             #
83             # RETURN
84             # Objekt im Erfolgsfall, 0 wenn Fehlgeschlagen
85             #
86             #******************************************************************************
87             sub _setAccount {
88 0     0     my $self = shift;
89 0           my $file = shift;
90              
91 0 0 0       if ( $file && length( $file->{name} ) > 0 ) {
92 0 0         if ( !$self->_validNumeric( $file->{bank_code} ) ) {
93 0           carp "Please provide a valid sender BLZ.";
94 0           return 0; # Fehlerhafte Bankleitzahl
95             }
96 0 0         if ( !$self->_validNumeric( $file->{account_number} ) ) {
97 0           carp "Please provide a valid sender account-number.";
98 0           return 0; # Fehlerhafte Kontonummer
99             }
100 0 0 0       if ( !$file->{additional_name} || length( $file->{additional_name} ) == 0 ) {
101 0           $file->{additional_name} = ''; # additional_name setzen, wenn nicht angegeben
102             }
103 0           $self->{account} = {
104             "name" => substr( $self->_makeValidString( $file->{name} ), 0, 27 ),
105             "bank_code" => $file->{bank_code},
106             "account_number" => $file->{account_number},
107             "additional_name" => substr( $self->_makeValidString( $file->{additional_name} ), 0, 27 )
108             };
109 0           return $self;
110             }
111             else {
112 0           croak "No sender account defined.";
113 0           return 0; # Account Name oder Parameter fehlt!
114             }
115             }
116              
117             #****s* addExchange
118             #
119             # DESCRIPTION
120             # Hinzufügen eines Bankauftrags
121             #
122             # PARAMETER
123             # %receiver Empfänger bei Gutschrift,
124             # bzw. Lastschrift (Einzug von)
125             #
126             # $amount Betrag der Zahlung in Euro z.B. 100.50
127             #
128             # $purposes Verwendungszweck: Einfacher String oder List mit zwei Strings
129             # %sender optional: Accountdaten des Kontoinhabers
130             #
131             # EXAMPLE
132             # $dta->addExchange({
133             # name => 'Mustermann',
134             # bank_code => '10120900',
135             # account_number => '1029384756',
136             # },112.45,["Kundennr: 16389176","Zeitraum: 7/06-9/06"]);
137             #
138             # RETURN
139             # Objekt im Erfolgsfall, 0 wenn Fehlgeschlagen
140             #
141             #******************************************************************************
142             sub addExchange {
143             my $self = shift;
144             my $receiver = shift;
145             my $amount = shift;
146             my $purposes = shift;
147             my $sender = shift;
148             my $exchange = {};
149              
150             if ( !$receiver->{additional_name} ) {
151             $receiver->{additional_name} = '';
152             }
153             foreach my $member qw(name bank_code account_number additional_name) {
154             if ( !$sender || !$sender->{$member} ) {
155             $sender->{$member} = $self->{account}{$member};
156             }
157             }
158             $exchange->{receiver} = $receiver;
159             $exchange->{sender} = $sender;
160              
161             foreach my $account qw(sender receiver) {
162             if ( !$exchange->{$account}{name} || length( $exchange->{$account}{name} ) == 0 ) {
163             carp "Please provide a valid $account name.";
164             return 0;
165             }
166             $exchange->{$account}{name} = substr( $self->_makeValidString( $exchange->{$account}{name} ), 0, 27 );
167              
168             if ( !$exchange->{$account}{bank_code} || !$self->_validNumeric( $exchange->{$account}{bank_code} ) ) {
169             carp "Please privide a valid $account bank code.";
170             return 0;
171             }
172             if ( !$exchange->{$account}{account_number}
173             || !$self->_validNumeric( $exchange->{$account}{account_number} ) )
174             {
175             carp "Please provide a valid $account account number.";
176             return 0;
177             }
178             $exchange->{$account}{additional_name} =
179             substr( $self->_makeValidString( $exchange->{$account}{additional_name} ), 0, 27 );
180             }
181             unless($amount){
182             carp "Please check the amount of the transaction.";
183             return 0;
184             }
185             $amount =~ s/,/\./g;
186             $exchange->{amount} = sprintf( "%.02f", $amount ) * 100;# if $amount && $amount > 0;
187              
188             if ( !ref($purposes) ) {
189             $purposes = [ $purposes, '' ];
190             }
191             my $length = @$purposes;
192             for ( my $i = 0 ; $i < $length ; ++$i ) {
193             if ( $purposes->[$i] ) {
194             $purposes->[$i] = substr( $self->_makeValidString( $purposes->[$i] ), 0, 27 );
195             }
196             }
197             $exchange->{purposes} = $purposes;
198              
199             push( @{ $self->{exchanges} }, $exchange );
200             $self->{amount} += $amount if $amount && $amount > 0;
201             $self->{items}++;
202             $self->{sum_accounts} += $exchange->{receiver}{account_number};
203             $self->{sum_bank_codes} += $exchange->{receiver}{bank_code};
204              
205             return $self;
206             }
207              
208             #****s* getContent
209             #
210             # DESCRIPTION
211             # Abruf der DTAUS Daten. Formatieren einer DTAUS0.txt Datensequenz.
212             #
213             # PARAMETER
214             # $execday optional: Zeitpunkt des Transfers
215             # Format: DD.MM.[YY]YY oder YYYY-MM-DD
216             #
217             # RETURN
218             # String mit den DTAUS Daten der Transaktionen
219             #
220             #******************************************************************************
221             sub getContent {
222             my $self = shift;
223             my $execday = shift;
224             my $text;
225              
226             my $sum_account_numbers = 0;
227             my $sum_bank_codes = 0;
228             my $sum_amounts = 0;
229             my $num_exchanges = 0;
230              
231             ## data record A
232              
233             # record length (128 Bytes)
234             $text .= sprintf( "%04s", 128 );
235              
236             # record type
237             $text .= "A";
238              
239             # file mode (credit or debit)
240             $text .= ( $self->{type} == 0 ) ? "G" : "L";
241              
242             # Customer File ("K") / Bank File ("B")
243             $text .= "K";
244              
245             # sender's bank code
246             $text .= sprintf( "%08s", $self->{account}{bank_code} );
247              
248             # only used if Bank File, otherwise NULL
249             $text .= sprintf( "%08s", "" );
250              
251             # sender's name
252             $text .= sprintf( "%- 27s", $self->{account}{name} );
253              
254             # date of file creation
255             $text .= strftime( "%d%m%y", localtime( $self->{timestamp} ) );
256              
257             # free (bank internal)
258             $text .= sprintf( "% 4s", "" );
259              
260             # sender's account number
261             $text .= sprintf( "%010s", $self->{account}{account_number} );
262              
263             # sender's reference number (optional)
264             $text .= sprintf( "%010s", "" );
265              
266             # free (reserve)
267             $text .= sprintf( "% 15s", "" );
268              
269             # execution date
270             $execday = strftime("%d.%m.%Y", localtime( $self->{timestamp} ))
271             unless $execday && length($execday);
272             my @dayvec = split /\./,$execday; # DD.MM.YYYY
273             if ($execday =~ m|-|) {
274             @dayvec = reverse(split /-/, $execday); # YYYY-MM-DD
275             }
276             $dayvec[2] -= 1900 unless $dayvec[2] < 1900;
277             my $exectime = timelocal(0,0,12,$dayvec[0],$dayvec[1] - 1,$dayvec[2]);
278             if($exectime <= $self->{timestamp} - 12 * 60 * 60 ||
279             $exectime > $self->{timestamp} + 365 * 24 * 60 * 60) {
280             carp "The date you provided is not plausible. Please double check results!";
281             }
282             # set execution date ("DDMMYYYY", optional)
283             $text .= sprintf( "% 8s", strftime( "%d%m%Y", localtime($exectime) ));
284              
285             # free (reserve)
286             $text .= sprintf( "% 24s", "" );
287              
288             # currency (1 = Euro)
289             $text .= "1";
290              
291             foreach my $exchange ( @{ $self->{exchanges} } ) {
292             ## data record(s) C
293              
294             $sum_account_numbers += $exchange->{receiver}{account_number};
295             $sum_bank_codes += $exchange->{receiver}{bank_code};
296             $sum_amounts += $exchange->{amount};
297             $num_exchanges += 1;
298              
299             my @additional_purposes = @{ $exchange->{purposes} };
300             my $first_purpose = shift(@additional_purposes);
301             my @additional_parts = ();
302             my $additional_parts_number = @additional_parts;
303              
304             if ( length( $exchange->{receiver}{additional_name} ) > 0 ) {
305             push(
306             @additional_parts,
307             {
308             type => "01",
309             data => $exchange->{receiver}{additional_name},
310             }
311             );
312             $additional_parts_number = @additional_parts;
313             }
314             foreach my $additional_purpose (@additional_purposes) {
315             push(
316             @additional_parts,
317             {
318             type => "02",
319             data => $additional_purpose,
320             }
321             );
322             $additional_parts_number = @additional_parts;
323             }
324             if ( length( $exchange->{sender}{additional_name} ) > 0 ) {
325             push(
326             @additional_parts,
327             {
328             type => "03",
329             data => $exchange->{sender}{additional_name},
330             }
331             );
332             $additional_parts_number = @additional_parts;
333             }
334              
335             my $data;
336              
337             # record length (187 Bytes + 29 Bytes for each additional part)
338             $data .= sprintf( "%04d", 187 + $additional_parts_number * 29 );
339              
340             # record type
341             $data .= "C";
342              
343             # first involved bank
344             $data .= sprintf( "%08s", $exchange->{sender}{bank_code} );
345              
346             # receiver's bank code
347             $data .= sprintf( "%08s", $exchange->{receiver}{bank_code} );
348              
349             # receiver's account number
350             $data .= sprintf( "%010s", $exchange->{receiver}{account_number} );
351              
352             # internal customer number (11 chars) or NULL
353             $data .= "0" . sprintf( "%011s", "" ) . "0";
354              
355             # payment mode (text key)
356             $data .= ( $self->{type} == 0 ) ? "51" : "05";
357              
358             # additional text key
359             $data .= "000";
360              
361             # bank internal
362             $data .= " ";
363              
364             # free (reserve)
365             $data .= sprintf( "%011s", "" );
366              
367             # sender's bank code
368             $data .= sprintf( "%08s", $exchange->{sender}{bank_code} );
369              
370             # sender's account number
371             $data .= sprintf( "%010s", $exchange->{sender}{account_number} );
372              
373             # amount
374             $data .= sprintf( "%011s", $exchange->{amount} );
375              
376             # free (reserve)
377             $data .= sprintf( "% 3s", "" );
378              
379             # receiver's name
380             $data .= sprintf( "%- 27s", $exchange->{receiver}{name} );
381              
382             # delimitation
383             $data .= sprintf( "% 8s", "" );
384              
385             # sender's name
386             $data .= sprintf( "%- 27s", $exchange->{sender}{name} );
387              
388             # first line of purposes
389             $data .= sprintf( "%- 27s", $first_purpose );
390              
391             # currency (1 = Euro)
392             $data .= "1";
393              
394             # free (reserve)
395             $data .= sprintf( "% 2s", "" );
396              
397             # amount of additional parts
398             $data .= sprintf( "%02d", $additional_parts_number );
399              
400             if ( $additional_parts_number > 0 ) {
401             my $part;
402             for ( my $index = 1 ; $index <= 2 ; $index++ ) {
403             my $additional_part;
404             if ( $additional_parts_number > 0 ) {
405             $additional_part = shift(@additional_parts);
406             $additional_parts_number = @additional_parts;
407             }
408             else {
409             $additional_part = {
410             type => " ",
411             data => ""
412             };
413             }
414              
415             # type of addional part
416             $part .= $additional_part->{type};
417              
418             # additional part content
419             $part .= sprintf( "%- 27s", $additional_part->{data} );
420             }
421              
422             # delimitation
423             $part .= sprintf( "% 11s", "" );
424             $data .= $part;
425             }
426              
427             for ( my $part = 3 ; $part <= 5 ; $part++ ) {
428             my $more;
429             if ( $additional_parts_number > 0 ) {
430             for ( my $index = 1 ; $index <= 4 ; $index++ ) {
431             my $additional_part;
432             if ( $additional_parts_number > 0 ) {
433             $additional_part = shift(@additional_parts);
434             $additional_parts_number = @additional_parts;
435             }
436             else {
437             $additional_part = {
438             type => " ",
439             data => ""
440             };
441             }
442              
443             # type of addional part
444             $more .= $additional_part->{type};
445              
446             # additional part content
447             $more .= sprintf( "%- 27s", $additional_part->{data} );
448             }
449              
450             # delimitation
451             $more .= sprintf("% 12s");
452             $data .= $more;
453             }
454             }
455              
456             # print "
ap=$additional_parts_number=\n".encode_entities(Dumper($data))."
";
457             $text .= $data;
458             }
459              
460             ## data record E
461              
462             # record length (128 bytes)
463             $text .= sprintf( "%04d", 128 );
464              
465             # record type
466             $text .= "E";
467              
468             # free (reserve)
469             $text .= sprintf( "% 5s", "" );
470              
471             # number of records type C
472             $text .= sprintf( "%07s", $num_exchanges );
473              
474             # free (reserve)
475             $text .= sprintf( "%013s", "" );
476              
477             # sum of account numbers
478             $text .= sprintf( "%017s", $sum_account_numbers );
479              
480             # sum of bank codes
481             $text .= sprintf( "%017s", $sum_bank_codes );
482              
483             # sum of amounts
484             $text .= sprintf( "%013s", $sum_amounts );
485              
486             # delimitation
487             $text .= sprintf( "% 51s", "" );
488              
489             return $self->{text} = $text;
490             }
491              
492             #****s* _validChar
493             #
494             # DESCRIPTION
495             # überprüfen auf gültiges DTAUS Zeichen
496             #
497             # PARAMETER
498             # $char zu überprüfendes Zeichen
499             #
500             # RETURN
501             # 1 wenn gültig, 'undef' wenn ungültig
502             #
503             #******************************************************************************
504             sub _validChar {
505             my $self = shift;
506             my $char = ord(shift);
507              
508             return $self->{_validChars}{"$char"};
509             }
510              
511             #****s* _validString
512             #
513             # DESCRIPTION
514             # Überprüfen auf gültige DTAUS Zeichen im String.
515             #
516             # PARAMETER
517             # $string zu überprüfende Zeichenkette
518             #
519             # RETURN
520             # 1 wenn gültig, 0 wenn ungültig
521             #
522             #******************************************************************************
523             sub _validString {
524             my $char;
525             my $self = shift;
526             my $string = shift;
527             foreach $char ( split //, $string ) {
528             if ( !$self->_validChar($char) ) {
529             return 0;
530             }
531             }
532             return 1;
533             }
534              
535             #****s* _validNumeric
536             #
537             # DESCRIPTION
538             # Überprüfen ob gültige Zahl.
539             #
540             # PARAMETER
541             # $string zu überprüfende Zeichenkette
542             #
543             # RETURN
544             # 1 wenn gültig, 0 wenn ungültig
545             #
546             #******************************************************************************
547             sub _validNumeric {
548             my $self = shift;
549             my $string = shift;
550              
551             return ( $string =~ m/^[0-9]+$/ ) ? 1 : 0;
552             }
553              
554             #****s* _makeValidString
555             #
556             # DESCRIPTION
557             # Umwandeln oder entfernen ungültiger Zeichen.
558             #
559             # PARAMETER
560             # $string zu behandelnde Zeichenkette
561             #
562             # RETURN
563             # Umgewandelte Zeichenkette
564             #
565             #******************************************************************************
566             sub _makeValidString {
567             my $char;
568             my $self = shift;
569             my $string = shift;
570             my $result = '';
571              
572             $string =~ s/Ä/Ae/;
573             $string =~ s/Ö/Oe/;
574             $string =~ s/Ü/Ue/;
575             $string =~ s/ä/ae/;
576             $string =~ s/ö/oe/;
577             $string =~ s/ü/ue/;
578             $string =~ s/ß/ss/;
579             $string = uc($string);
580              
581             foreach $char ( split //, $string ) {
582             $result .= ( $self->_validChar($char) ) ? $char : ' ';
583             }
584              
585             return $result;
586             }
587              
588             sub amount {
589             my $self = shift;
590              
591             return $self->{amount};
592             }
593              
594             sub items {
595             my $self = shift;
596              
597             return $self->{items};
598              
599             }
600              
601             sub sum_accounts {
602             my $self = shift;
603             return $self->{sum_accounts};
604             }
605              
606             sub sum_bank_codes {
607             my $self = shift;
608             return $self->{sum_bank_codes};
609             }
610              
611             __END__