Projekt

Allgemein

Profil

« Zurück | Weiter » 

Revision 7130c4c1

Von Jan Büren vor etwa 7 Jahren hinzugefügt

  • ID 7130c4c1a0cd348455b9b046b58edd6b45ec47a8
  • Vorgänger 39499f9d
  • Nachfolger 18a2ca33

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

Unterschiede anzeigen:

SL/DATEV.pm
375 375

  
376 376
    $self->generate_datev_data(from_to => $self->fromto);
377 377
    return if $self->errors;
378
    my $datev_ref;
379
    ($datev_ref, $self->{warnings}) = SL::DATEV::CSV->new(datev_lines  => $self->generate_datev_lines,
380
                                                          from         => $self->from,
381
                                                          to           => $self->to,
382
                                                          locked       => $self->locked,
383
                                                         );
378

  
379
    my $datev_csv = SL::DATEV::CSV->new(
380
      datev_lines  => $self->generate_datev_lines,
381
      from         => $self->from,
382
      to           => $self->to,
383
      locked       => $self->locked,
384
    );
385

  
384 386

  
385 387
    my $filename = "EXTF_DATEV_kivitendo" . $self->from->ymd() . '-' . $self->to->ymd() . ".csv";
386 388

  
......
392 394
              }) or die "Cannot use CSV: ".Text::CSV_XS->error_diag();
393 395

  
394 396
    my $csv_file = IO::File->new($self->export_path . '/' . $filename, '>:encoding(cp1252)') or die "Can't open: $!";
395
    $csv->print($csv_file, $_) for @{ $datev_ref };
397
    $csv->print($csv_file, $_) for @{ $datev_csv->header };
398
    $csv->print($csv_file, $_) for @{ $datev_csv->lines  };
396 399
    $csv_file->close;
400
    $self->{warnings} = $datev_csv->warnings;
397 401

  
398 402
    return { download_token => $self->download_token, filenames => $filename };
399 403

  
......
1634 1638
  # };
1635 1639

  
1636 1640

  
1637
=item csv_buchungsexport
1638

  
1639
Generates the CSV-Format data for the CSV DATEV export and returns
1640
an 2-dimensional array as an array_ref.
1641

  
1642
Requires $self->fromto for a valid DATEV header.
1643

  
1644
Furthermore we assume that the first day of the fiscal year is
1645
the first of January and we cannot guarantee that our data in kivitendo
1646
is locked, that means a booking cannot be modified after a defined (vat tax)
1647
period.
1648
Some validity checks (max_length and regex) will be done if the
1649
data structure contains them and the field is defined.
1650

  
1651
To add or alter the structure of the data take a look at SL::DATEV::CSV.pm
1652

  
1653
=item _csv_buchungsexport_to_file
1654

  
1655
Generates one downloadable csv file wrapped in a zip archive.
1656
Basically this method is just a thin wrapper for TEXT::CSV_XS.pm
1657

  
1658
Generates a CSV-file with the same encodings as defined in DATEV Format CSV 2015:
1659
 $ file
1660
 $ EXTF_Buchungsstapel.csv: ISO-8859 text, with very long lines, with CRLF line terminators
1661

  
1662
Usage: _csv_buchungsexport_to_file($self, data => $self->csv_buchungsexport);
1663

  
1664 1641
=item check_vcnumbers_are_valid_pk_numbers
1665 1642

  
1666 1643
Returns 1 if all vcnumbers are suitable for the DATEV export, 0 if not.
SL/DATEV/CSV.pm
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

  

Auch abrufbar als: Unified diff