9 |
9 |
use Carp;
|
10 |
10 |
use DateTime;
|
11 |
11 |
use Encode qw(decode);
|
|
12 |
use Scalar::Util qw(looks_like_number);
|
12 |
13 |
|
13 |
14 |
|
14 |
15 |
my @kivitendo_to_datev = (
|
... | ... | |
17 |
18 |
csv_header_name => t8('Transaction Value'),
|
18 |
19 |
max_length => 13,
|
19 |
20 |
type => 'Value',
|
|
21 |
required => 1,
|
|
22 |
input_check => sub { my ($input) = @_; return (looks_like_number($input) && length($input) <= 13) },
|
|
23 |
formatter => sub { my ($input) = @_; return _format_amount($input) },
|
20 |
24 |
valid_check => sub { my ($check) = @_; return ($check =~ m/^\d{1,10}(\,\d{1,2})?$/) },
|
21 |
25 |
},
|
22 |
26 |
{
|
... | ... | |
24 |
28 |
csv_header_name => t8('Debit/Credit Label'),
|
25 |
29 |
max_length => 1,
|
26 |
30 |
type => 'Text',
|
|
31 |
required => 1,
|
|
32 |
default => 'S',
|
|
33 |
input_check => sub { my ($check) = @_; return ($check =~ m/^(S|H)$/) },
|
|
34 |
formatter => sub { my ($input) = @_; return $input eq 'H' ? 'H' : 'S' },
|
27 |
35 |
valid_check => sub { my ($check) = @_; return ($check =~ m/^(S|H)$/) },
|
28 |
36 |
},
|
29 |
37 |
{
|
... | ... | |
31 |
39 |
csv_header_name => t8('Transaction Value Currency Code'),
|
32 |
40 |
max_length => 3,
|
33 |
41 |
type => 'Text',
|
|
42 |
default => '',
|
|
43 |
input_check => sub { my ($check) = @_; return ($check eq '' || $check =~ m/^[A-Z]{3}$/) },
|
34 |
44 |
valid_check => sub { my ($check) = @_; return ($check =~ m/^[A-Z]{3}$/) },
|
35 |
45 |
},
|
36 |
46 |
{
|
... | ... | |
38 |
48 |
csv_header_name => t8('Exchange Rate'),
|
39 |
49 |
max_length => 11,
|
40 |
50 |
type => 'Number',
|
|
51 |
default => '',
|
41 |
52 |
valid_check => sub { my ($check) = @_; return ($check =~ m/^[0-9]*\.?[0-9]*$/) },
|
42 |
53 |
},
|
43 |
54 |
{
|
44 |
55 |
kivi_datev_name => 'not yet implemented',
|
45 |
|
csv_header_name => t8('Base Transaction Value'),
|
|
56 |
sv_header_name => t8('Base Transaction Value'),
|
46 |
57 |
},
|
47 |
58 |
{
|
48 |
59 |
kivi_datev_name => 'not yet implemented',
|
... | ... | |
51 |
62 |
{
|
52 |
63 |
kivi_datev_name => 'konto',
|
53 |
64 |
csv_header_name => t8('Account'),
|
54 |
|
max_length => 9, # May contain a maximum of 8 or 9 digits -> perldoc
|
|
65 |
max_length => 9,
|
55 |
66 |
type => 'Account',
|
56 |
|
valid_check => sub { my ($check) = @_; return ($check =~ m/^[0-9]{4,9}$/) },
|
|
67 |
required => 1,
|
|
68 |
input_check => sub { my ($check) = @_; return ($check =~ m/^[0-9]{4,9}$/) },
|
57 |
69 |
},
|
58 |
70 |
{
|
59 |
71 |
kivi_datev_name => 'gegenkonto',
|
60 |
72 |
csv_header_name => t8('Contra Account'),
|
61 |
|
max_length => 9, # May contain a maximum of 8 or 9 digits -> perldoc
|
|
73 |
max_length => 9,
|
62 |
74 |
type => 'Account',
|
63 |
|
valid_check => sub { my ($check) = @_; return ($check =~ m/^[0-9]{4,9}$/) },
|
|
75 |
required => 1,
|
|
76 |
input_check => sub { my ($check) = @_; return ($check =~ m/^[0-9]{4,9}$/) },
|
64 |
77 |
},
|
65 |
78 |
{
|
66 |
79 |
kivi_datev_name => 'buchungsschluessel',
|
67 |
80 |
csv_header_name => t8('Posting Key'),
|
68 |
81 |
max_length => 2,
|
69 |
82 |
type => 'Text',
|
70 |
|
valid_check => sub { my ($check) = @_; return ($check =~ m/^[0-9]{0,2}$/) },
|
|
83 |
default => '',
|
|
84 |
input_check => sub { my ($check) = @_; return ($check =~ m/^[0-9]{0,2}$/) },
|
71 |
85 |
},
|
72 |
86 |
{
|
73 |
87 |
kivi_datev_name => 'datum',
|
74 |
88 |
csv_header_name => t8('Invoice Date'),
|
75 |
89 |
max_length => 4,
|
76 |
90 |
type => 'Date',
|
|
91 |
required => 1,
|
|
92 |
input_check => sub { my ($check) = @_; return (ref (DateTime->from_kivitendo($check)) eq 'DateTime') },
|
|
93 |
formatter => sub { my ($input) = @_; return DateTime->from_kivitendo($input)->strftime('%d%m') },
|
77 |
94 |
valid_check => sub { my ($check) = @_; return ($check =~ m/^[0-9]{4}$/) },
|
78 |
95 |
},
|
79 |
96 |
{
|
... | ... | |
81 |
98 |
csv_header_name => t8('Invoice Field 1'),
|
82 |
99 |
max_length => 12,
|
83 |
100 |
type => 'Text',
|
84 |
|
valid_check => sub { my ($text) = @_; check_encoding($text); },
|
|
101 |
default => '',
|
|
102 |
input_check => sub { my ($text) = @_; check_encoding($text); },
|
|
103 |
formatter => sub { my ($input) = @_; return substr($input, 0, 12) },
|
85 |
104 |
},
|
86 |
105 |
{
|
87 |
106 |
kivi_datev_name => 'not yet implemented',
|
88 |
107 |
csv_header_name => t8('Invoice Field 2'),
|
89 |
|
max_length => 12,
|
|
108 |
max_length => 12,
|
90 |
109 |
type => 'Text',
|
|
110 |
default => '',
|
91 |
111 |
valid_check => sub { my ($check) = @_; return ($check =~ m/[ -~]{1,12}/) },
|
92 |
112 |
},
|
93 |
113 |
{
|
... | ... | |
100 |
120 |
csv_header_name => t8('Posting Text'),
|
101 |
121 |
max_length => 60,
|
102 |
122 |
type => 'Text',
|
103 |
|
valid_check => sub { my ($text) = @_; return 1 unless $text; check_encoding($text); },
|
|
123 |
default => '',
|
|
124 |
input_check => sub { my ($text) = @_; return 1 unless $text; check_encoding($text); },
|
|
125 |
formatter => sub { my ($input) = @_; return substr($input, 0, 60) },
|
104 |
126 |
}, # pos 14
|
105 |
127 |
{
|
106 |
128 |
kivi_datev_name => 'not yet implemented',
|
... | ... | |
177 |
199 |
csv_header_name => t8('Cost Center'),
|
178 |
200 |
max_length => 8,
|
179 |
201 |
type => 'Text',
|
180 |
|
valid_check => sub { my ($text) = @_; return 1 unless $text; check_encoding($text); },
|
|
202 |
default => '',
|
|
203 |
input_check => sub { my ($text) = @_; return 1 unless $text; check_encoding($text); },
|
|
204 |
formatter => sub { my ($input) = @_; return substr($input, 0, 8) },
|
181 |
205 |
}, # pos 37
|
182 |
206 |
{
|
183 |
207 |
kivi_datev_name => 'kost2',
|
184 |
208 |
csv_header_name => t8('Cost Center'),
|
185 |
209 |
max_length => 8,
|
186 |
210 |
type => 'Text',
|
187 |
|
valid_check => sub { my ($text) = @_; return 1 unless $text; check_encoding($text); },
|
|
211 |
default => '',
|
|
212 |
input_check => sub { my ($text) = @_; return 1 unless $text; check_encoding($text); },
|
|
213 |
formatter => sub { my ($input) = @_; return substr($input, 0, 8) },
|
188 |
214 |
}, # pos 38
|
189 |
215 |
{
|
190 |
216 |
kivi_datev_name => 'not yet implemented',
|
... | ... | |
198 |
224 |
csv_header_name => t8('EU Member State and VAT ID Number'),
|
199 |
225 |
max_length => 15,
|
200 |
226 |
type => 'Text',
|
|
227 |
default => '',
|
|
228 |
input_check => sub { my ($check) = @_; return ($check eq '' || $check =~ m/[A-Z]{2}\w{5,13}/) },
|
|
229 |
formatter => sub { my ($input) = @_; return ($input =~ s/\s//g) },
|
201 |
230 |
valid_check => sub {
|
202 |
231 |
my ($ustid) = @_;
|
203 |
|
return 1 unless defined($ustid);
|
|
232 |
return 1 if ('' eq $ustid);
|
204 |
233 |
return ($ustid =~ m/^CH|^[A-Z]{2}\w{5,13}$/);
|
205 |
234 |
},
|
206 |
235 |
}, # pos 40
|
... | ... | |
250 |
279 |
|
251 |
280 |
# we need from and to in YYYYDDMM
|
252 |
281 |
croak "Wrong format for from $params{from}" unless $params{from} =~ m/^[0-9]{8}$/;
|
253 |
|
croak "Wrong format for to $params{to}" unless $params{to} =~ m/^[0-9]{8}$/;
|
|
282 |
croak "Wrong format for to $params{to}" unless $params{to} =~ m/^[0-9]{8}$/;
|
254 |
283 |
|
255 |
284 |
# who knows if we want locking and when our fiscal year starts
|
256 |
285 |
# croak "Wrong state of locking" unless $params{locked} =~ m/^(0|1)$/;
|
... | ... | |
310 |
339 |
foreach my $row (@{ $params{datev_lines} }) {
|
311 |
340 |
my @current_datev_row;
|
312 |
341 |
|
313 |
|
# shorten strings
|
314 |
|
if ($row->{belegfeld1}) {
|
315 |
|
$row->{buchungsbes} = $row->{belegfeld1} if $row->{belegfeld1};
|
316 |
|
$row->{belegfeld1} = substr($row->{belegfeld1}, 0, 12);
|
317 |
|
$row->{buchungsbes} = substr($row->{buchungsbes}, 0, 60);
|
318 |
|
}
|
319 |
|
|
320 |
|
$row->{datum} = DateTime->from_kivitendo($row->{datum})->strftime('%d%m');
|
321 |
|
|
322 |
|
$row->{kost1} = substr($row->{kost1}, 0, 8) if $row->{kost1};
|
323 |
|
$row->{kost2} = substr($row->{kost2}, 0, 8) if $row->{kost2};
|
324 |
|
|
325 |
|
# , as decimal point and trim for UstID
|
326 |
|
$row->{umsatz} = _format_amount($row->{umsatz});
|
327 |
|
$row->{ustid} =~ s/\s//g if $row->{ustid}; # trim whitespace
|
328 |
|
|
|
342 |
# 1. check all datev_lines and see if we have a defined value
|
|
343 |
# 2. if we don't have a defined value set a default if exists
|
|
344 |
# 3. otherwise die
|
329 |
345 |
foreach my $column (@csv_columns) {
|
330 |
|
if (exists $column->{max_length} && $column->{kivi_datev_name} ne 'not yet implemented') {
|
331 |
|
# check max length
|
332 |
|
die "Incorrect length of field" if length($row->{ $column->{kivi_datev_name} }) > $column->{max_length};
|
|
346 |
if ($column->{kivi_datev_name} eq 'not yet implemented') {
|
|
347 |
push @current_datev_row, '';
|
|
348 |
next;
|
|
349 |
}
|
|
350 |
my $data = $row->{$column->{kivi_datev_name}};
|
|
351 |
if (!defined $data) {
|
|
352 |
if (defined $column->{default}) {
|
|
353 |
$data = $column->{default};
|
|
354 |
} else {
|
|
355 |
die 'No sensible value or a sensible default found for the entry: ' . $column->{kivi_datev_name};
|
|
356 |
}
|
|
357 |
}
|
|
358 |
# checkpoint a: no undefined data. All strict checks now!
|
|
359 |
if (exists $column->{input_check}) {
|
|
360 |
die t8("Wrong field value '#1' for field '#2' for the transaction with amount '#3'",
|
|
361 |
$data, $column->{kivi_datev_name}, $row->{umsatz})
|
|
362 |
unless $column->{input_check}->($data);
|
|
363 |
}
|
|
364 |
# checkpoint b: we can safely format the input
|
|
365 |
if ($column->{formatter}) {
|
|
366 |
$data = $column->{formatter}->($data);
|
333 |
367 |
}
|
334 |
|
if (exists $column->{valid_check} && $column->{kivi_datev_name} ne 'not yet implemented') {
|
335 |
|
# more checks, listed as user warnings
|
|
368 |
# checkpoint c: all soft checks now, will pop up as a user warning
|
|
369 |
if (exists $column->{valid_check} && !$column->{valid_check}->($data)) {
|
336 |
370 |
push @warnings, t8("Wrong field value '#1' for field '#2' for the transaction" .
|
337 |
|
" with amount '#3'",$row->{ $column->{kivi_datev_name} },
|
338 |
|
$column->{kivi_datev_name},$row->{umsatz})
|
339 |
|
unless ($column->{valid_check}->($row->{ $column->{kivi_datev_name} }));
|
|
371 |
" with amount '#3'", $data, $column->{kivi_datev_name}, $row->{umsatz});
|
340 |
372 |
}
|
341 |
|
push @current_datev_row, $row->{ $column->{kivi_datev_name} };
|
|
373 |
push @current_datev_row, $data;
|
342 |
374 |
}
|
343 |
375 |
push @array_of_datev, \@current_datev_row;
|
344 |
376 |
}
|
Weitere Überarbeitung DATEV/CSV.pm
Default-Werte falls definiert in datev_csv gesetzt.
Pflichtfelder markiert
Kern-Algorithmus klarer definiert (Hinweise von Sven)
- Formatierung in Array pro Feldwert ausgelagert
- Eingangs-Werte prüfen (input_check)
- Logikstruktur klarer (kein unless nach if-bedingung)