11 |
11 |
use Encode qw(decode);
|
12 |
12 |
use Scalar::Util qw(looks_like_number);
|
13 |
13 |
|
|
14 |
use Rose::Object::MakeMethods::Generic (
|
|
15 |
scalar => [ qw(datev_lines from to locked warnings) ],
|
|
16 |
);
|
14 |
17 |
|
15 |
18 |
my @kivitendo_to_datev = (
|
16 |
19 |
{
|
... | ... | |
239 |
242 |
my $class = shift;
|
240 |
243 |
my %data = @_;
|
241 |
244 |
|
242 |
|
my $obj = bless {}, $class;
|
243 |
|
|
244 |
245 |
croak(t8('We need a valid from date')) unless (ref $data{from} eq 'DateTime');
|
245 |
246 |
croak(t8('We need a valid to date')) unless (ref $data{to} eq 'DateTime');
|
246 |
247 |
croak(t8('We need a array of datev_lines')) unless (ref $data{datev_lines} eq 'ARRAY');
|
247 |
248 |
|
248 |
|
# TODO no params here, better class variables/values
|
249 |
|
return _csv_buchungsexport(from => $data{from},
|
250 |
|
to => $data{to},
|
251 |
|
datev_lines => $data{datev_lines},
|
252 |
|
locked => $data{locked},
|
253 |
|
);
|
254 |
|
|
|
249 |
my $obj = bless {}, $class;
|
|
250 |
$obj->$_($data{$_}) for keys %data;
|
255 |
251 |
$obj;
|
256 |
252 |
}
|
257 |
253 |
|
... | ... | |
274 |
270 |
return @kivitendo_to_datev;
|
275 |
271 |
}
|
276 |
272 |
|
277 |
|
sub _generate_csv_header {
|
278 |
|
my %params = @_;
|
279 |
|
|
280 |
|
# we need from and to in YYYYDDMM
|
281 |
|
croak "Wrong format for from $params{from}" unless $params{from} =~ m/^[0-9]{8}$/;
|
282 |
|
croak "Wrong format for to $params{to}" unless $params{to} =~ m/^[0-9]{8}$/;
|
283 |
|
|
284 |
|
# who knows if we want locking and when our fiscal year starts
|
285 |
|
# croak "Wrong state of locking" unless $params{locked} =~ m/^(0|1)$/;
|
286 |
|
my $locked = defined($params{locked}) ? 1 : 0;
|
287 |
|
croak "No startdate of fiscal year" unless $params{first_day_of_fiscal_year} =~ m/^[0-9]{8}$/;
|
|
273 |
sub header {
|
|
274 |
my ($self) = @_;
|
288 |
275 |
|
|
276 |
my @header;
|
289 |
277 |
|
290 |
278 |
# we can safely set these defaults
|
|
279 |
# TODO use Helper::DateTime and get lenght_of_accounts from DATEV.pm
|
291 |
280 |
my $today = DateTime->now(time_zone => "local");
|
292 |
281 |
my $created_on = $today->ymd('') . $today->hms('') . '000';
|
293 |
282 |
my $length_of_accounts = length(SL::DB::Manager::Chart->get_first(where => [charttype => 'A'])->accno) // 4;
|
... | ... | |
308 |
297 |
$meta_datev{$k} = substr $datev->{$k}, 0, $v;
|
309 |
298 |
}
|
310 |
299 |
|
311 |
|
my @header = (
|
|
300 |
my @header_row_1 = (
|
312 |
301 |
"EXTF", "300", 21, "Buchungsstapel", 7, $created_on, "", "ki",
|
313 |
302 |
"kivitendo-datev", "", $meta_datev{beraternr}, $meta_datev{mandantennr},
|
314 |
|
$params{first_day_of_fiscal_year}, $length_of_accounts,
|
315 |
|
$params{from}, $params{to}, "", "", 1, "", $locked,
|
|
303 |
$self->first_day_of_fiscal_year->ymd(''), $length_of_accounts,
|
|
304 |
$self->from->ymd(''), $self->to->ymd(''), "", "", 1, "", $self->locked,
|
316 |
305 |
$default_curr, "", "", "",""
|
317 |
306 |
);
|
|
307 |
push @header, [ @header_row_1 ];
|
|
308 |
|
|
309 |
# second header row, just the column names
|
|
310 |
push @header, [ map { $_->{csv_header_name} } _kivitendo_to_datev() ];
|
318 |
311 |
|
319 |
|
return @header;
|
|
312 |
return \@header;
|
320 |
313 |
}
|
321 |
314 |
|
322 |
|
sub _csv_buchungsexport {
|
323 |
|
my %params = @_;
|
|
315 |
sub lines {
|
|
316 |
my ($self) = @_;
|
324 |
317 |
|
|
318 |
my (@array_of_datev, @warnings);
|
325 |
319 |
my @csv_columns = _kivitendo_to_datev();
|
326 |
|
my @csv_headers = _generate_csv_header(
|
327 |
|
from => $params{from}->ymd(''),
|
328 |
|
to => $params{to}->ymd(''),
|
329 |
|
first_day_of_fiscal_year => $params{to}->year . '0101',
|
330 |
|
locked => $params{locked}
|
331 |
|
);
|
332 |
|
|
333 |
|
my @array_of_datev;
|
334 |
320 |
|
335 |
|
# 2 Headers
|
336 |
|
push @array_of_datev, \@csv_headers;
|
337 |
|
push @array_of_datev, [ map { $_->{csv_header_name} } @csv_columns ];
|
338 |
|
|
339 |
|
my @warnings;
|
340 |
|
foreach my $row (@{ $params{datev_lines} }) {
|
|
321 |
foreach my $row (@{ $self->datev_lines }) {
|
341 |
322 |
my @current_datev_row;
|
342 |
323 |
|
343 |
324 |
# 1. check all datev_lines and see if we have a defined value
|
... | ... | |
357 |
338 |
}
|
358 |
339 |
}
|
359 |
340 |
# checkpoint a: no undefined data. All strict checks now!
|
360 |
|
if (exists $column->{input_check}) {
|
|
341 |
if (exists $column->{input_check} && !$column->{input_check}->($data)) {
|
361 |
342 |
die t8("Wrong field value '#1' for field '#2' for the transaction with amount '#3'",
|
362 |
|
$data, $column->{kivi_datev_name}, $row->{umsatz})
|
363 |
|
unless $column->{input_check}->($data);
|
|
343 |
$data, $column->{kivi_datev_name}, $row->{umsatz});
|
364 |
344 |
}
|
365 |
345 |
# checkpoint b: we can safely format the input
|
366 |
346 |
if ($column->{formatter}) {
|
... | ... | |
375 |
355 |
}
|
376 |
356 |
push @array_of_datev, \@current_datev_row;
|
377 |
357 |
}
|
378 |
|
return (\@array_of_datev, \@warnings);
|
|
358 |
$self->warnings(\@warnings);
|
|
359 |
return \@array_of_datev;
|
379 |
360 |
}
|
380 |
361 |
|
|
362 |
# helper
|
|
363 |
|
381 |
364 |
sub _format_amount {
|
382 |
365 |
$::form->format_amount({ numberformat => '1000,00' }, @_);
|
383 |
366 |
}
|
384 |
367 |
|
|
368 |
sub first_day_of_fiscal_year {
|
|
369 |
$_[0]->to->clone->truncate(to => 'year');
|
|
370 |
}
|
|
371 |
|
385 |
372 |
1;
|
386 |
373 |
|
387 |
374 |
__END__
|
... | ... | |
407 |
394 |
);
|
408 |
395 |
$datev->generate_datev_data;
|
409 |
396 |
|
410 |
|
my $datev_ref = SL::DATEV::CSV->new(datev_lines => $datev->generate_datev_lines,
|
|
397 |
my $datev_csv = SL::DATEV::CSV->new(datev_lines => $datev->generate_datev_lines,
|
411 |
398 |
from => $datev->from,
|
412 |
399 |
to => $datev->to,
|
413 |
400 |
locked => $datev->locked,
|
414 |
401 |
);
|
|
402 |
$datev_csv->header; # returns the required 2 rows of header ($aref = [ ["row1" ..], [ "row2" .. ] ]) as array of array
|
|
403 |
$datev_csv->lines; # returns an array_ref of rows of array_refs soll uns die ein Arrayref von Zeilen zurückgeben, die jeweils Arrayrefs sind
|
|
404 |
$datev_csv->warnings; # returns warnings
|
|
405 |
|
|
406 |
|
|
407 |
# The above object methods can be directly chained to a CSV export function, like this:
|
|
408 |
my $csv_file = IO::File->new($somewhere_in_filesystem)') or die "Can't open: $!";
|
|
409 |
$csv->print($csv_file, $_) for @{ $datev_csv->header };
|
|
410 |
$csv->print($csv_file, $_) for @{ $datev_csv->lines };
|
|
411 |
$csv_file->close;
|
|
412 |
$self->{warnings} = $datev_csv->warnings;
|
|
413 |
|
|
414 |
|
|
415 |
|
415 |
416 |
|
416 |
417 |
=head1 DESCRIPTION
|
417 |
418 |
|
... | ... | |
490 |
491 |
Helper function, returns true if a string is not empty and cp1252 encoded
|
491 |
492 |
For example some arabic utf-8 like ݐ will return false
|
492 |
493 |
|
493 |
|
=item generate_csv_header(from => 'YYYYDDMM', to => 'YYYYDDMM', locked => 0,
|
494 |
|
first_day_of_fiscal_year => 'YYYYDDMM')
|
|
494 |
=item header
|
495 |
495 |
|
496 |
496 |
Mostly all other header information are constants or metadata loaded
|
497 |
497 |
from SL::DB::Datev.pm.
|
... | ... | |
499 |
499 |
Returns the first two entries for the header (see above: File Structure)
|
500 |
500 |
as an array.
|
501 |
501 |
|
502 |
|
All params are mandatory:
|
503 |
|
C<params{from}>, C<params{to}>
|
504 |
|
and C<params{first_day_of_fiscal_year}> have to be in YYYYDDMM date string
|
505 |
|
format.
|
506 |
|
Furthermore C<params{locked}> is a perlish boolean.
|
507 |
|
|
508 |
|
|
509 |
502 |
=item kivitendo_to_datev
|
510 |
503 |
|
511 |
504 |
Returns the data structure C<@datev_data> as an array
|
... | ... | |
516 |
509 |
Expects a number in kivitendo database format and returns the same number
|
517 |
510 |
in DATEV format.
|
518 |
511 |
|
519 |
|
=item _csv_buchungsexport
|
|
512 |
=item first_day_of_fiscal_year
|
|
513 |
|
|
514 |
Takes a look at $self->to to determine the first day of the fiscal year.
|
|
515 |
|
|
516 |
=item lines
|
520 |
517 |
|
521 |
518 |
Generates the CSV-Format data for the CSV DATEV export and returns
|
522 |
519 |
an 2-dimensional array as an array_ref.
|
... | ... | |
533 |
530 |
|
534 |
531 |
To add or alter the structure of the data take a look at the C<@kivitendo_to_datev> structure.
|
535 |
532 |
|
536 |
|
|
537 |
533 |
=back
|
|
534 |
|
|
535 |
=head1 TODO CAVEAT
|
|
536 |
|
|
537 |
|
|
538 |
Currently no effort has be done that _kivitenod_to_datev is only intializied once:
|
|
539 |
Therefore the second call may generate integrity faults:
|
|
540 |
|
|
541 |
my $datev_csv_1 = SL::DATEV::CSV->new(...)->lines;
|
|
542 |
my $datev_csv_2 = SL::DATEV::CSV->new(...)->lines;
|
|
543 |
|
|
544 |
Secondly one can circumevent the check of the warnings.quite easily,
|
|
545 |
becaus warnings are generated after the call to lines:
|
|
546 |
|
|
547 |
# WRONG usage
|
|
548 |
die if @{ $datev_csv->warnings };
|
|
549 |
somethin_with($datev_csv->lines);
|
|
550 |
|
|
551 |
# safe usage
|
|
552 |
my $lines = $datev_csv->lines;
|
|
553 |
die if @{ $datev_csv->warnings };
|
|
554 |
somethin_with($lines);
|
|
555 |
|
DATEV: Saubere Objektmethoden für CSV.pm implementiert
PODs ergänzt.
Hintergrund: Sehr klare Ideen von Sven implementiert, sprengt den
Rahmen der Commit-Message, Details siehe Doku in redmine
http://redmine.kivitendo-premium.de/documents/18