Projekt

Allgemein

Profil

« Zurück | Weiter » 

Revision e713c314

Von Moritz Bunkus vor etwa 7 Jahren hinzugefügt

  • ID e713c3142d8c603b31d25fff371da47f56976aae
  • Vorgänger 9d06f394
  • Nachfolger 09c8c053

Benutzerdefinierte Datenexporte zu CSV anlegen und ausführen können

Unterschiede anzeigen:

SL/Controller/CustomDataExport.pm
1
package SL::Controller::CustomDataExport;
2

  
3
use strict;
4
use utf8;
5

  
6
use parent qw(SL::Controller::Base);
7

  
8
use DBI qw(:sql_types);
9
use File::Temp ();
10
use List::UtilsBy qw(sort_by);
11
use POSIX qw(strftime);
12
use Text::CSV_XS;
13

  
14
use SL::DB::CustomDataExportQuery;
15
use SL::Locale::String qw(t8);
16

  
17
use Rose::Object::MakeMethods::Generic
18
(
19
  scalar                  => [ qw(rows) ],
20
  'scalar --get_set_init' => [ qw(query queries parameters today) ],
21
);
22

  
23
__PACKAGE__->run_before('check_auth');
24
__PACKAGE__->run_before('setup_javascripts');
25

  
26
#
27
# actions
28
#
29

  
30
sub action_list {
31
  my ($self) = @_;
32

  
33
  $self->render('custom_data_export/list', title => $::locale->text('Execute a custom data export query'));
34
}
35

  
36
sub action_export {
37
  my ($self) = @_;
38

  
39
  if (!$::form->{format}) {
40
    $self->setup_export_action_bar;
41
    return $self->render('custom_data_export/export', title => t8("Execute custom data export '#1'", $self->query->name));
42
  }
43

  
44
  $self->execute_query;
45

  
46
  if (scalar(@{ $self->rows // [] }) == 1) {
47
    $self->setup_empty_result_set_action_bar;
48
    return $self->render('custom_data_export/empty_result_set', title => t8("Execute custom data export '#1'", $self->query->name));
49
  }
50

  
51

  
52
  my $method = "export_as_" . $::form->{format};
53
  $self->$method;
54
}
55

  
56
#
57
# filters
58
#
59

  
60
sub check_auth {
61
  my ($self) = @_;
62
  $::auth->assert($self->query->access_right) if $self->query->access_right;
63
}
64

  
65
sub setup_javascripts {
66
  $::request->layout->add_javascripts('kivi.Validator.js');
67
}
68

  
69
#
70
# helpers
71
#
72

  
73
sub init_query      { $::form->{id} ? SL::DB::CustomDataExportQuery->new(id => $::form->{id})->load : SL::DB::CustomDataExportQuery->new }
74
sub init_parameters { [ sort_by { lc $_->name } @{ $_[0]->query->parameters // [] } ] }
75
sub init_today      { DateTime->today_local }
76

  
77
sub init_queries {
78
  my %rights_map     = %{ $::auth->load_rights_for_user($::form->{login}) };
79
  my @granted_rights = grep { $rights_map{$_} } keys %rights_map;
80

  
81
  return scalar SL::DB::Manager::CustomDataExportQuery->get_all_sorted(
82
    where => [
83
      or => [
84
        access_right => undef,
85
        access_right => '',
86
        (access_right => \@granted_rights) x !!@granted_rights,
87
      ],
88
    ],
89
  )
90
}
91

  
92
sub setup_export_action_bar {
93
  my ($self) = @_;
94

  
95
  for my $bar ($::request->layout->get('actionbar')) {
96
    $bar->add(
97
      action => [
98
        t8('Export'),
99
        submit    => [ '#form', { action => 'CustomDataExport/export' } ],
100
        checks    => [ 'kivi.validate_form' ],
101
        accesskey => 'enter',
102
      ],
103
      action => [
104
        t8('Back'),
105
        call => [ 'kivi.history_back' ],
106
      ],
107
    );
108
  }
109
}
110

  
111
sub setup_empty_result_set_action_bar {
112
  my ($self) = @_;
113

  
114
  for my $bar ($::request->layout->get('actionbar')) {
115
    $bar->add(
116
      action => [
117
        t8('Back'),
118
        call => [ 'kivi.history_back' ],
119
      ],
120
    );
121
  }
122
}
123

  
124
sub prepare_query {
125
  my ($self) = @_;
126

  
127
  my $sql_query = $self->query->sql_query;
128
  my @values;
129

  
130
  my %values_by_name;
131

  
132
  foreach my $parameter (@{ $self->query->parameters // [] }) {
133
    my $value                           = ($::form->{parameters} // {})->{ $parameter->name };
134
    $values_by_name{ $parameter->name } = $parameter->parameter_type eq 'number' ? $::form->parse_amount(\%::myconfig, $value) : $value;
135
  }
136

  
137
  while ($sql_query =~ m{<\%(.+?)\%>}) {
138
    push @values, $values_by_name{$1};
139
    substr($sql_query, $-[0], $+[0] - $-[0], '?');
140
  }
141

  
142
  return ($sql_query, @values);
143
}
144

  
145
sub execute_query {
146
  my ($self) = @_;
147

  
148
  my ($sql_query, @values) = $self->prepare_query;
149
  my $sth                  = $self->query->db->dbh->prepare($sql_query) || $::form->dberror;
150
  $sth->execute(@values)                                                || $::form->dberror;
151

  
152
  my @names = @{ $sth->{NAME} };
153
  my @types = @{ $sth->{TYPE} };
154
  my @data  = @{ $sth->fetchall_arrayref };
155

  
156
  $sth->finish;
157

  
158
  foreach my $row (@data) {
159
    foreach my $col (0..$#types) {
160
      my $type = $types[$col];
161

  
162
      if ($type == SQL_NUMERIC) {
163
        $row->[$col] = $::form->format_amount(\%::myconfig, $row->[$col]);
164
      }
165
    }
166
  }
167

  
168
  $self->rows([
169
    \@names,
170
    @data,
171
  ]);
172
}
173

  
174
sub export_as_csv {
175
  my ($self) = @_;
176

  
177
  my $csv = Text::CSV_XS->new({
178
    binary   => 1,
179
    sep_char => ';',
180
    eol      => "\n",
181
  });
182

  
183
  my ($file_handle, $file_name) = File::Temp::tempfile;
184

  
185
  binmode $file_handle, ":encoding(utf8)";
186

  
187
  $csv->print($file_handle, $_) for @{ $self->rows };
188

  
189
  $file_handle->close;
190

  
191
  my $report_name =  $self->query->name;
192
  $report_name    =~ s{[^[:word:]]+}{_}ig;
193
  $report_name   .=  strftime('_%Y-%m-%d_%H-%M-%S.csv', localtime());
194

  
195
  $self->send_file(
196
    $file_name,
197
    content_type => 'text/csv',
198
    name         => $report_name,
199
  );
200
}
201

  
202
1;
SL/Controller/CustomDataExportDesigner.pm
1
package SL::Controller::CustomDataExportDesigner;
2

  
3
use strict;
4
use utf8;
5

  
6
use parent qw(SL::Controller::Base);
7

  
8
use List::UtilsBy qw(sort_by);
9

  
10
use SL::DB::CustomDataExportQuery;
11
use SL::Helper::Flash qw(flash_later);
12
use SL::Locale::String qw(t8);
13

  
14
use Rose::Object::MakeMethods::Generic
15
(
16
  'scalar --get_set_init' => [ qw(query queries access_rights) ],
17
);
18

  
19
__PACKAGE__->run_before('check_auth');
20
__PACKAGE__->run_before('setup_javascripts');
21

  
22
#
23
# actions
24
#
25

  
26
sub action_list {
27
  my ($self) = @_;
28

  
29
  $self->setup_list_action_bar;
30
  $self->render('custom_data_export_designer/list', title => $::locale->text('Design custom data export queries'));
31
}
32

  
33
sub action_edit {
34
  my ($self) = @_;
35

  
36
  my $title = $self->query->id ? t8('Edit custom data export query') : t8('Add custom data export query');
37

  
38
  $self->setup_edit_action_bar;
39
  $self->render('custom_data_export_designer/edit', title => $title);
40
}
41

  
42
sub action_edit_parameters {
43
  my ($self) = @_;
44

  
45
  my $title     = $self->query->id ? t8('Edit custom data export query') : t8('Add custom data export query');
46
  my @parameters = $self->gather_query_data;
47

  
48
  $self->setup_edit_parameters_action_bar;
49
  $self->render('custom_data_export_designer/edit_parameters', title => $title, PARAMETERS => \@parameters);
50
}
51

  
52
sub action_save {
53
  my ($self) = @_;
54

  
55
  my @parameters = $self->gather_query_data;
56

  
57
  $self->query->parameters(\@parameters);
58

  
59
  $self->query->save;
60

  
61
  flash_later('info', t8('The custom data export has been saved.'));
62

  
63
  $self->redirect_to($self->url_for(action => 'list'));
64
}
65

  
66
sub action_delete {
67
  my ($self) = @_;
68

  
69
  $self->query->delete;
70

  
71
  flash_later('info', t8('The custom data export has been deleted.'));
72

  
73
  $self->redirect_to($self->url_for(action => 'list'));
74
}
75

  
76
#
77
# filters
78
#
79

  
80
sub check_auth {
81
  $::auth->assert('custom_data_export_designer');
82
}
83

  
84
sub setup_javascripts {
85
  $::request->layout->add_javascripts('kivi.Validator.js');
86
}
87

  
88
#
89
# helpers
90
#
91

  
92
sub init_query   { $::form->{id} ? SL::DB::CustomDataExportQuery->new(id => $::form->{id})->load : SL::DB::CustomDataExportQuery->new }
93
sub init_queries { scalar SL::DB::Manager::CustomDataExportQuery->get_all_sorted }
94

  
95
sub init_access_rights {
96
  my @rights = ([ '', t8('Available to all users') ]);
97
  my $category;
98

  
99
  foreach my $right ($::auth->all_rights_full) {
100
    # name, description, category
101

  
102
    if ($right->[2]) {
103
      $category = t8($right->[1]);
104
    } elsif ($category) {
105
      push @rights, [ $right->[0], sprintf('%s → %s [%s]', $category, t8($right->[1]), $right->[0]) ];
106
    }
107
  }
108

  
109
  return \@rights;
110
}
111

  
112
sub setup_list_action_bar {
113
  my ($self) = @_;
114

  
115
  for my $bar ($::request->layout->get('actionbar')) {
116
    $bar->add(
117
      link => [
118
        t8('Add'),
119
        link      => $self->url_for(action => 'edit'),
120
        accesskey => 'enter',
121
      ],
122
    );
123
  }
124
}
125

  
126
sub setup_edit_action_bar {
127
  my ($self) = @_;
128

  
129
  for my $bar ($::request->layout->get('actionbar')) {
130
    $bar->add(
131
      action => [
132
        t8('Continue'),
133
        submit    => [ '#form', { action => 'CustomDataExportDesigner/edit_parameters' } ],
134
        checks    => [ 'kivi.validate_form' ],
135
        accesskey => 'enter',
136
      ],
137
      action => [
138
        t8('Delete'),
139
        submit   => [ '#form', { action => 'CustomDataExportDesigner/delete' } ],
140
        confirm  => t8('Do you really want to delete this object?'),
141
        disabled => !$self->query->id ? t8('This object has not been saved yet.')
142
                  :                      undef,
143
      ],
144
      action => [
145
        t8('Back'),
146
        call => [ 'kivi.history_back' ],
147
      ],
148
    );
149
  }
150
}
151

  
152
sub setup_edit_parameters_action_bar {
153
  my ($self) = @_;
154

  
155
  for my $bar ($::request->layout->get('actionbar')) {
156
    $bar->add(
157
      action => [
158
        t8('Save'),
159
        submit    => [ '#form', { action => 'CustomDataExportDesigner/save' } ],
160
        checks    => [ 'kivi.validate_form' ],
161
        accesskey => 'enter',
162
      ],
163
      action => [
164
        t8('Back'),
165
        call => [ 'kivi.history_back' ],
166
      ],
167
    );
168
  }
169
}
170

  
171
sub gather_query_data {
172
  my ($self) = @_;
173

  
174
  $self->query->$_($::form->{query}->{$_}) for qw(name description sql_query access_right);
175
  return $self->gather_query_parameters;
176
}
177

  
178
sub gather_query_parameters {
179
  my ($self) = @_;
180

  
181
  my %used_parameter_names  = map  { ($_ => 1) }                       $self->query->used_parameter_names;
182
  my @existing_parameters   = grep { $used_parameter_names{$_->name} } @{ $self->query->parameters // [] };
183
  my %parameters_by_name    = map  { ($_->name => $_) }                @existing_parameters;
184
  $parameters_by_name{$_} //= SL::DB::CustomDataExportQueryParameter->new(name => $_, parameter_type => 'text') for keys %used_parameter_names;
185

  
186
  foreach my $parameter_data (@{ $::form->{parameters} // [] }) {
187
    my $parameter_obj = $parameters_by_name{ $parameter_data->{name} };
188
    next unless $parameter_obj;
189

  
190
    $parameter_obj->$_($parameter_data->{$_}) for qw(parameter_type description);
191
  }
192

  
193
  return sort_by { lc $_->name } values %parameters_by_name;
194
}
195

  
196
1;
SL/DB/CustomDataExportQuery.pm
1
package SL::DB::CustomDataExportQuery;
2

  
3
use strict;
4

  
5
use SL::DB::MetaSetup::CustomDataExportQuery;
6
use SL::DB::Manager::CustomDataExportQuery;
7

  
8
__PACKAGE__->meta->add_relationship(
9
  parameters => {
10
    type       => 'one to many',
11
    class      => 'SL::DB::CustomDataExportQueryParameter',
12
    column_map => { id => 'query_id' },
13
  },
14
);
15

  
16
__PACKAGE__->meta->initialize;
17

  
18
sub used_parameter_names {
19
  my ($self) = @_;
20

  
21
  my %parameters;
22

  
23
  my $sql_query   = $self->sql_query // '';
24
  $parameters{$1} = 1 while $sql_query =~ m{<\%(.+?)\%>}g;
25

  
26
  return sort keys %parameters;
27
}
28

  
29
1;
SL/DB/CustomDataExportQueryParameter.pm
1
# This file has been auto-generated only because it didn't exist.
2
# Feel free to modify it at will; it will not be overwritten automatically.
3

  
4
package SL::DB::CustomDataExportQueryParameter;
5

  
6
use strict;
7

  
8
use SL::DB::MetaSetup::CustomDataExportQueryParameter;
9
use SL::DB::Manager::CustomDataExportQueryParameter;
10

  
11
__PACKAGE__->meta->initialize;
12

  
13
1;
SL/DB/Helper/ALL.pm
32 32
use SL::DB::CsvImportReportRow;
33 33
use SL::DB::CsvImportReportStatus;
34 34
use SL::DB::Currency;
35
use SL::DB::CustomDataExportQuery;
36
use SL::DB::CustomDataExportQueryParameter;
35 37
use SL::DB::CustomVariable;
36 38
use SL::DB::CustomVariableConfig;
37 39
use SL::DB::CustomVariableConfigPartsgroup;
SL/DB/Helper/Mappings.pm
117 117
  csv_import_report_rows         => 'csv_import_report_row',
118 118
  csv_import_report_status       => 'csv_import_report_status',
119 119
  currencies                     => 'currency',
120
  custom_data_export_queries     => 'CustomDataExportQuery',
121
  custom_data_export_query_parameters => 'CustomDataExportQueryParameter',
120 122
  custom_variable_config_partsgroups => 'custom_variable_config_partsgroup',
121 123
  custom_variable_configs        => 'custom_variable_config',
122 124
  custom_variables               => 'custom_variable',
SL/DB/Manager/CustomDataExportQuery.pm
1
package SL::DB::Manager::CustomDataExportQuery;
2

  
3
use strict;
4

  
5
use parent qw(SL::DB::Helper::Manager);
6

  
7
use SL::DB::Helper::Sorted;
8

  
9
sub object_class { 'SL::DB::CustomDataExportQuery' }
10

  
11
__PACKAGE__->make_manager_methods;
12

  
13
sub _sort_spec {
14
  return ( default => [ 'name', 1 ],
15
           name    => 'lower(custom_data_export_queries.name)',
16
           columns => { SIMPLE => 'ALL' });
17
}
18

  
19
1;
SL/DB/Manager/CustomDataExportQueryParameter.pm
1
# This file has been auto-generated only because it didn't exist.
2
# Feel free to modify it at will; it will not be overwritten automatically.
3

  
4
package SL::DB::Manager::CustomDataExportQueryParameter;
5

  
6
use strict;
7

  
8
use parent qw(SL::DB::Helper::Manager);
9

  
10
sub object_class { 'SL::DB::CustomDataExportQueryParameter' }
11

  
12
__PACKAGE__->make_manager_methods;
13

  
14
1;
SL/DB/MetaSetup/CustomDataExportQuery.pm
1
# This file has been auto-generated. Do not modify it; it will be overwritten
2
# by rose_auto_create_model.pl automatically.
3
package SL::DB::CustomDataExportQuery;
4

  
5
use strict;
6

  
7
use parent qw(SL::DB::Object);
8

  
9
__PACKAGE__->meta->table('custom_data_export_queries');
10

  
11
__PACKAGE__->meta->columns(
12
  access_right => { type => 'text' },
13
  description  => { type => 'text', not_null => 1 },
14
  id           => { type => 'serial', not_null => 1 },
15
  itime        => { type => 'timestamp', default => 'now()', not_null => 1 },
16
  mtime        => { type => 'timestamp', default => 'now()', not_null => 1 },
17
  name         => { type => 'text', not_null => 1 },
18
  sql_query    => { type => 'text', not_null => 1 },
19
);
20

  
21
__PACKAGE__->meta->primary_key_columns([ 'id' ]);
22

  
23
__PACKAGE__->meta->allow_inline_column_values(1);
24

  
25
1;
26
;
SL/DB/MetaSetup/CustomDataExportQueryParameter.pm
1
# This file has been auto-generated. Do not modify it; it will be overwritten
2
# by rose_auto_create_model.pl automatically.
3
package SL::DB::CustomDataExportQueryParameter;
4

  
5
use strict;
6

  
7
use parent qw(SL::DB::Object);
8

  
9
__PACKAGE__->meta->table('custom_data_export_query_parameters');
10

  
11
__PACKAGE__->meta->columns(
12
  description    => { type => 'text' },
13
  id             => { type => 'serial', not_null => 1 },
14
  itime          => { type => 'timestamp', default => 'now()', not_null => 1 },
15
  mtime          => { type => 'timestamp', default => 'now()', not_null => 1 },
16
  name           => { type => 'text', not_null => 1 },
17
  parameter_type => { type => 'enum', check_in => [ 'text', 'number', 'date', 'timestamp' ], db_type => 'custom_data_export_query_parameter_type_enum', not_null => 1 },
18
  query_id       => { type => 'integer', not_null => 1 },
19
);
20

  
21
__PACKAGE__->meta->primary_key_columns([ 'id' ]);
22

  
23
__PACKAGE__->meta->allow_inline_column_values(1);
24

  
25
__PACKAGE__->meta->foreign_keys(
26
  query => {
27
    class       => 'SL::DB::CustomDataExportQuery',
28
    key_columns => { query_id => 'id' },
29
  },
30
);
31

  
32
1;
33
;
locale/de/all
196 196
  'Add booking group'           => 'Buchungsgruppe erfassen',
197 197
  'Add business'                => 'Kunden-/Lieferantentyp hinzufügen',
198 198
  'Add complexity'              => 'Komplexitätsgrad hinzufügen',
199
  'Add custom data export query' => 'Benutzerdefinierte Datenexport-Abfrage erfassen',
199 200
  'Add custom variable'         => 'Benutzerdefinierte Variable erfassen',
200 201
  'Add department'              => 'Abteilung hinzufügen',
201 202
  'Add empty line (csv_import)' => 'Leere Zeile einfügen',
......
367 368
  'Available Prices'            => 'Mögliche Preise',
368 369
  'Available identity fields'   => 'Verfügbare Felder',
369 370
  'Available qty'               => 'Lagerbestand',
371
  'Available to all users'      => 'Für alle BenutzerInnen verfügbar',
370 372
  'BALANCE SHEET'               => 'BILANZ',
371 373
  'BB Balance'                  => 'Saldo Bank',
372 374
  'BIC'                         => 'BIC',
......
788 790
  'Currently #1 delivery orders can be converted into invoices and printed.' => 'Momentan können #1 Lieferscheine in Rechnungen umgewandelt werden.',
789 791
  'Custom CSV format'           => 'Eigenes CSV-Format',
790 792
  'Custom Variables'            => 'Benutzerdefinierte Variablen',
793
  'Custom data export'          => 'Benutzerdefinierter Datenexport',
791 794
  'Custom shipto'               => 'Individuelle Lieferadresse',
792 795
  'Custom variables for module' => 'Benutzerdefinierte Variablen für Modul',
793 796
  'Customer'                    => 'Kunde',
......
975 978
  'Description (translation for #1)' => 'Beschreibung (Übersetzung für #1)',
976 979
  'Description missing!'        => 'Beschreibung fehlt.',
977 980
  'Description of #1'           => 'Beschreibung von #1',
981
  'Design custom data export queries' => 'Benutzerdefinierte Datenexport-Abfragen designen',
978 982
  'Destination BIC'             => 'Ziel-BIC',
979 983
  'Destination IBAN'            => 'Ziel-IBAN',
980 984
  'Destination bin'             => 'Ziellagerplatz',
......
1150 1154
  'Edit booking group'          => 'Buchungsgruppe bearbeiten',
1151 1155
  'Edit business'               => 'Kunden-/Lieferantentyp bearbeiten',
1152 1156
  'Edit complexity'             => 'Komplexitätsgrad bearbeiten',
1157
  'Edit custom data export query' => 'Benutzerdefinierte Datenexport-Abfrage bearbeiten',
1153 1158
  'Edit custom shipto'          => 'Individuelle Lieferadresse bearbeiten',
1154 1159
  'Edit custom variable'        => 'Benutzerdefinierte Variable bearbeiten',
1155 1160
  'Edit delivery term'          => 'Lieferbedingungen bearbeiten',
......
1314 1319
  'Exchangerate for payment missing!' => 'Es fehlt der Wechselkurs für die Bezahlung!',
1315 1320
  'Exchangerate missing!'       => 'Es fehlt der Wechselkurs!',
1316 1321
  'Execute'                     => 'Ausführen',
1322
  'Execute a custom data export query' => 'Benutzerdefinierte Datenexport-Abfrage ausführen',
1323
  'Execute custom data export \'#1\'' => 'Benutzerdefinierter Datenexport »#1« ausführen',
1317 1324
  'Executed'                    => 'Ausgeführt',
1318 1325
  'Execution date'              => 'Ausführungsdatum',
1319 1326
  'Execution date from'         => 'Ausführungsdatum von',
......
1941 1948
  'No clients have been created yet.' => 'Es wurden noch keine Mandanten angelegt.',
1942 1949
  'No contact selected to delete' => 'Keine Ansprechperson zum Löschen ausgewählt',
1943 1950
  'No contra account selected!' => 'Kein Gegenkonto ausgewählt!',
1951
  'No custom data exports have been created yet.' => 'Es wurden noch keine benutzerdefinierten Datenexporte angelegt.',
1944 1952
  'No customer has been selected yet.' => 'Es wurde noch kein Kunde ausgewählt.',
1945 1953
  'No data was found.'          => 'Es wurden keine Daten gefunden.',
1946 1954
  'No default currency'         => 'Keine Standardwährung',
......
2011 2019
  'Not done yet'                => 'Noch nicht fertig',
2012 2020
  'Not obsolete'                => 'Gültig',
2013 2021
  'Note'                        => 'Hinweis',
2022
  'Note that parameter names must not be quoted.' => 'Beachten Sie, dass Parameternamen nicht in Anführungszeichen stehen dürfen.',
2014 2023
  'Note: Taxkeys must have a "valid from" date, and will not behave correctly without.' => 'Hinweis: Steuerschlüssel sind fehlerhaft ohne "Gültig ab" Datum',
2015 2024
  'Note: the object is already in use. Therefore some values cannot be changed.' => 'Anmerkung: das Objekt ist bereits in Benutzung. Einige Werte können daher nicht geändert werden.',
2016 2025
  'Notes'                       => 'Bemerkungen',
......
2059 2068
  'On'                          => 'An',
2060 2069
  'On Hand'                     => 'Auf Lager',
2061 2070
  'On Order'                    => 'Ist bestellt',
2071
  'On the next page the type of all variables can be set.' => 'Auf der folgenden Seite können die Typen aller Variablen gesetzt werden.',
2062 2072
  'One OB-transaction'          => 'Eine EB-Buchung',
2063 2073
  'One SB-transaction'          => 'Eine SB-Buchung',
2064 2074
  'One of the columns "qty" or "target_qty" must be given. If "target_qty" is given, the quantity to transfer for each transfer will be calculate, so that the quantity for this part, warehouse and bin will result in the given "target_qty" after each transfer.' => 'Eine der Spalten "qty" oder "target_qty" muss angegeben werden. Wird "target_qty" angegeben, so wird die zu bewegende Menge für jede Lagerbewegung so berechnet, dass die Lagermenge für diesen Artikel, Lager und Lagerplatz nach jeder Lagerbewegung der angegebenen Zielmenge entspricht.',
......
2428 2438
  'Quarter'                     => 'Quartal',
2429 2439
  'Quarterly'                   => 'quartalsweise',
2430 2440
  'Query Type'                  => 'Art der Abfrage',
2441
  'Query parameters'            => 'Abfrageparameter',
2431 2442
  'Queue'                       => 'Warteschlange',
2432 2443
  'Quick Search'                => 'Schnellsuche',
2433 2444
  'Quick Searches that will be shown in the header in this client' => 'Schnellsuchen, die in der Kopfzeile in diesem Mandanten gezeigt werden sollen',
......
2539 2550
  'Requests for Quotation'      => 'Preisanfragen',
2540 2551
  'Require a transaction description in purchase and sales records' => 'Vorgangsbezeichnung in Einkaufs- und Verkaufsbelegen erzwingen',
2541 2552
  'Require stock out to consider a delivery order position delivered?' => 'Muss eine Lieferscheinposition ausgelagert sein um als geliefert zu gelten?',
2553
  'Required access right'       => 'Benötigtes Zugriffsrecht',
2542 2554
  'Required by'                 => 'Lieferdatum',
2543 2555
  'Requirement Spec Status'     => 'Pflichtenheftstatus',
2544 2556
  'Requirement Spec Statuses'   => 'Pflichtenheftstatus',
......
2596 2608
  'SEPA message ID'             => 'SEPA-Nachrichten-ID',
2597 2609
  'SEPA message IDs'            => 'SEPA-Nachrichten-IDs',
2598 2610
  'SEPA strings'                => 'SEPA-Überweisungen',
2611
  'SQL query'                   => 'SQL-Abfrage',
2599 2612
  'SWIFT MT940 format'          => 'SWIFT-MT940-Format',
2600 2613
  'Saldo'                       => 'Saldo',
2601 2614
  'Saldo Credit'                => 'Saldo Haben',
......
2911 2924
  'Status'                      => 'Status',
2912 2925
  'Status Shoptransfer'         => 'Status Shoptransfer',
2913 2926
  'Status Shopupload'           => 'Status Shopupload',
2927
  'Step #1/#2'                  => 'Schritt #1/#2',
2914 2928
  'Step 1 -- limit number of delivery orders to process' => 'Schritt 1 -- Anzahl zu verarbeitender Lieferscheine begrenzen',
2915 2929
  'Step 2'                      => 'Schritt 2',
2916 2930
  'Step 2 -- Watch status'      => 'Schritt 2 -- Status beobachten',
......
3026 3040
  'Templates'                   => 'Vorlagen',
3027 3041
  'Terms missing in row '       => '+Tage fehlen in Zeile ',
3028 3042
  'Test database connectivity'  => 'Datenbankverbindung testen',
3043
  'Text'                        => 'Text',
3029 3044
  'Text block actions'          => 'Textblockaktionen',
3030 3045
  'Text block picture actions'  => 'Aktionen für Textblockbilder',
3031 3046
  'Text blocks'                 => 'Textblöcke',
......
3059 3074
  'The PDF has been printed'    => 'Das PDF-Dokument wurde gedruckt.',
3060 3075
  'The SEPA export has been created.' => 'Der SEPA-Export wurde erstellt',
3061 3076
  'The SEPA strings have been saved.' => 'Die bei SEPA-Überweisungen verwendeten Begriffe wurden gespeichert.',
3077
  'The SQL query can be parameterized with variables named as follows: <%name%>.' => 'Die SQL-Abfrage kann mittels Variablen wie folgt parametrisiert werden: <%Variablenname%>.',
3078
  'The SQL query does not contain any parameter that need to be configured.' => 'Die SQL-Abfrage enthält keine Parameter, die angegeben werden müssten.',
3062 3079
  'The URL is missing.'         => 'URL fehlt',
3063 3080
  'The WebDAV feature has been used.' => 'Das WebDAV-Feature wurde benutzt.',
3064 3081
  'The abbreviation is missing.' => 'Abkürzung fehlt',
......
3124 3141
  'The contact person attribute "birthday" is converted from a free-form text field into a date field.' => 'Das Kontaktpersonenfeld "Geburtstag" wird von einem freien Textfeld auf ein Datumsfeld umgestellt.',
3125 3142
  'The creation of the authentication database failed:' => 'Das Anlegen der Authentifizierungsdatenbank schlug fehl:',
3126 3143
  'The credentials (username & password) for connecting database are wrong.' => 'Die Daten (Benutzername & Passwort) für das Login zur Datenbank sind falsch.',
3144
  'The custom data export has been deleted.' => 'Der benutzerdefinierte Datenexport wurde gelöscht.',
3145
  'The custom data export has been saved.' => 'Der benutzerdefinierte Datenexport wurde gespeichert.',
3127 3146
  'The custom variable has been created.' => 'Die benutzerdefinierte Variable wurde erfasst.',
3128 3147
  'The custom variable has been deleted.' => 'Die benutzerdefinierte Variable wurde gelöscht.',
3129 3148
  'The custom variable has been saved.' => 'Die benutzerdefinierte Variable wurde gespeichert.',
......
3262 3281
  'The project link has been updated.' => 'Die Projektverknüpfung wurde aktualisiert.',
3263 3282
  'The project number is already in use.' => 'Die Projektnummer wird bereits verwendet.',
3264 3283
  'The project number is missing.' => 'Die Projektnummer fehlt.',
3284
  'The query did not return any data.' => 'Die Abfrage lieferte keine Daten',
3265 3285
  'The receivables chart isn\'t a valid chart.' => 'Das Forderungskonto ist kein gültiges Konto',
3266 3286
  'The recipient, subject or body is missing.' => 'Der Empfäger, der Betreff oder der Text ist leer.',
3267 3287
  'The record template \'#1\' has been loaded.' => 'Die Belegvorlage »#1« wurde geladen.',
......
3789 3809
  'You cannot create an invoice for delivery orders from different vendors.' => 'Sie können keine Rechnung aus Lieferscheinen von verschiedenen Lieferanten erstellen.',
3790 3810
  'You cannot modify individual assigments from additional articles to line items.' => 'Eine individuelle Zuordnung der zusätzlichen Artikel zu Positionen kann nicht vorgenommen werden.',
3791 3811
  'You cannot paste function blocks or sub function blocks if there is no section.' => 'Sie können keine Funktionsblöcke oder Unterfunktionsblöcke einfügen, wenn es noch keinen Abschnitt gibt.',
3812
  'You do not have access to any custom data export.' => 'Sie haben auf keine benutzerdefinierten Datenexporte Zugriff.',
3792 3813
  'You do not have permission to access this entry.' => 'Sie verfügen nicht über die Berechtigung, auf diesen Eintrag zuzugreifen.',
3793 3814
  'You do not have the permissions to access this function.' => 'Sie verfügen nicht über die notwendigen Rechte, um auf diese Funktion zuzugreifen.',
3794 3815
  'You don\'t have the rights to edit this customer.' => 'Sie verfügen nicht über die erforderlichen Rechte, um diesen Kunden zu bearbeiten.',
menus/user/10-custom-data-export.yaml
1
---
2
- parent: reports
3
  id: custom_data_export
4
  name: Custom data export
5
  order: 9000
6
  params:
7
    action: CustomDataExport/list
8
- parent: system
9
  id: custom_data_export_designer
10
  name: Custom data export
11
  order: 2250
12
  access: custom_data_export_designer
13
  params:
14
    action: CustomDataExportDesigner/list
sql/Pg-upgrade2-auth/custom_data_export_rights.pl
1
# @tag: custom_data_export_rights
2
# @description: Rechte für benutzerdefinierten Datenexport
3
# @depends: release_3_5_0
4
package SL::DBUpgrade2::Auth::custom_data_export_rights;
5

  
6
use strict;
7
use utf8;
8

  
9
use parent qw(SL::DBUpgrade2::Base);
10

  
11
sub run {
12
  my ($self) = @_;
13
  my $right  = 'custom_data_export_designer';
14

  
15
  $self->db_query("INSERT INTO auth.master_rights (position, name, description) VALUES (4275, '${right}', 'Custom data export')");
16

  
17
  my $groups = $::auth->read_groups;
18

  
19
  foreach my $group (grep { $_->{rights}->{admin} } values %{$groups}) {
20
    $group->{rights}->{$right} = 1;
21
    $::auth->save_group($group);
22
  }
23

  
24
  return 1;
25
}
26

  
27
1;
sql/Pg-upgrade2/custom_data_export.sql
1
-- @tag: custom_data_export
2
-- @description: Benutzerdefinierter Datenexport
3
-- @depends: release_3_5_0
4
CREATE TYPE custom_data_export_query_parameter_type_enum AS ENUM ('text', 'number', 'date', 'timestamp');
5

  
6
CREATE TABLE custom_data_export_queries (
7
  id           SERIAL,
8
  name         TEXT      NOT NULL,
9
  description  TEXT      NOT NULL,
10
  sql_query    TEXT      NOT NULL,
11
  access_right TEXT,
12
  itime        TIMESTAMP NOT NULL DEFAULT now(),
13
  mtime        TIMESTAMP NOT NULL DEFAULT now(),
14

  
15
  PRIMARY KEY (id)
16
);
17

  
18
CREATE TABLE custom_data_export_query_parameters (
19
  id             SERIAL,
20
  query_id       INTEGER NOT NULL,
21
  name           TEXT NOT NULL,
22
  description    TEXT,
23
  parameter_type custom_data_export_query_parameter_type_enum NOT NULL,
24
  itime          TIMESTAMP NOT NULL DEFAULT now(),
25
  mtime          TIMESTAMP NOT NULL DEFAULT now(),
26

  
27
  PRIMARY KEY (id),
28
  FOREIGN KEY (query_id) REFERENCES custom_data_export_queries (id) ON DELETE CASCADE
29
);
30

  
31
CREATE TRIGGER mtime_custom_data_export_queries
32
BEFORE UPDATE ON custom_data_export_queries
33
FOR EACH ROW EXECUTE PROCEDURE set_mtime();
34

  
35
CREATE TRIGGER mtime_custom_data_export_query_parameters
36
BEFORE UPDATE ON custom_data_export_query_parameters
37
FOR EACH ROW EXECUTE PROCEDURE set_mtime();
templates/webpages/custom_data_export/empty_result_set.html
1
[% USE HTML %][% USE L %][% USE LxERP %]
2

  
3
<h1>[% FORM.title %]</h1>
4

  
5
[%- INCLUDE 'common/flash.html' %]
6

  
7
<p>
8
 [% LxERP.t8("The query did not return any data.") %]
9
</p>
templates/webpages/custom_data_export/export.html
1
[% USE HTML %][% USE L %][% USE LxERP %]
2

  
3
<h1>[% FORM.title %]</h1>
4

  
5
[%- INCLUDE 'common/flash.html' %]
6

  
7
<form method="post" action="controller.pl" id="form">
8
 [% L.hidden_tag("id", SELF.query.id) %]
9
 [% L.hidden_tag("format", "csv") %]
10

  
11
 [% IF !SELF.parameters.size %]
12
  <p>
13
   [% LxERP.t8("The SQL query does not contain any parameter that need to be configured.") %]
14
  </p>
15

  
16
 [% ELSE %]
17

  
18
 <table>
19
  <thead>
20
   <tr class="listheading">
21
    <th>[% LxERP.t8("Variable Name") %]</th>
22
    <th>[% LxERP.t8("Type") %]</th>
23
    <th>[% LxERP.t8("Value") %]</th>
24
    <th>[% LxERP.t8("Description") %]</th>
25
   </tr>
26
  </thead>
27

  
28
  <tbody>
29
   [% FOREACH parameter = SELF.parameters %]
30
    <tr class="listrow">
31
     <td>
32
      [% HTML.escape(parameter.name) %]
33
     </td>
34

  
35
     [% IF parameter.parameter_type == "number" %]
36
      <td>[% LxERP.t8("Number") %]</td>
37
      <td>[% L.input_tag("parameters." _ parameter.name, "", style="width: 300px", "data-validate"="required") %]</td>
38

  
39
     [% ELSIF parameter.parameter_type == "date" %]
40
      <td>[% LxERP.t8("Date") %]</td>
41
      <td>[% L.date_tag("parameters." _ parameter.name, SELF.today.to_kivitendo, style="width: 300px", "data-validate"="required") %]</td>
42

  
43
     [% ELSE %]
44
      <td>[% LxERP.t8("Text") %]</td>
45
      <td>[% L.input_tag("parameters." _ parameter.name, "", style="width: 300px", "data-validate"="required") %]</td>
46
     [% END %]
47

  
48
     <td>[% HTML.escape(parameter.description) %]</td>
49
    </tr>
50
   [% END %]
51
  </tbody>
52
 [% END %]
53
</form>
templates/webpages/custom_data_export/list.html
1
[% USE HTML %][% USE L %][% USE LxERP %]
2

  
3
<h1>[% FORM.title %]</h1>
4

  
5
[%- INCLUDE 'common/flash.html' %]
6

  
7
[% IF !SELF.queries.size %]
8
 <p>
9
  [%- LxERP.t8("You do not have access to any custom data export.") %]
10
 </p>
11

  
12
[%- ELSE %]
13
 <table width="100%">
14
  <thead>
15
   <tr class="listheading">
16
    <th>[% LxERP.t8("Name") %]</th>
17
    <th>[% LxERP.t8("Description") %]</th>
18
   </tr>
19
  </thead>
20

  
21
  <tbody>
22
   [%- FOREACH query = SELF.queries %]
23
    <tr class="listrow">
24
     <td>[% L.link(SELF.url_for(action="export", id=query.id), query.name) %]</td>
25
     <td>[% IF query.description %][% L.link(SELF.url_for(action="export", id=query.id), query.description) %][% END %]</td>
26
    </tr>
27
   [%- END %]
28
  </tbody>
29
 </table>
30
[%- END %]
templates/webpages/custom_data_export_designer/edit.html
1
[% USE HTML %][% USE L %][% USE LxERP %]
2

  
3
<h1>[% FORM.title %] — [% LxERP.t8("Step #1/#2", 1, 2) %] — [% LxERP.t8("Basic Data") %]</h1>
4

  
5
[%- INCLUDE 'common/flash.html' %]
6

  
7
<form method="post" action="controller.pl" id="form">
8
 [% L.hidden_tag("id", SELF.query.id) %]
9

  
10
 <table>
11
  <tr>
12
   <th align="right">[%- LxERP.t8("Name") %]</th>
13
   <td>[% L.input_tag("query.name", SELF.query.name, style="width: 800px", "data-validate"="required") %]</td>
14
  </tr>
15

  
16
  <tr>
17
   <th align="right">[%- LxERP.t8("Required access right") %]</th>
18
   <td>[% L.select_tag("query.access_right", SELF.access_rights, default=SELF.query.access_right, style="width: 800px") %]</td>
19
  </tr>
20

  
21
  <tr>
22
   <th valign="top" align="right">[%- LxERP.t8("Description") %]</th>
23
   <td valign="top">[% L.textarea_tag("query.description", SELF.query.description, rows=5, style="width: 800px") %]</td>
24
  </tr>
25

  
26
  <tr>
27
   <th valign="top" align="right">[%- LxERP.t8("SQL query") %]</th>
28
   <td valign="top">[% L.textarea_tag("query.sql_query", SELF.query.sql_query, rows=20, style="width: 800px", "data-validate"="required") %]</td>
29
  </tr>
30
 </table>
31
</form>
32

  
33
<p>
34
 [% LxERP.t8("The SQL query can be parameterized with variables named as follows: <%name%>.") %]
35
 [% LxERP.t8("On the next page the type of all variables can be set.") %]
36
 [% LxERP.t8("Note that parameter names must not be quoted.") %]
37
 [% LxERP.t8("Example") %]:
38
</p>
39

  
40
<pre>
41
SELECT extract(YEAR FROM oe.transdate) AS &quot;Jahr&quot;, SUM(oe.amount) AS &quot;Angebotssumme&quot;
42
FROM oe
43
LEFT JOIN employee ON (oe.employee_id = employee.id)
44
WHERE (oe.customer_id IS NOT NULL)
45
  AND COALESCE(oe.quotation, FALSE)
46
  AND (employee.login = &lt;%Benutzer-Login%&gt;)
47
GROUP BY &quot;Jahr&quot;
48
ORDER BY &quot;Jahr&quot;
49
</pre>
templates/webpages/custom_data_export_designer/edit_parameters.html
1
[% USE HTML %][% USE L %][% USE LxERP %]
2

  
3
<h1>[% FORM.title %] — [% LxERP.t8("Step #1/#2", 2, 2) %] — [% LxERP.t8("Query parameters") %]</h1>
4

  
5
[%- INCLUDE 'common/flash.html' %]
6

  
7
<form method="post" action="controller.pl" id="form">
8
 [% L.hidden_tag("id", SELF.query.id) %]
9
 [% L.hidden_tag("query.name", SELF.query.name) %]
10
 [% L.hidden_tag("query.access_right", SELF.query.access_right) %]
11
 [% L.hidden_tag("query.description", SELF.query.description) %]
12
 [% L.hidden_tag("query.sql_query", SELF.query.sql_query) %]
13

  
14
 [% IF !PARAMETERS.size %]
15
  <p>
16
   [% LxERP.t8("The SQL query does not contain any parameter that need to be configured.") %]
17
  </p>
18

  
19
 [% ELSE %]
20

  
21
 <table>
22
  <thead>
23
   <tr class="listheading">
24
    <th>[% LxERP.t8("Variable Name") %]</th>
25
    <th>[% LxERP.t8("Type") %]</th>
26
    <th>[% LxERP.t8("Description") %]</th>
27
   </tr>
28
  </thead>
29

  
30
  <tbody>
31
   [% FOREACH parameter = PARAMETERS %]
32
    <tr class="listrow">
33
     <td>
34
      [% L.hidden_tag("parameters[+].name", parameter.name) %]
35
      [% HTML.escape(parameter.name) %]
36
     </td>
37
     <td>
38
      [% L.select_tag("parameters[].parameter_type", [ [ "text", LxERP.t8("Text") ], [ "number", LxERP.t8("Number") ], [ "date", LxERP.t8("Date") ] ], default=parameter.parameter_type) %]
39
     </td>
40
     <td>[% L.input_tag("parameters[].description", parameter.description, size=100) %]</td>
41
    </tr>
42
   [% END %]
43
  </tbody>
44
 [% END %]
45
</form>
templates/webpages/custom_data_export_designer/list.html
1
[% USE HTML %][% USE L %][% USE LxERP %]
2

  
3
<h1>[% FORM.title %]</h1>
4

  
5
[%- INCLUDE 'common/flash.html' %]
6

  
7
[% IF !SELF.queries.size %]
8
 <p>
9
  [%- LxERP.t8("No custom data exports have been created yet.") %]
10
 </p>
11

  
12
[%- ELSE %]
13
 <table width="100%">
14
  <thead>
15
   <tr class="listheading">
16
    <th>[% LxERP.t8("Name") %]</th>
17
    <th>[% LxERP.t8("Description") %]</th>
18
   </tr>
19
  </thead>
20

  
21
  <tbody>
22
   [%- FOREACH query = SELF.queries %]
23
    <tr class="listrow">
24
     <td>[% L.link(SELF.url_for(action="edit", id=query.id), query.name) %]</td>
25
     <td>[% IF query.description %][% L.link(SELF.url_for(action="edit", id=query.id), query.description) %][% END %]</td>
26
    </tr>
27
   [%- END %]
28
  </tbody>
29
 </table>
30
[%- END %]

Auch abrufbar als: Unified diff