Projekt

Allgemein

Profil

« Zurück | Weiter » 

Revision 00b6dc22

Von Sven Schöling vor etwa 8 Jahren hinzugefügt

  • ID 00b6dc2240564add9114864655e96eb7cc6dde4f
  • Vorgänger 8a76038a
  • Nachfolger ae278b58

GDPDU: DATEV-ähnlicher Buchungsexport Rohversion

Unterschiede anzeigen:

SL/Controller/Gdpdu.pm
12 12
use SL::Helper::Flash;
13 13

  
14 14
use Rose::Object::MakeMethods::Generic (
15
  'scalar --get_set_init' => [ qw(from to tables) ],
15
  'scalar --get_set_init' => [ qw(from to) ],
16 16
);
17 17

  
18 18
__PACKAGE__->run_before('check_auth');
......
39 39
    location   => $::instance_conf->get_address,
40 40
    from       => $self->from,
41 41
    to         => $self->to,
42
    tables     => $self->tables,
43
    all_tables => !@{ $self->tables } && $::form->{all_tables},
42
    all_tables => $::form->{all_tables},
44 43
  );
45 44

  
46 45
  my $filename = $gdpdu->generate_export;
......
57 56

  
58 57
  my $error = 0;
59 58

  
60
  if ($::form->{tables}) {
61
    $self->tables([ keys %{ $::form->{tables} } ]);
62
    # theese three get inferred
63
    push @{ $self->tables }, 'invoice'              if $::form->{tables}{ar} || $::form->{tables}{ap};
64
    push @{ $self->tables }, 'orderitems'           if $::form->{tables}{oe};
65
    push @{ $self->tables }, 'delivery_order_items' if $::form->{tables}{delivery_orders};
66
  }
67

  
68
  if (!@{ $self->tables } && !$::form->{all_tables}) {
69
    flash('error', t8('No, I really do need checked tables to export.'));
70
    $error = 1;
71
  }
72

  
73 59
  if (!$::form->{from}) {
74 60
    my $epoch = DateTime->new(day => 1, month => 1, year => 1900);
75 61
    flash('info', t8('No start date given, setting to #1', $epoch->to_kivitendo));
......
86 72

  
87 73
sub init_from { DateTime->from_kivitendo($::form->{from}) }
88 74
sub init_to { DateTime->from_kivitendo($::form->{to}) }
89
sub init_tables { [ ] }
90 75

  
91 76
1;
SL/DATEV.pm
32 32
use SL::DBUtils;
33 33
use SL::DATEV::KNEFile;
34 34
use SL::DB;
35
use SL::HTML::Util ();
35 36

  
36 37
use Data::Dumper;
37 38
use DateTime;
38 39
use Exporter qw(import);
39 40
use File::Path;
40
use List::Util qw(max sum);
41
use IO::File;
42
use List::MoreUtils qw(any);
43
use List::Util qw(min max sum);
44
use List::UtilsBy qw(partition_by sort_by);
45
use Text::CSV_XS;
41 46
use Time::HiRes qw(gettimeofday);
42 47

  
43 48
{
......
45 50
  use constant {
46 51
    DATEV_ET_BUCHUNGEN => $i++,
47 52
    DATEV_ET_STAMM     => $i++,
53
    DATEV_ET_CSV       => $i++,
48 54

  
49 55
    DATEV_FORMAT_KNE   => $i++,
50 56
    DATEV_FORMAT_OBE   => $i++,
51 57
  };
52 58
}
53 59

  
54
my @export_constants = qw(DATEV_ET_BUCHUNGEN DATEV_ET_STAMM DATEV_FORMAT_KNE DATEV_FORMAT_OBE);
60
my @export_constants = qw(DATEV_ET_BUCHUNGEN DATEV_ET_STAMM DATEV_ET_CSV DATEV_FORMAT_KNE DATEV_FORMAT_OBE);
55 61
our @EXPORT_OK = (@export_constants);
56 62
our %EXPORT_TAGS = (CONSTANTS => [ @export_constants ]);
57 63

  
......
324 330
    $result = $self->kne_buchungsexport;
325 331
  } elsif ($self->exporttype == DATEV_ET_STAMM) {
326 332
    $result = $self->kne_stammdatenexport;
333
  } elsif ($self->exporttype == DATEV_ET_CSV) {
334
    $result = $self->csv_export_for_tax_accountant;
327 335
  } else {
328 336
    die 'unrecognized exporttype';
329 337
  }
......
349 357

  
350 358
sub _get_transactions {
351 359
  $main::lxdebug->enter_sub();
352
  my $self     = shift;
353
  my $fromto   = shift;
354
  my $progress_callback = shift || sub {};
360

  
361
  my ($self, %params)   = @_;
362
  my $fromto            = $params{from_to};
363
  my $progress_callback = $params{progress_callback} || sub {};
355 364

  
356 365
  my $form     =  $main::form;
357 366

  
......
374 383
  my %all_taxchart_ids = selectall_as_map($form, $self->dbh, qq|SELECT DISTINCT chart_id, TRUE AS is_set FROM tax|, 'chart_id', 'is_set');
375 384

  
376 385
  my $query    =
377
    qq|SELECT ac.acc_trans_id, ac.transdate, ac.trans_id,ar.id, ac.amount, ac.taxkey,
386
    qq|SELECT ac.acc_trans_id, ac.transdate, ac.trans_id,ar.id, ac.amount, ac.taxkey, ac.memo,
378 387
         ar.invnumber, ar.duedate, ar.amount as umsatz, ar.deliverydate,
379
         ct.name, ct.ustid,
380
         c.accno, c.taxkey_id as charttax, c.datevautomatik, c.id, ac.chart_link AS link,
388
         ct.name, ct.ustid, ct.customernumber AS vcnumber, ct.id AS customer_id, NULL AS vendor_id,
389
         c.accno, c.description AS accname, c.taxkey_id as charttax, c.datevautomatik, c.id, ac.chart_link AS link,
381 390
         ar.invoice,
382 391
         t.rate AS taxrate,
383 392
         'ar' as table
393
         tc.accno AS tax_accno, tc.description AS tax_accname,
394
         ar.notes
384 395
       FROM acc_trans ac
385 396
       LEFT JOIN ar          ON (ac.trans_id    = ar.id)
386 397
       LEFT JOIN customer ct ON (ar.customer_id = ct.id)
387 398
       LEFT JOIN chart c     ON (ac.chart_id    = c.id)
388 399
       LEFT JOIN tax t       ON (ac.tax_id      = t.id)
400
       LEFT JOIN chart tc    ON (t.chart_id     = tc.id)
389 401
       WHERE (ar.id IS NOT NULL)
390 402
         AND $fromto
391 403
         $trans_id_filter
......
393 405

  
394 406
       UNION ALL
395 407

  
396
       SELECT ac.acc_trans_id, ac.transdate, ac.trans_id,ap.id, ac.amount, ac.taxkey,
408
       SELECT ac.acc_trans_id, ac.transdate, ac.trans_id,ap.id, ac.amount, ac.taxkey, ac.memo,
397 409
         ap.invnumber, ap.duedate, ap.amount as umsatz, ap.deliverydate,
398
         ct.name,ct.ustid,
399
         c.accno, c.taxkey_id as charttax, c.datevautomatik, c.id, ac.chart_link AS link,
410
         ct.name, ct.ustid, ct.vendornumber AS vcnumber, NULL AS customer_id, ct.id AS vendor_id,
411
         c.accno, c.description AS accname, c.taxkey_id as charttax, c.datevautomatik, c.id, ac.chart_link AS link,
400 412
         ap.invoice,
401 413
         t.rate AS taxrate,
402 414
         'ap' as table
415
         tc.accno AS tax_accno, tc.description AS tax_accname,
416
         ap.notes
403 417
       FROM acc_trans ac
404 418
       LEFT JOIN ap        ON (ac.trans_id  = ap.id)
405 419
       LEFT JOIN vendor ct ON (ap.vendor_id = ct.id)
406 420
       LEFT JOIN chart c   ON (ac.chart_id  = c.id)
407 421
       LEFT JOIN tax t     ON (ac.tax_id    = t.id)
422
       LEFT JOIN chart tc    ON (t.chart_id     = tc.id)
408 423
       WHERE (ap.id IS NOT NULL)
409 424
         AND $fromto
410 425
         $trans_id_filter
......
412 427

  
413 428
       UNION ALL
414 429

  
415
       SELECT ac.acc_trans_id, ac.transdate, ac.trans_id,gl.id, ac.amount, ac.taxkey,
430
       SELECT ac.acc_trans_id, ac.transdate, ac.trans_id,gl.id, ac.amount, ac.taxkey, ac.memo,
416 431
         gl.reference AS invnumber, gl.transdate AS duedate, ac.amount as umsatz, NULL as deliverydate,
417
         gl.description AS name, NULL as ustid,
418
         c.accno, c.taxkey_id as charttax, c.datevautomatik, c.id, ac.chart_link AS link,
432
         gl.description AS name, NULL as ustid, '' AS vcname, NULL AS customer_id, NULL AS vendor_id,
433
         c.accno, c.description AS accname, c.taxkey_id as charttax, c.datevautomatik, c.id, ac.chart_link AS link,
419 434
         FALSE AS invoice,
420 435
         t.rate AS taxrate,
421 436
         'gl' as table
437
         tc.accno AS tax_accno, tc.description AS tax_accname,
438
         gl.notes
422 439
       FROM acc_trans ac
423 440
       LEFT JOIN gl      ON (ac.trans_id  = gl.id)
424 441
       LEFT JOIN chart c ON (ac.chart_id  = c.id)
425 442
       LEFT JOIN tax t   ON (ac.tax_id    = t.id)
443
       LEFT JOIN chart tc    ON (t.chart_id     = tc.id)
426 444
       WHERE (gl.id IS NOT NULL)
427 445
         AND $fromto
428 446
         $trans_id_filter
......
812 830

  
813 831
  my $fromto = $self->fromto;
814 832

  
815
  $self->_get_transactions($fromto);
833
  $self->_get_transactions(from_to => $fromto);
816 834

  
817 835
  return if $self->errors;
818 836

  
......
1082 1100
  return { 'download_token' => $self->download_token, 'filenames' => \@filenames };
1083 1101
}
1084 1102

  
1103
sub _format_accno {
1104
  my ($accno) = @_;
1105
  return $accno . ('0' x (6 - min(length($accno), 6)));
1106
}
1107

  
1108
sub csv_export_for_tax_accountant {
1109
  my ($self) = @_;
1110

  
1111
  $self->_get_transactions(from_to => $self->fromto);
1112

  
1113
  foreach my $transaction (@{ $self->{DATEV} }) {
1114
    foreach my $entry (@{ $transaction }) {
1115
      $entry->{sortkey} = join '-', map { lc } (DateTime->from_kivitendo($entry->{transdate})->strftime('%Y%m%d'), $entry->{name}, $entry->{reference});
1116
    }
1117
  }
1118

  
1119
  my %transactions =
1120
    partition_by { $_->[0]->{table} }
1121
    sort_by      { $_->[0]->{sortkey} }
1122
    grep         { 2 == scalar(@{ $_ }) }
1123
    @{ $self->{DATEV} };
1124

  
1125
  my %column_defs = (
1126
    acc_trans_id      => { 'text' => $::locale->text('ID'), },
1127
    amount            => { 'text' => $::locale->text('Amount'), },
1128
    credit_accname    => { 'text' => $::locale->text('Credit Account Name'), },
1129
    credit_accno      => { 'text' => $::locale->text('Credit Account'), },
1130
    debit_accname     => { 'text' => $::locale->text('Debit Account Name'), },
1131
    debit_accno       => { 'text' => $::locale->text('Debit Account'), },
1132
    invnumber         => { 'text' => $::locale->text('Reference'), },
1133
    name              => { 'text' => $::locale->text('Name'), },
1134
    notes             => { 'text' => $::locale->text('Notes'), },
1135
    tax               => { 'text' => $::locale->text('Tax'), },
1136
    taxkey            => { 'text' => $::locale->text('Taxkey'), },
1137
    tax_accname       => { 'text' => $::locale->text('Tax Account Name'), },
1138
    tax_accno         => { 'text' => $::locale->text('Tax Account'), },
1139
    transdate         => { 'text' => $::locale->text('Invoice Date'), },
1140
    vcnumber          => { 'text' => $::locale->text('Customer/Vendor Number'), },
1141
  );
1142

  
1143
  my @columns = qw(
1144
    acc_trans_id name           vcnumber
1145
    transdate    invnumber      amount
1146
    debit_accno  debit_accname
1147
    credit_accno credit_accname
1148
    tax
1149
    tax_accno    tax_accname    taxkey
1150
    notes
1151
  );
1152

  
1153
  my %filenames_by_type = (
1154
    ar => $::locale->text('AR Transactions'),
1155
    ap => $::locale->text('AP Transactions'),
1156
    gl => $::locale->text('GL Transactions'),
1157
  );
1158

  
1159
  my @filenames;
1160
  foreach my $type (qw(ap ar)) {
1161
    my %csvs = (
1162
      invoices   => {
1163
        content  => '',
1164
        filename => sprintf('%s %s - %s.csv', $filenames_by_type{$type}, $self->from->to_kivitendo, $self->to->to_kivitendo),
1165
        csv      => Text::CSV_XS->new({
1166
          binary   => 1,
1167
          eol      => "\n",
1168
          sep_char => ";",
1169
        }),
1170
      },
1171
      payments   => {
1172
        content  => '',
1173
        filename => sprintf('Zahlungen %s %s - %s.csv', $filenames_by_type{$type}, $self->from->to_kivitendo, $self->to->to_kivitendo),
1174
        csv      => Text::CSV_XS->new({
1175
          binary   => 1,
1176
          eol      => "\n",
1177
          sep_char => ";",
1178
        }),
1179
      },
1180
    );
1181

  
1182
    foreach my $csv (values %csvs) {
1183
      $csv->{out} = IO::File->new($self->export_path . '/' . $csv->{filename}, '>:encoding(utf8)') ;
1184
      $csv->{csv}->print($csv->{out}, [ map { $column_defs{$_}->{text} } @columns ]);
1185

  
1186
      push @filenames, $csv->{filename};
1187
    }
1188

  
1189
    foreach my $transaction (@{ $transactions{$type} }) {
1190
      my $is_payment     = any { $_->{link} =~ m{A[PR]_paid} } @{ $transaction };
1191
      my $csv            = $is_payment ? $csvs{payments} : $csvs{invoices};
1192

  
1193
      my ($soll, $haben) = map { $transaction->[$_] } ($transaction->[0]->{amount} > 0 ? (1, 0) : (0, 1));
1194
      my $tax            = defined($soll->{tax_accno})  ? $soll : $haben;
1195
      my $amount         = defined($soll->{net_amount}) ? $soll : $haben;
1196
      $haben->{notes}    = ($haben->{memo} || $soll->{memo}) if $is_payment;
1197
      $haben->{notes}  //= '';
1198
      $haben->{notes}    =  SL::HTML::Util->strip($haben->{notes});
1199
      $haben->{notes}    =~ s{\r}{}g;
1200
      $haben->{notes}    =~ s{\n+}{ }g;
1201

  
1202
      my %row            = (
1203
        amount           => $::form->format_amount({ numberformat => '1000,00' }, abs($amount->{amount}), 2),
1204
        debit_accno      => _format_accno($soll->{accno}),
1205
        debit_accname    => $soll->{accname},
1206
        credit_accno     => _format_accno($haben->{accno}),
1207
        credit_accname   => $haben->{accname},
1208
        tax              => $::form->format_amount({ numberformat => '1000,00' }, abs($amount->{amount}) - abs($amount->{net_amount}), 2),
1209
        notes            => $haben->{notes},
1210
        (map { ($_ => $tax->{$_})                    } qw(taxkey tax_accname tax_accno)),
1211
        (map { ($_ => ($haben->{$_} // $soll->{$_})) } qw(acc_trans_id invnumber name vcnumber transdate)),
1212
      );
1213

  
1214
      $csv->{csv}->print($csv->{out}, [ map { $row{$_} } @columns ]);
1215
    }
1216

  
1217
    $_->{out}->close for values %csvs;
1218
  }
1219

  
1220
  $self->add_filenames(@filenames);
1221

  
1222
  return { download_token => $self->download_token, filenames => \@filenames };
1223
}
1224

  
1085 1225
sub DESTROY {
1086 1226
  clean_temporary_directories();
1087 1227
}
SL/GDPDU.pm
13 13
use Archive::Zip;
14 14
use File::Temp ();
15 15
use File::Spec ();
16
use List::UtilsBy qw(partition_by);
16
use List::MoreUtils qw(any);
17
use List::UtilsBy qw(partition_by sort_by);
17 18

  
18 19
use SL::DB::Helper::ALL; # since we work on meta data, we need everything
19 20
use SL::DB::Helper::Mappings;
20 21
use SL::Locale::String qw(t8);
21 22

  
22 23
use Rose::Object::MakeMethods::Generic (
23
  scalar                  => [ qw(from to tables writer company location) ],
24
  'scalar --get_set_init' => [ qw(files tempfiles export_ids) ],
24
  scalar                  => [ qw(from to writer company location) ],
25
  'scalar --get_set_init' => [ qw(files tempfiles export_ids tables) ],
25 26
);
26 27

  
27 28
# in this we find:
......
32 33
# keep:        arrayref of columns that should be saved for further referencing
33 34
# tables:      arrayref with one column and one or many table.column references that were kept earlier
34 35
my %known_tables = (
35
  ar                    => { name => t8('Invoice'),                 description => t8('Sales Invoices and Accounts Receivables'),   keep => [ qw(id customer_id vendor_id) ], transdate => 'transdate', },
36
  ap                    => { name => t8('Purchase Invoice'),        description => t8('Purchase Invoices and Accounts Payables'),   keep => [ qw(id customer_id vendor_id) ], transdate => 'transdate', },
37
  oe                    => { name => t8('Orders'),                  description => t8('Orders and Quotations, Sales and Purchase'), keep => [ qw(id customer_id vendor_id) ], transdate => 'transdate', },
38
  delivery_orders       => { name => t8('Delivery Orders'),         description => t8('Delivery Orders'),                           keep => [ qw(id customer_id vendor_id) ], transdate => 'transdate', },
39
  gl                    => { name => t8('General Ledger'),          description => t8('General Ledger Entries'),                    keep => [ qw(id) ],                       transdate => 'transdate', },
40
  invoice               => { name => t8('Invoice Positions'),       description => t8('Positions for all Invoices'),                keep => [ qw(parts_id) ], tables => [ trans_id => "ar.id", "ap.id" ] },
41
  orderitems            => { name => t8('OrderItems'),              description => t8('Positions for all Orders'),                  keep => [ qw(parts_id) ], tables => [ trans_id => "oe.id" ] },
42
  delivery_order_items  => { name => t8('Delivery Order Items'),    description => t8('Positions for all Delivery Orders'),                      keep => [ qw(parts_id) ], tables => [ delivery_order_id => "delivery_orders.id" ] },
43
  acc_trans             => { name => t8('Transactions'),            description => t8('All general ledger entries'),                keep => [ qw(chart_id) ], tables => [ trans_id => "ar.id", "ap.id", "oe.id", "delivery_orders.id", "gl.id" ] },
44
  chart                 => { name => t8('Charts'),                  description => t8('Chart of Accounts'),                                                   tables => [ id => "acc_trans.chart_id" ] },
45
  customer              => { name => t8('Customers'),               description => t8('Customer Master Data'),                                                tables => [ id => "ar.customer_id", "ap.customer_id", "oe.customer_id", "delivery_orders.customer_id" ] },
46
  vendor                => { name => t8('Vendors'),                 description => t8('Vendor Master Data'),                                                  tables => [ id => "ar.vendor_id",   "ap.vendor_id",   "oe.vendor_id",   "delivery_orders.vendor_id" ] },
47
  parts                 => { name => t8('Parts'),                   description => t8('Parts, Services, and Assemblies'),                                     tables => [ id => "invoice.parts_id", "orderitems.parts_id", "delivery_order_items.parts_id" ] },
36
  chart                 => { name => t8('Charts'),                  description => t8('Chart of Accounts'),       primary_key => 'accno'     },
37
  customer              => { name => t8('Customers'),               description => t8('Customer Master Data'),                                    },
38
  vendor                => { name => t8('Vendors'),                 description => t8('Vendor Master Data'),                                               },
39
);
40

  
41
my %datev_column_defs = (
42
  acc_trans_id      => { type => 'Rose::DB::Object::Metadata::Column::Integer', text => t8('ID'), primary_key => 1 },
43
  amount            => { type => 'Rose::DB::Object::Metadata::Column::Numeric', text => t8('Amount'), },
44
  credit_accname    => { type => 'Rose::DB::Object::Metadata::Column::Text',    text => t8('Credit Account Name'), },
45
  credit_accno      => { type => 'Rose::DB::Object::Metadata::Column::Text',    text => t8('Credit Account'), },
46
  debit_accname     => { type => 'Rose::DB::Object::Metadata::Column::Text',    text => t8('Debit Account Name'), },
47
  debit_accno       => { type => 'Rose::DB::Object::Metadata::Column::Text',    text => t8('Debit Account'), },
48
  invnumber         => { type => 'Rose::DB::Object::Metadata::Column::Text',    text => t8('Reference'), },
49
  name              => { type => 'Rose::DB::Object::Metadata::Column::Text',    text => t8('Name'), },
50
  notes             => { type => 'Rose::DB::Object::Metadata::Column::Text',    text => t8('Notes'), },
51
  tax               => { type => 'Rose::DB::Object::Metadata::Column::Text',    text => t8('Tax'), },
52
  taxkey            => { type => 'Rose::DB::Object::Metadata::Column::Integer', text => t8('Taxkey'), },
53
  tax_accname       => { type => 'Rose::DB::Object::Metadata::Column::Text',    text => t8('Tax Account Name'), },
54
  tax_accno         => { type => 'Rose::DB::Object::Metadata::Column::Text',    text => t8('Tax Account'), },
55
  transdate         => { type => 'Rose::DB::Object::Metadata::Column::Date',    text => t8('Invoice Date'), },
56
  vcnumber          => { type => 'Rose::DB::Object::Metadata::Column::Text',    text => t8('Customer/Vendor Number'), },
57
  customer_id       => { type => 'Rose::DB::Object::Metadata::Column::Integer', text => t8('Customer ID'), },
58
  vendor_id         => { type => 'Rose::DB::Object::Metadata::Column::Integer', text => t8('Vendor ID'), },
59
);
60

  
61
my @datev_columns = qw(
62
  acc_trans_id
63
  customer_id vendor_id
64
  name           vcnumber
65
  transdate    invnumber      amount
66
  debit_accno  debit_accname
67
  credit_accno credit_accname
68
  tax
69
  tax_accno    tax_accname    taxkey
70
  notes
48 71
);
49 72

  
50 73
# rows in this listing are tiers.
......
108 131
    $self->do_csv_export($_);
109 132
  }
110 133

  
134
  $self->do_datev_csv_export;
135

  
111 136
  # write xml file
112 137
  $self->do_xml_file;
113 138

  
......
157 182
      for (reverse $self->sorted_tables) { $self  # see CAVEATS for table order
158 183
        ->table($_)
159 184
      }
185
      $self->do_datev_xml_table;
160 186
    })
161 187
  });
162 188
  close($fh);
......
194 220
  my $package = SL::DB::Helper::Mappings::get_package_for_table($table);
195 221

  
196 222
  # PrimaryKeys must come before regular columns, so partition first
197
  partition_by { 1 * $_->is_primary_key_member } $package->meta->columns;
223
  partition_by {
224
    $known_tables{$table}{primary_key}
225
      ? 1 * ($_ eq $known_tables{$table}{primary_key})
226
      : 1 * $_->is_primary_key_member
227
  } $package->meta->columns;
198 228
}
199 229

  
200 230
sub columns {
......
255 285
  }
256 286
}
257 287

  
288
sub do_datev_xml_table {
289
  my ($self) = @_;
290
  my $writer = $self->writer;
291

  
292
  $self->tag('Table', sub { $self
293
    ->tag('URL', "transaction.csv")
294
    ->tag('Name', t8('Transactions'))
295
    ->tag('Description', t8('Transactions'))
296
    ->tag('Validity', sub { $self
297
      ->tag('Range', sub { $self
298
        ->tag('From', $self->from->to_kivitendo(dateformat => 'dd.mm.yyyy'))
299
        ->tag('To',   $self->to->to_kivitendo(dateformat => 'dd.mm.yyyy'))
300
      })
301
      ->tag('Format', $date_format)
302
    })
303
    ->tag('UTF8')
304
    ->tag('DecimalSymbol', '.')
305
    ->tag('DigitGroupingSymbol', '|')     # see CAVEATS in documentation
306
    ->tag('VariableLength', sub { $self
307
      ->tag('ColumnDelimiter', ',')       # see CAVEATS for missing RecordDelimiter
308
      ->tag('TextEncapsulator', '"')
309
      ->datev_columns
310
      ->datev_foreign_keys
311
    })
312
  });
313
}
314

  
315
sub datev_columns {
316
  my ($self, $table) = @_;
317

  
318
  my %cols_by_primary_key = partition_by { $datev_column_defs{$_}{primary_key} } @datev_columns;
319
  $::lxdebug->dump(0,  "cols", \%cols_by_primary_key);
320

  
321
  for my $column (@{ $cols_by_primary_key{1} }) {
322
    my $type = $column_types{ $datev_column_defs{$column}{type} };
323

  
324
    die "unknown col type @{[ $column ]}" unless $type;
325

  
326
    $self->tag('VariablePrimaryKey', sub { $self
327
      ->tag('Name', $column);
328
      $type->($self);
329
    })
330
  }
331

  
332
  for my $column (@{ $cols_by_primary_key{''} }) {
333
    my $type = $column_types{ $datev_column_defs{$column}{type} };
334

  
335
    die "unknown col type @{[ ref $column]}" unless $type;
336

  
337
    $self->tag('VariableColumn', sub { $self
338
      ->tag('Name', $column);
339
      $type->($self);
340
    })
341
  }
342

  
343
  $self;
344
}
345

  
346
sub datev_foreign_keys {
347
  my ($self) = @_;
348
  # hard code weeee
349
  $self->tag('ForeignKey', sub { $_[0]
350
    ->tag('Name', 'customer_id')
351
    ->tag('References', 'customer')
352
  });
353
  $self->tag('ForeignKey', sub { $_[0]
354
    ->tag('Name', 'vendor_id')
355
    ->tag('References', 'vendor')
356
  });
357
  $self->tag('ForeignKey', sub { $_[0]
358
    ->tag('Name', $_)
359
    ->tag('References', 'chart')
360
  }) for qw(debit_accno credit_accno tax_accno);
361
}
362

  
363
sub do_datev_csv_export {
364
  my ($self) = @_;
365

  
366
  my $datev = SL::DATEV->new(from => $self->from, to => $self->to);
367

  
368
  $datev->_get_transactions(from_to => $datev->fromto);
369

  
370
  for my $transaction (@{ $datev->{DATEV} }) {
371
    for my $entry (@{ $transaction }) {
372
      $entry->{sortkey} = join '-', map { lc } (DateTime->from_kivitendo($entry->{transdate})->strftime('%Y%m%d'), $entry->{name}, $entry->{reference});
373
    }
374
  }
375

  
376
  my @transactions = sort_by { $_->[0]->{sortkey} } @{ $datev->{DATEV} };
377

  
378
  my $csv = Text::CSV_XS->new({
379
    binary   => 1,
380
    eol      => "\n",
381
    sep_char => ";",
382
  });
383

  
384
  my ($fh, $filename) = File::Temp::tempfile();
385
  binmode($fh, ':utf8');
386

  
387
  $self->files->{"transactions.csv"} = $filename;
388
  push @{ $self->tempfiles }, $filename;
389

  
390
  for my $transaction (@transactions) {
391
    my $is_payment     = any { $_->{link} =~ m{A[PR]_paid} } @{ $transaction };
392

  
393
    my ($soll, $haben) = map { $transaction->[$_] } ($transaction->[0]->{amount} > 0 ? (1, 0) : (0, 1));
394
    my $tax            = defined($soll->{tax_accno})  ? $soll : $haben;
395
    my $amount         = defined($soll->{net_amount}) ? $soll : $haben;
396
    $haben->{notes}    = ($haben->{memo} || $soll->{memo}) if $haben->{memo} || $soll->{memo};
397
    $haben->{notes}  //= '';
398
    $haben->{notes}    =  SL::HTML::Util->strip($haben->{notes});
399
    $haben->{notes}    =~ s{\r}{}g;
400
    $haben->{notes}    =~ s{\n+}{ }g;
401

  
402
    my %row            = (
403
      customer_id      => $soll->{customer_id} || $haben->{customer_id},
404
      vendor_id        => $soll->{vendor_id} || $haben->{vendor_id},
405
      amount           => abs($amount->{amount}),
406
      debit_accno      => $soll->{accno},
407
      debit_accname    => $soll->{accname},
408
      credit_accno     => $haben->{accno},
409
      credit_accname   => $haben->{accname},
410
      tax              => abs($amount->{amount}) - abs($amount->{net_amount}),
411
      notes            => $haben->{notes},
412
      (map { ($_ => $tax->{$_})                    } qw(taxkey tax_accname tax_accno)),
413
      (map { ($_ => ($haben->{$_} // $soll->{$_})) } qw(acc_trans_id invnumber name vcnumber transdate)),
414
    );
415

  
416
    $csv->print($fh, [ map { $row{$_} } @datev_columns ]);
417
  }
418

  
419
  # and build xml spec for it
420
}
421

  
258 422
sub do_csv_export {
259 423
  my ($self, $table) = @_;
260 424

  
......
382 546
sub init_files { +{} }
383 547
sub init_export_ids { +{} }
384 548
sub init_tempfiles { [] }
549
sub init_tables { [ grep { $known_tables{$_} } @export_table_order ] }
385 550

  
386 551
sub API_VERSION {
387 552
  DateTime->new(year => 2002, month => 8, day => 14)->to_kivitendo;
locale/de/all
717 717
  'Credit'                      => 'Haben',
718 718
  'Credit (one letter abbreviation)' => 'H',
719 719
  'Credit Account'              => 'Habenkonto',
720
  'Credit Account Name'         => 'Haben-Kontoname',
720 721
  'Credit Limit'                => 'Kreditlimit',
721 722
  'Credit Limit exceeded!!!'    => 'Kreditlimit überschritten!',
722 723
  'Credit Note'                 => 'Gutschrift',
......
832 833
  'Debit'                       => 'Soll',
833 834
  'Debit (one letter abbreviation)' => 'S',
834 835
  'Debit Account'               => 'Sollkonto',
836
  'Debit Account Name'          => 'Soll-Kontoname',
835 837
  'Debit Starting Balance'      => 'EB Passiva',
836 838
  'Debit Tax'                   => 'Vorsteuer',
837 839
  'Debit Tax Account'           => 'Vorsteuerkonto',
......
1270 1272
  'Export date'                 => 'Exportdatum',
1271 1273
  'Export date from'            => 'Exportdatum von',
1272 1274
  'Export date to'              => 'Exportdatum bis',
1275
  'Export for tax accountant'   => 'Export für Steuerberater',
1273 1276
  'Extend automatically by n months' => 'Automatische Verlängerung um x Monate',
1274 1277
  'Extended'                    => 'Gesamt',
1275 1278
  'Extended status'             => 'Erweiterter Status',
......
2716 2719
  'Task server control'         => 'Task-Server-Steuerung',
2717 2720
  'Task server status'          => 'Task-Server-Status',
2718 2721
  'Tax'                         => 'Steuer',
2722
  'Tax Account'                 => 'Steuerkonto',
2723
  'Tax Account Name'            => 'Steuerkontoname',
2719 2724
  'Tax Consultant'              => 'Steuerberater/-in',
2720 2725
  'Tax ID number'               => 'UStID-Nummer',
2721 2726
  'Tax Included'                => 'Steuer im Preis inbegriffen',
templates/webpages/gdpdu/filter.html
20 20
    <td>[% 'To Date' | $T8 %]</td>
21 21
    <td>[% L.date_tag('to', SELF.to) %]</td>
22 22
  </tr>
23
  <tr>
24
    <td>[% 'Include in Report' | $T8 %]</td>
25
    <td>
26
      [% L.checkbox_tag('tables.ar', label=LxERP.t8('Invoices'), checked=1) %]
27
      [% L.checkbox_tag('tables.ap', label=LxERP.t8('Purchase Invoices'), checked=1) %]
28
      [% L.checkbox_tag('tables.gl', label=LxERP.t8('GL Transactions'), checked=1) %]
29
      [% L.checkbox_tag('tables.delivery_orders', label=LxERP.t8('Delivery Orders'), checked=1) %]
30
      [% L.checkbox_tag('tables.oe', label=LxERP.t8('Quotations and orders'), checked=1) %]
31
      [% L.checkbox_tag('tables.customer', label=LxERP.t8('Customers'), checked=1) %]
32
      [% L.checkbox_tag('tables.vendor', label=LxERP.t8('Vendors'), checked=1) %]
33
      [% L.checkbox_tag('tables.parts', label=LxERP.t8('Parts'), checked=1) %]
34
      [% L.checkbox_tag('tables.acc_trans', label=LxERP.t8('Transactions'), checked=1) %]
35
      [% L.checkbox_tag('tables.chart', label=LxERP.t8('Charts'), checked=1) %]
36
    </td>
37
  </tr>
38 23
</table>
39 24

  
40 25
[% L.hidden_tag('action', 'Gdpdu/dispatch') %]

Auch abrufbar als: Unified diff