Revision 6f45549b
Von Bernd Bleßmann vor fast 11 Jahren hinzugefügt
SL/Controller/CsvImport.pm | ||
---|---|---|
15 | 15 |
use SL::Controller::CsvImport::Part; |
16 | 16 |
use SL::Controller::CsvImport::Shipto; |
17 | 17 |
use SL::Controller::CsvImport::Project; |
18 |
use SL::Controller::CsvImport::Order; |
|
18 | 19 |
use SL::BackgroundJob::CsvImport; |
19 | 20 |
use SL::System::TaskServer; |
20 | 21 |
|
... | ... | |
125 | 126 |
my $file = SL::SessionFile->new($file_name, mode => '>', encoding => $self->profile->get('charset')); |
126 | 127 |
my $csv = Text::CSV_XS->new({ binary => 1, map { ( $_ => $self->profile->get($_) ) } qw(sep_char escape_char quote_char),}); |
127 | 128 |
|
128 |
$csv->print($file->fh, [ map { $_->{name} } @{ $self->displayable_columns } ]); |
|
129 |
$file->fh->print("\r\n"); |
|
130 |
$csv->print($file->fh, [ map { $_->{description} } @{ $self->displayable_columns } ]); |
|
131 |
$file->fh->print("\r\n"); |
|
129 |
if ($self->worker->is_multiplexed) { |
|
130 |
foreach my $ri (keys %{ $self->displayable_columns }) { |
|
131 |
$csv->print($file->fh, [ map { $_->{name} } @{ $self->displayable_columns->{$ri} } ]); |
|
132 |
$file->fh->print("\r\n"); |
|
133 |
} |
|
134 |
foreach my $ri (keys %{ $self->displayable_columns }) { |
|
135 |
$csv->print($file->fh, [ map { $_->{description} } @{ $self->displayable_columns->{$ri} } ]); |
|
136 |
$file->fh->print("\r\n"); |
|
137 |
} |
|
138 |
} else { |
|
139 |
$csv->print($file->fh, [ map { $_->{name} } @{ $self->displayable_columns } ]); |
|
140 |
$file->fh->print("\r\n"); |
|
141 |
$csv->print($file->fh, [ map { $_->{description} } @{ $self->displayable_columns } ]); |
|
142 |
$file->fh->print("\r\n"); |
|
143 |
} |
|
132 | 144 |
|
133 | 145 |
$file->fh->close; |
134 | 146 |
|
... | ... | |
158 | 170 |
: $page; |
159 | 171 |
$pages->{common} = [ grep { $_->{visible} } @{ SL::DB::Helper::Paginated::make_common_pages($pages->{cur}, $pages->{max}) } ]; |
160 | 172 |
|
173 |
$self->{report_numheaders} = $self->{report}->numheaders; |
|
174 |
my $first_row_header = 0; |
|
175 |
my $last_row_header = $self->{report_numheaders} - 1; |
|
176 |
my $first_row_data = $pages->{per_page} * ($pages->{cur}-1) + $self->{report_numheaders}; |
|
177 |
my $last_row_data = min($pages->{per_page} * $pages->{cur}, $num_rows) + $self->{report_numheaders} - 1; |
|
161 | 178 |
$self->{display_rows} = [ |
162 |
0, |
|
163 |
$pages->{per_page} * ($pages->{cur}-1) + 1 |
|
179 |
$first_row_header |
|
180 |
.. |
|
181 |
$last_row_header, |
|
182 |
$first_row_data |
|
164 | 183 |
.. |
165 |
min($pages->{per_page} * $pages->{cur}, $num_rows)
|
|
184 |
$last_row_data
|
|
166 | 185 |
]; |
167 | 186 |
|
168 | 187 |
my @query = ( |
169 | 188 |
csv_import_report_id => $report_id, |
170 | 189 |
or => [ |
171 |
row => 0, |
|
172 | 190 |
and => [ |
173 |
row => { gt => $pages->{per_page} * ($pages->{cur}-1) }, |
|
174 |
row => { le => $pages->{per_page} * $pages->{cur} }, |
|
191 |
row => { ge => $first_row_header }, |
|
192 |
row => { le => $last_row_header }, |
|
193 |
], |
|
194 |
and => [ |
|
195 |
row => { ge => $first_row_data }, |
|
196 |
row => { le => $last_row_data }, |
|
175 | 197 |
] |
176 | 198 |
] |
177 | 199 |
); |
... | ... | |
199 | 221 |
sub check_type { |
200 | 222 |
my ($self) = @_; |
201 | 223 |
|
202 |
die "Invalid CSV import type" if none { $_ eq $::form->{profile}->{type} } qw(parts customers_vendors addresses contacts projects); |
|
224 |
die "Invalid CSV import type" if none { $_ eq $::form->{profile}->{type} } qw(parts customers_vendors addresses contacts projects orders);
|
|
203 | 225 |
$self->type($::form->{profile}->{type}); |
204 | 226 |
} |
205 | 227 |
|
... | ... | |
242 | 264 |
: $self->type eq 'contacts' ? $::locale->text('CSV import: contacts') |
243 | 265 |
: $self->type eq 'parts' ? $::locale->text('CSV import: parts and services') |
244 | 266 |
: $self->type eq 'projects' ? $::locale->text('CSV import: projects') |
267 |
: $self->type eq 'orders' ? $::locale->text('CSV import: orders') |
|
245 | 268 |
: die; |
246 | 269 |
|
247 | 270 |
if ($self->{type} eq 'parts') { |
... | ... | |
363 | 386 |
$::form->{settings}->{sellprice_adjustment} = $::form->parse_amount(\%::myconfig, $::form->{settings}->{sellprice_adjustment}); |
364 | 387 |
} |
365 | 388 |
|
389 |
if ($self->type eq 'orders') { |
|
390 |
$::form->{settings}->{order_column} = 'Order'; |
|
391 |
$::form->{settings}->{item_column} = 'OrderItem'; |
|
392 |
} |
|
393 |
|
|
366 | 394 |
delete $::form->{profile}->{id}; |
367 | 395 |
$self->profile($existing_profile || SL::DB::CsvImportProfile->new(login => $::myconfig{login})); |
368 | 396 |
$self->profile->assign_attributes(%{ $::form->{profile} }); |
... | ... | |
389 | 417 |
sub save_report { |
390 | 418 |
my ($self, $report_id) = @_; |
391 | 419 |
|
420 |
if ($self->worker->is_multiplexed) { |
|
421 |
return $self->save_report_multi($report_id); |
|
422 |
} else { |
|
423 |
return $self->save_report_single($report_id); |
|
424 |
} |
|
425 |
} |
|
426 |
|
|
427 |
sub save_report_single { |
|
428 |
my ($self, $report_id) = @_; |
|
429 |
|
|
392 | 430 |
$self->track_progress(phase => 'building report', progress => 0); |
393 | 431 |
|
394 | 432 |
my $clone_profile = $self->profile->clone_and_reset_deep; |
... | ... | |
400 | 438 |
type => $self->type, |
401 | 439 |
file => '', |
402 | 440 |
numrows => scalar @{ $self->data }, |
441 |
numheaders => 1, |
|
403 | 442 |
); |
404 | 443 |
|
405 | 444 |
$report->save(cascade => 1) or die $report->db->error; |
... | ... | |
455 | 494 |
return $report->id; |
456 | 495 |
} |
457 | 496 |
|
497 |
sub save_report_multi { |
|
498 |
my ($self, $report_id) = @_; |
|
499 |
|
|
500 |
$self->track_progress(phase => 'building report', progress => 0); |
|
501 |
|
|
502 |
my $clone_profile = $self->profile->clone_and_reset_deep; |
|
503 |
$clone_profile->save; # weird bug. if this isn't saved before adding it to the report, it will default back to the last profile. |
|
504 |
|
|
505 |
my $report = SL::DB::CsvImportReport->new( |
|
506 |
session_id => $::auth->create_or_refresh_session, |
|
507 |
profile => $clone_profile, |
|
508 |
type => $self->type, |
|
509 |
file => '', |
|
510 |
numrows => scalar @{ $self->data }, |
|
511 |
numheaders => scalar @{ $self->worker->profile }, |
|
512 |
); |
|
513 |
|
|
514 |
$report->save(cascade => 1) or die $report->db->error; |
|
515 |
|
|
516 |
my $dbh = $::form->get_standard_dbh; |
|
517 |
$dbh->begin_work; |
|
518 |
|
|
519 |
my $query = 'INSERT INTO csv_import_report_rows (csv_import_report_id, col, row, value) VALUES (?, ?, ?, ?)'; |
|
520 |
my $query2 = 'INSERT INTO csv_import_report_status (csv_import_report_id, row, type, value) VALUES (?, ?, ?, ?)'; |
|
521 |
|
|
522 |
my $sth = $dbh->prepare($query); |
|
523 |
my $sth2 = $dbh->prepare($query2); |
|
524 |
|
|
525 |
# save headers |
|
526 |
my ($headers, $info_methods, $raw_methods, $methods); |
|
527 |
|
|
528 |
for my $i (0 .. $#{ $self->worker->profile }) { |
|
529 |
my $row_ident = $self->worker->profile->[$i]->{row_ident}; |
|
530 |
|
|
531 |
for my $i (0 .. $#{ $self->info_headers->{$row_ident}->{headers} }) { |
|
532 |
next unless $self->info_headers->{$row_ident}->{used}->{ $self->info_headers->{$row_ident}->{methods}->[$i] }; |
|
533 |
push @{ $headers->{$row_ident} }, $self->info_headers->{$row_ident}->{headers}->[$i]; |
|
534 |
push @{ $info_methods->{$row_ident} }, $self->info_headers->{$row_ident}->{methods}->[$i]; |
|
535 |
} |
|
536 |
for my $i (0 .. $#{ $self->headers->{$row_ident}->{headers} }) { |
|
537 |
next unless $self->headers->{$row_ident}->{used}->{ $self->headers->{$row_ident}->{headers}->[$i] }; |
|
538 |
push @{ $headers->{$row_ident} }, $self->headers->{$row_ident}->{headers}->[$i]; |
|
539 |
push @{ $methods->{$row_ident} }, $self->headers->{$row_ident}->{methods}->[$i]; |
|
540 |
} |
|
541 |
|
|
542 |
for my $i (0 .. $#{ $self->raw_data_headers->{$row_ident}->{headers} }) { |
|
543 |
next unless $self->raw_data_headers->{$row_ident}->{used}->{ $self->raw_data_headers->{$row_ident}->{headers}->[$i] }; |
|
544 |
push @{ $headers->{$row_ident} }, $self->raw_data_headers->{$row_ident}->{headers}->[$i]; |
|
545 |
push @{ $raw_methods->{$row_ident} }, $self->raw_data_headers->{$row_ident}->{headers}->[$i]; |
|
546 |
} |
|
547 |
|
|
548 |
} |
|
549 |
|
|
550 |
for my $i (0 .. $#{ $self->worker->profile }) { |
|
551 |
my $row_ident = $self->worker->profile->[$i]->{row_ident}; |
|
552 |
$sth->execute($report->id, $_, $i, $headers->{$row_ident}->[$_]) for 0 .. $#{ $headers->{$row_ident} }; |
|
553 |
} |
|
554 |
|
|
555 |
# col offsets |
|
556 |
my ($off1, $off2); |
|
557 |
for my $i (0 .. $#{ $self->worker->profile }) { |
|
558 |
my $row_ident = $self->worker->profile->[$i]->{row_ident}; |
|
559 |
my $n_info_methods = $info_methods->{$row_ident} ? scalar @{ $info_methods->{$row_ident} } : 0; |
|
560 |
my $n_methods = $methods->{$row_ident} ? scalar @{ $methods->{$row_ident} } : 0; |
|
561 |
|
|
562 |
$off1->{$row_ident} = $n_info_methods; |
|
563 |
$off2->{$row_ident} = $off1->{$row_ident} + $n_methods; |
|
564 |
} |
|
565 |
|
|
566 |
my $n_header_rows = scalar @{ $self->worker->profile }; |
|
567 |
|
|
568 |
for my $row (0 .. $#{ $self->data }) { |
|
569 |
$self->track_progress(progress => $row / @{ $self->data } * 100) if $row % 1000 == 0; |
|
570 |
my $data_row = $self->{data}[$row]; |
|
571 |
my $row_ident = $data_row->{raw_data}{datatype}; |
|
572 |
|
|
573 |
my $o1 = $off1->{$row_ident}; |
|
574 |
my $o2 = $off2->{$row_ident}; |
|
575 |
|
|
576 |
$sth->execute($report->id, $_, $row + $n_header_rows, $data_row->{info_data}{ $info_methods->{$row_ident}->[$_] }) for 0 .. $#{ $info_methods->{$row_ident} }; |
|
577 |
$sth->execute($report->id, $o1 + $_, $row + $n_header_rows, $data_row->{object}->${ \ $methods->{$row_ident}->[$_] }) for 0 .. $#{ $methods->{$row_ident} }; |
|
578 |
$sth->execute($report->id, $o2 + $_, $row + $n_header_rows, $data_row->{raw_data}{ $raw_methods->{$row_ident}->[$_] }) for 0 .. $#{ $raw_methods->{$row_ident} }; |
|
579 |
|
|
580 |
$sth2->execute($report->id, $row + $n_header_rows, 'information', $_) for @{ $data_row->{information} || [] }; |
|
581 |
$sth2->execute($report->id, $row + $n_header_rows, 'errors', $_) for @{ $data_row->{errors} || [] }; |
|
582 |
} |
|
583 |
|
|
584 |
$dbh->commit; |
|
585 |
|
|
586 |
return $report->id; |
|
587 |
} |
|
588 |
|
|
458 | 589 |
sub csv_file_name { |
459 | 590 |
my ($self) = @_; |
460 | 591 |
return "csv-import-" . $self->type . ".csv"; |
... | ... | |
474 | 605 |
: $self->{type} eq 'addresses' ? SL::Controller::CsvImport::Shipto->new(@args) |
475 | 606 |
: $self->{type} eq 'parts' ? SL::Controller::CsvImport::Part->new(@args) |
476 | 607 |
: $self->{type} eq 'projects' ? SL::Controller::CsvImport::Project->new(@args) |
608 |
: $self->{type} eq 'orders' ? SL::Controller::CsvImport::Order->new(@args) |
|
477 | 609 |
: die "Program logic error"; |
478 | 610 |
} |
479 | 611 |
|
SL/Controller/CsvImport/Base.pm | ||
---|---|---|
18 | 18 |
use Rose::Object::MakeMethods::Generic |
19 | 19 |
( |
20 | 20 |
scalar => [ qw(controller file csv test_run save_with_cascade) ], |
21 |
'scalar --get_set_init' => [ qw(profile displayable_columns existing_objects class manager_class cvar_columns all_cvar_configs all_languages payment_terms_by all_currencies default_currency_id all_vc vc_by) ], |
|
21 |
'scalar --get_set_init' => [ qw(is_multiplexed profile displayable_columns existing_objects class manager_class cvar_columns all_cvar_configs all_languages payment_terms_by all_currencies default_currency_id all_vc vc_by) ],
|
|
22 | 22 |
); |
23 | 23 |
|
24 | 24 |
sub run { |
... | ... | |
311 | 311 |
$self->manager_class("SL::DB::Manager::" . $1); |
312 | 312 |
} |
313 | 313 |
|
314 |
sub init_is_multiplexed { |
|
315 |
my ($self) = @_; |
|
316 |
|
|
317 |
$self->is_multiplexed('ARRAY' eq ref ($self->class) && scalar @{ $self->class } > 1); |
|
318 |
} |
|
319 |
|
|
314 | 320 |
sub check_objects { |
315 | 321 |
} |
316 | 322 |
|
SL/Controller/CsvImport/BaseMulti.pm | ||
---|---|---|
1 |
package SL::Controller::CsvImport::BaseMulti; |
|
2 |
|
|
3 |
use strict; |
|
4 |
|
|
5 |
use List::MoreUtils qw(pairwise); |
|
6 |
|
|
7 |
use SL::Helper::Csv; |
|
8 |
use SL::DB::Customer; |
|
9 |
use SL::DB::Language; |
|
10 |
use SL::DB::PaymentTerm; |
|
11 |
use SL::DB::Vendor; |
|
12 |
use SL::DB::Contact; |
|
13 |
|
|
14 |
use parent qw(SL::Controller::CsvImport::Base); |
|
15 |
|
|
16 |
sub run { |
|
17 |
my ($self, %params) = @_; |
|
18 |
|
|
19 |
$self->test_run($params{test_run}); |
|
20 |
|
|
21 |
$self->controller->track_progress(phase => 'parsing csv', progress => 0); |
|
22 |
|
|
23 |
my $profile = $self->profile; |
|
24 |
|
|
25 |
$self->csv(SL::Helper::Csv->new(file => $self->file->file_name, |
|
26 |
encoding => $self->controller->profile->get('charset'), |
|
27 |
profile => $profile, |
|
28 |
ignore_unknown_columns => 1, |
|
29 |
strict_profile => 1, |
|
30 |
case_insensitive_header => 1, |
|
31 |
map { ( $_ => $self->controller->profile->get($_) ) } qw(sep_char escape_char quote_char), |
|
32 |
)); |
|
33 |
|
|
34 |
$self->controller->track_progress(progress => 10); |
|
35 |
|
|
36 |
my $old_numberformat = $::myconfig{numberformat}; |
|
37 |
$::myconfig{numberformat} = $self->controller->profile->get('numberformat'); |
|
38 |
|
|
39 |
$self->csv->parse; |
|
40 |
|
|
41 |
$self->controller->track_progress(progress => 50); |
|
42 |
|
|
43 |
# bb: make sanity-check of it? |
|
44 |
#if ($self->csv->is_multiplexed != $self->is_multiplexed) { |
|
45 |
# die "multiplex controller on simplex data or vice versa"; |
|
46 |
#} |
|
47 |
|
|
48 |
$self->controller->errors([ $self->csv->errors ]) if $self->csv->errors; |
|
49 |
|
|
50 |
return if ( !$self->csv->header || $self->csv->errors ); |
|
51 |
|
|
52 |
my $headers; |
|
53 |
my $i = 0; |
|
54 |
foreach my $header (@{ $self->csv->header }) { |
|
55 |
|
|
56 |
my $profile = $self->csv->profile->[$i]->{profile}; |
|
57 |
my $row_ident = $self->csv->profile->[$i]->{row_ident}; |
|
58 |
|
|
59 |
my $h = { headers => [ grep { $profile->{$_} } @{ $header } ] }; |
|
60 |
$h->{methods} = [ map { $profile->{$_} } @{ $h->{headers} } ]; |
|
61 |
$h->{used} = { map { ($_ => 1) } @{ $h->{headers} } }; |
|
62 |
|
|
63 |
$headers->{$row_ident} = $h; |
|
64 |
$i++; |
|
65 |
} |
|
66 |
|
|
67 |
$self->controller->headers($headers); |
|
68 |
|
|
69 |
my $raw_data_headers; |
|
70 |
my $info_headers; |
|
71 |
foreach my $p (@{ $self->csv->profile }) { |
|
72 |
$raw_data_headers->{ $p->{row_ident} } = { used => { }, headers => [ ] }; |
|
73 |
$info_headers->{ $p->{row_ident} } = { used => { }, headers => [ ] }; |
|
74 |
} |
|
75 |
$self->controller->raw_data_headers($raw_data_headers); |
|
76 |
$self->controller->info_headers($info_headers); |
|
77 |
|
|
78 |
|
|
79 |
my @objects = $self->csv->get_objects; |
|
80 |
$self->controller->track_progress(progress => 70); |
|
81 |
|
|
82 |
my @raw_data = @{ $self->csv->get_data }; |
|
83 |
|
|
84 |
$self->controller->track_progress(progress => 80); |
|
85 |
|
|
86 |
$self->controller->data([ pairwise { { object => $a, raw_data => $b, errors => [], information => [], info_data => {} } } @objects, @raw_data ]); |
|
87 |
|
|
88 |
$self->controller->track_progress(progress => 90); |
|
89 |
|
|
90 |
$self->check_objects; |
|
91 |
if ( $self->controller->profile->get('duplicates', 'no_check') ne 'no_check' ) { |
|
92 |
$self->check_std_duplicates(); |
|
93 |
$self->check_duplicates(); |
|
94 |
} |
|
95 |
$self->fix_field_lengths; |
|
96 |
|
|
97 |
$self->controller->track_progress(progress => 100); |
|
98 |
|
|
99 |
$::myconfig{numberformat} = $old_numberformat; |
|
100 |
} |
|
101 |
|
|
102 |
sub add_columns { |
|
103 |
my ($self, $row_ident, @columns) = @_; |
|
104 |
|
|
105 |
my $h = $self->controller->headers->{$row_ident}; |
|
106 |
|
|
107 |
foreach my $column (grep { !$h->{used}->{$_} } @columns) { |
|
108 |
$h->{used}->{$column} = 1; |
|
109 |
push @{ $h->{methods} }, $column; |
|
110 |
push @{ $h->{headers} }, $column; |
|
111 |
} |
|
112 |
} |
|
113 |
|
|
114 |
sub add_info_columns { |
|
115 |
my ($self, $row_ident, @columns) = @_; |
|
116 |
|
|
117 |
my $h = $self->controller->info_headers->{$row_ident}; |
|
118 |
|
|
119 |
foreach my $column (grep { !$h->{used}->{ $_->{method} } } map { ref $_ eq 'HASH' ? $_ : { method => $_, header => $_ } } @columns) { |
|
120 |
$h->{used}->{ $column->{method} } = 1; |
|
121 |
push @{ $h->{methods} }, $column->{method}; |
|
122 |
push @{ $h->{headers} }, $column->{header}; |
|
123 |
} |
|
124 |
} |
|
125 |
|
|
126 |
sub add_raw_data_columns { |
|
127 |
my ($self, $row_ident, @columns) = @_; |
|
128 |
|
|
129 |
my $h = $self->controller->raw_data_headers->{$row_ident}; |
|
130 |
|
|
131 |
foreach my $column (grep { !$h->{used}->{$_} } @columns) { |
|
132 |
$h->{used}->{$column} = 1; |
|
133 |
push @{ $h->{headers} }, $column; |
|
134 |
} |
|
135 |
} |
|
136 |
|
|
137 |
sub add_cvar_raw_data_columns { |
|
138 |
my ($self) = @_; |
|
139 |
|
|
140 |
map { $self->add_raw_data_columns($_) if exists $self->controller->data->[0]->{raw_data}->{$_} } @{ $self->cvar_columns }; |
|
141 |
} |
|
142 |
|
|
143 |
sub init_profile { |
|
144 |
my ($self) = @_; |
|
145 |
|
|
146 |
my @profile; |
|
147 |
foreach my $class (@{ $self->class }) { |
|
148 |
eval "require " . $class; |
|
149 |
|
|
150 |
my %unwanted = map { ( $_ => 1 ) } (qw(itime mtime), map { $_->name } @{ $class->meta->primary_key_columns }); |
|
151 |
my %prof; |
|
152 |
$prof{datatype} = ''; |
|
153 |
for my $col ($class->meta->columns) { |
|
154 |
next if $unwanted{$col}; |
|
155 |
|
|
156 |
my $name = $col->isa('Rose::DB::Object::Metadata::Column::Numeric') ? "$col\_as_number" |
|
157 |
: $col->isa('Rose::DB::Object::Metadata::Column::Date') ? "$col\_as_date" |
|
158 |
: $col->isa('Rose::DB::Object::Metadata::Column::Timestamp') ? "$col\_as_date" |
|
159 |
: $col->name; |
|
160 |
|
|
161 |
$prof{$col} = $name; |
|
162 |
} |
|
163 |
|
|
164 |
$prof{ 'cvar_' . $_->name } = '' for @{ $self->all_cvar_configs }; |
|
165 |
|
|
166 |
$class =~ m/^SL::DB::(.+)/; |
|
167 |
push @profile, {'profile' => \%prof, 'class' => $class, 'row_ident' => $1}; |
|
168 |
} |
|
169 |
|
|
170 |
\@profile; |
|
171 |
} |
|
172 |
|
|
173 |
sub add_displayable_columns { |
|
174 |
my ($self, $row_ident, @columns) = @_; |
|
175 |
|
|
176 |
my $dis_cols = $self->controller->displayable_columns || {}; |
|
177 |
|
|
178 |
my @cols = @{ $dis_cols->{$row_ident} || [] }; |
|
179 |
my %ex_col_map = map { $_->{name} => $_ } @cols; |
|
180 |
|
|
181 |
foreach my $column (@columns) { |
|
182 |
if ($ex_col_map{ $column->{name} }) { |
|
183 |
@{ $ex_col_map{ $column->{name} } }{ keys %{ $column } } = @{ $column }{ keys %{ $column } }; |
|
184 |
} else { |
|
185 |
push @cols, $column; |
|
186 |
} |
|
187 |
} |
|
188 |
|
|
189 |
my $by_name_datatype_first = sub { 'datatype' eq $a->{name} ? -1 : |
|
190 |
'datatype' eq $b->{name} ? 1 : |
|
191 |
$a->{name} cmp $b->{name} }; |
|
192 |
$dis_cols->{$row_ident} = [ sort $by_name_datatype_first @cols ]; |
|
193 |
|
|
194 |
$self->controller->displayable_columns($dis_cols); |
|
195 |
} |
|
196 |
|
|
197 |
sub setup_displayable_columns { |
|
198 |
my ($self) = @_; |
|
199 |
|
|
200 |
foreach my $p (@{ $self->profile }) { |
|
201 |
$self->add_displayable_columns($p->{row_ident}, map { { name => $_ } } keys %{ $p->{profile} }); |
|
202 |
} |
|
203 |
} |
|
204 |
|
|
205 |
sub add_cvar_columns_to_displayable_columns { |
|
206 |
my ($self) = @_; |
|
207 |
|
|
208 |
$self->add_displayable_columns(map { { name => 'cvar_' . $_->name, |
|
209 |
description => $::locale->text('#1 (custom variable)', $_->description) } } |
|
210 |
@{ $self->all_cvar_configs }); |
|
211 |
} |
|
212 |
|
|
213 |
sub init_existing_objects { |
|
214 |
my ($self) = @_; |
|
215 |
|
|
216 |
eval "require " . $self->class; |
|
217 |
$self->existing_objects($self->manager_class->get_all); |
|
218 |
} |
|
219 |
|
|
220 |
sub init_class { |
|
221 |
die "class not set"; |
|
222 |
} |
|
223 |
|
|
224 |
sub init_manager_class { |
|
225 |
my ($self) = @_; |
|
226 |
|
|
227 |
$self->class =~ m/^SL::DB::(.+)/; |
|
228 |
$self->manager_class("SL::DB::Manager::" . $1); |
|
229 |
} |
|
230 |
|
|
231 |
1; |
|
232 |
|
SL/Controller/CsvImport/Order.pm | ||
---|---|---|
1 |
package SL::Controller::CsvImport::Order; |
|
2 |
|
|
3 |
|
|
4 |
use strict; |
|
5 |
|
|
6 |
use List::MoreUtils qw(any); |
|
7 |
|
|
8 |
use SL::Helper::Csv; |
|
9 |
use SL::DB::Order; |
|
10 |
use SL::DB::OrderItem; |
|
11 |
use SL::DB::Part; |
|
12 |
use SL::DB::PaymentTerm; |
|
13 |
use SL::DB::Contact; |
|
14 |
|
|
15 |
use parent qw(SL::Controller::CsvImport::BaseMulti); |
|
16 |
|
|
17 |
|
|
18 |
use Rose::Object::MakeMethods::Generic |
|
19 |
( |
|
20 |
'scalar --get_set_init' => [ qw(settings languages_by all_parts parts_by all_contacts contacts_by) ], |
|
21 |
); |
|
22 |
|
|
23 |
|
|
24 |
sub init_class { |
|
25 |
my ($self) = @_; |
|
26 |
$self->class(['SL::DB::Order', 'SL::DB::OrderItem']); |
|
27 |
} |
|
28 |
|
|
29 |
|
|
30 |
sub init_settings { |
|
31 |
my ($self) = @_; |
|
32 |
|
|
33 |
return { map { ( $_ => $self->controller->profile->get($_) ) } qw(order_column item_column) }; |
|
34 |
} |
|
35 |
|
|
36 |
|
|
37 |
sub init_profile { |
|
38 |
my ($self) = @_; |
|
39 |
|
|
40 |
my $profile = $self->SUPER::init_profile; |
|
41 |
|
|
42 |
foreach my $p (@{ $profile }) { |
|
43 |
my $prof = $p->{profile}; |
|
44 |
if ($p->{row_ident} eq 'Order') { |
|
45 |
# no need to handle |
|
46 |
delete @{$prof}{qw(delivery_customer_id delivery_vendor_id proforma quotation amount netamount)}; |
|
47 |
# handable, but not handled by now |
|
48 |
} |
|
49 |
if ($p->{row_ident} eq 'OrderItem') { |
|
50 |
delete @{$prof}{qw(trans_id)}; |
|
51 |
} |
|
52 |
} |
|
53 |
|
|
54 |
return $profile; |
|
55 |
} |
|
56 |
|
|
57 |
|
|
58 |
sub setup_displayable_columns { |
|
59 |
my ($self) = @_; |
|
60 |
|
|
61 |
$self->SUPER::setup_displayable_columns; |
|
62 |
|
|
63 |
$self->add_displayable_columns('Order', |
|
64 |
{ name => 'datatype', description => $::locale->text('Zeilenkennung') }, |
|
65 |
{ name => 'verify_amount', description => $::locale->text('Amount (for verification)') }, |
|
66 |
{ name => 'verify_netamount', description => $::locale->text('Net amount (for verification)') }, |
|
67 |
{ name => 'taxincluded', description => $::locale->text('Tax Included') }, |
|
68 |
{ name => 'customer', description => $::locale->text('Customer (name)') }, |
|
69 |
{ name => 'customernumber', description => $::locale->text('Customer Number') }, |
|
70 |
{ name => 'customer_id', description => $::locale->text('Customer (database ID)') }, |
|
71 |
{ name => 'vendor', description => $::locale->text('Vendor (name)') }, |
|
72 |
{ name => 'vendornumber', description => $::locale->text('Vendor Number') }, |
|
73 |
{ name => 'vendor_id', description => $::locale->text('Vendor (database ID)') }, |
|
74 |
{ name => 'language_id', description => $::locale->text('Language (database ID)') }, |
|
75 |
{ name => 'language', description => $::locale->text('Language (name)') }, |
|
76 |
{ name => 'payment_id', description => $::locale->text('Payment terms (database ID)') }, |
|
77 |
{ name => 'payment', description => $::locale->text('Payment terms (name)') }, |
|
78 |
{ name => 'taxzone_id', description => $::locale->text('Steuersatz') }, |
|
79 |
{ name => 'contact_id', description => $::locale->text('Contact Person (database ID)') }, |
|
80 |
{ name => 'contact', description => $::locale->text('Contact Person (name)') }, |
|
81 |
); |
|
82 |
|
|
83 |
$self->add_displayable_columns('OrderItem', |
|
84 |
{ name => 'parts_id', description => $::locale->text('Part (database ID)') }, |
|
85 |
{ name => 'partnumber', description => $::locale->text('Part Number') }, |
|
86 |
); |
|
87 |
} |
|
88 |
|
|
89 |
|
|
90 |
sub init_languages_by { |
|
91 |
my ($self) = @_; |
|
92 |
|
|
93 |
return { map { my $col = $_; ( $col => { map { ( $_->$col => $_ ) } @{ $self->all_languages } } ) } qw(id description article_code) }; |
|
94 |
} |
|
95 |
|
|
96 |
sub init_all_parts { |
|
97 |
my ($self) = @_; |
|
98 |
|
|
99 |
return SL::DB::Manager::Part->get_all; |
|
100 |
} |
|
101 |
|
|
102 |
sub init_parts_by { |
|
103 |
my ($self) = @_; |
|
104 |
|
|
105 |
return { map { my $col = $_; ( $col => { map { ( $_->$col => $_ ) } @{ $self->all_parts } } ) } qw(id partnumber ean description) }; |
|
106 |
} |
|
107 |
|
|
108 |
sub init_all_contacts { |
|
109 |
my ($self) = @_; |
|
110 |
|
|
111 |
return SL::DB::Manager::Contact->get_all; |
|
112 |
} |
|
113 |
|
|
114 |
sub init_contacts_by { |
|
115 |
my ($self) = @_; |
|
116 |
|
|
117 |
my $cby = { map { my $col = $_; ( $col => { map { ( $_->$col => $_ ) } @{ $self->all_contacts } } ) } qw(cp_id cp_name) }; |
|
118 |
|
|
119 |
# by customer/vendor id _and_ contact person id |
|
120 |
$cby->{'cp_cv_id+cp_id'} = { map { ( $_->cp_cv_id . '+' . $_->cp_id => $_ ) } @{ $self->all_contacts } }; |
|
121 |
|
|
122 |
return $cby; |
|
123 |
} |
|
124 |
|
|
125 |
sub check_objects { |
|
126 |
my ($self) = @_; |
|
127 |
|
|
128 |
$self->controller->track_progress(phase => 'building data', progress => 0); |
|
129 |
|
|
130 |
my $i; |
|
131 |
my $num_data = scalar @{ $self->controller->data }; |
|
132 |
foreach my $entry (@{ $self->controller->data }) { |
|
133 |
$self->controller->track_progress(progress => $i/$num_data * 100) if $i % 100 == 0; |
|
134 |
|
|
135 |
if ($entry->{raw_data}->{datatype} eq $self->settings->{'order_column'}) { |
|
136 |
|
|
137 |
my $vc_obj; |
|
138 |
if (any { $entry->{raw_data}->{$_} } qw(customer customernumber customer_id)) { |
|
139 |
$self->check_vc($entry, 'customer_id'); |
|
140 |
$vc_obj = SL::DB::Customer->new(id => $entry->{object}->customer_id)->load if $entry->{object}->customer_id; |
|
141 |
} elsif (any { $entry->{raw_data}->{$_} } qw(vendor vendornumber vendor_id)) { |
|
142 |
$self->check_vc($entry, 'vendor_id'); |
|
143 |
$vc_obj = SL::DB::Vendor->new(id => $entry->{object}->vendor_id)->load if $entry->{object}->vendor_id; |
|
144 |
} else { |
|
145 |
push @{ $entry->{errors} }, $::locale->text('Error: Customer/vendor missing'); |
|
146 |
} |
|
147 |
|
|
148 |
$self->check_contact($entry); |
|
149 |
$self->check_language($entry); |
|
150 |
$self->check_payment($entry); |
|
151 |
|
|
152 |
if ($vc_obj) { |
|
153 |
# copy from customer if not given |
|
154 |
foreach (qw(payment_id language_id taxzone_id)) { |
|
155 |
$entry->{object}->$_($vc_obj->$_) unless $entry->{object}->$_; |
|
156 |
} |
|
157 |
} |
|
158 |
|
|
159 |
# ToDo: salesman and emloyee by name |
|
160 |
# salesman from customer or login if not given |
|
161 |
if (!$entry->{object}->salesman) { |
|
162 |
if ($vc_obj && $vc_obj->salesman_id) { |
|
163 |
$entry->{object}->salesman(SL::DB::Manager::Employee->find_by(id => $vc_obj->salesman_id)); |
|
164 |
} else { |
|
165 |
$entry->{object}->salesman(SL::DB::Manager::Employee->find_by(login => $::myconfig{login})); |
|
166 |
} |
|
167 |
} |
|
168 |
|
|
169 |
# employee from login if not given |
|
170 |
if (!$entry->{object}->employee_id) { |
|
171 |
$entry->{object}->employee_id(SL::DB::Manager::Employee->find_by(login => $::myconfig{login})->id); |
|
172 |
} |
|
173 |
|
|
174 |
} |
|
175 |
} |
|
176 |
|
|
177 |
$self->add_info_columns($self->settings->{'order_column'}, |
|
178 |
{ header => $::locale->text('Customer/Vendor'), method => 'vc_name' }); |
|
179 |
$self->add_columns($self->settings->{'order_column'}, |
|
180 |
map { "${_}_id" } grep { exists $self->controller->data->[0]->{raw_data}->{$_} } qw(business payment)); |
|
181 |
|
|
182 |
|
|
183 |
foreach my $entry (@{ $self->controller->data }) { |
|
184 |
if ($entry->{raw_data}->{datatype} eq $self->settings->{'item_column'} && $entry->{object}->can('part')) { |
|
185 |
|
|
186 |
next if !$self->check_part($entry); |
|
187 |
|
|
188 |
my $part_obj = SL::DB::Part->new(id => $entry->{object}->parts_id)->load; |
|
189 |
|
|
190 |
# copy from part if not given |
|
191 |
$entry->{object}->description($part_obj->description) unless $entry->{object}->description; |
|
192 |
$entry->{object}->longdescription($part_obj->notes) unless $entry->{object}->longdescription; |
|
193 |
$entry->{object}->unit($part_obj->unit) unless $entry->{object}->unit; |
|
194 |
|
|
195 |
# set to 0 if not given |
|
196 |
$entry->{object}->discount(0) unless $entry->{object}->discount; |
|
197 |
$entry->{object}->ship(0) unless $entry->{object}->ship; |
|
198 |
} |
|
199 |
} |
|
200 |
|
|
201 |
$self->add_info_columns($self->settings->{'item_column'}, |
|
202 |
{ header => $::locale->text('Part Number'), method => 'partnumber' }); |
|
203 |
|
|
204 |
# add orderitems to order |
|
205 |
my $order_entry; |
|
206 |
my @orderitems; |
|
207 |
foreach my $entry (@{ $self->controller->data }) { |
|
208 |
# search first Order |
|
209 |
if ($entry->{raw_data}->{datatype} eq $self->settings->{'order_column'}) { |
|
210 |
|
|
211 |
# new order entry: add collected orderitems to the last one |
|
212 |
if (defined $order_entry) { |
|
213 |
$order_entry->{object}->orderitems(@orderitems); |
|
214 |
@orderitems = (); |
|
215 |
} |
|
216 |
|
|
217 |
$order_entry = $entry; |
|
218 |
|
|
219 |
} elsif ( defined $order_entry && $entry->{raw_data}->{datatype} eq $self->settings->{'item_column'} ) { |
|
220 |
# collect orderitems to add to order (if they have no errors) |
|
221 |
# ( add_orderitems does not work here if we want to call |
|
222 |
# calculate_prices_and_taxes afterwards ... |
|
223 |
# so collect orderitems and add them at once) |
|
224 |
if (scalar @{ $entry->{errors} } == 0) { |
|
225 |
push @orderitems, $entry->{object}; |
|
226 |
} |
|
227 |
} |
|
228 |
} |
|
229 |
# add last collected orderitems to last order |
|
230 |
if ($order_entry) { |
|
231 |
$order_entry->{object}->orderitems(@orderitems); |
|
232 |
} |
|
233 |
|
|
234 |
# calculate prices and taxes |
|
235 |
foreach my $entry (@{ $self->controller->data }) { |
|
236 |
next if @{ $entry->{errors} }; |
|
237 |
|
|
238 |
if ($entry->{raw_data}->{datatype} eq $self->settings->{'order_column'}) { |
|
239 |
|
|
240 |
$entry->{object}->calculate_prices_and_taxes; |
|
241 |
|
|
242 |
$entry->{info_data}->{calc_amount} = $entry->{object}->amount_as_number; |
|
243 |
$entry->{info_data}->{calc_netamount} = $entry->{object}->netamount_as_number; |
|
244 |
} |
|
245 |
} |
|
246 |
|
|
247 |
# If amounts are given, show calculated amounts as info and given amounts (verify_xxx). |
|
248 |
# And throw an error if the differences are too big. |
|
249 |
my $max_diff = 0.02; |
|
250 |
my @to_verify = ( { column => 'amount', |
|
251 |
raw_column => 'verify_amount', |
|
252 |
info_header => 'Calc. Amount', |
|
253 |
info_method => 'calc_amount', |
|
254 |
err_msg => 'Amounts differ too much', |
|
255 |
}, |
|
256 |
{ column => 'netamount', |
|
257 |
raw_column => 'verify_netamount', |
|
258 |
info_header => 'Calc. Net amount', |
|
259 |
info_method => 'calc_netamount', |
|
260 |
err_msg => 'Net amounts differ too much', |
|
261 |
} ); |
|
262 |
|
|
263 |
foreach my $tv (@to_verify) { |
|
264 |
if (exists $self->controller->data->[0]->{raw_data}->{ $tv->{raw_column} }) { |
|
265 |
$self->add_raw_data_columns($self->settings->{'order_column'}, $tv->{raw_column}); |
|
266 |
$self->add_info_columns($self->settings->{'order_column'}, |
|
267 |
{ header => $::locale->text($tv->{info_header}), method => $tv->{info_method} }); |
|
268 |
} |
|
269 |
|
|
270 |
# check differences |
|
271 |
foreach my $entry (@{ $self->controller->data }) { |
|
272 |
next if @{ $entry->{errors} }; |
|
273 |
if ($entry->{raw_data}->{datatype} eq $self->settings->{'order_column'}) { |
|
274 |
next if !$entry->{raw_data}->{ $tv->{raw_column} }; |
|
275 |
my $parsed_value = $::form->parse_amount(\%::myconfig, $entry->{raw_data}->{ $tv->{raw_column} }); |
|
276 |
if (abs($entry->{object}->${ \$tv->{column} } - $parsed_value) > $max_diff) { |
|
277 |
push @{ $entry->{errors} }, $::locale->text($tv->{err_msg}); |
|
278 |
} |
|
279 |
} |
|
280 |
} |
|
281 |
} |
|
282 |
|
|
283 |
# If order has errors set error for orderitems as well |
|
284 |
my $order_entry; |
|
285 |
foreach my $entry (@{ $self->controller->data }) { |
|
286 |
# Search first order |
|
287 |
if ($entry->{raw_data}->{datatype} eq $self->settings->{'order_column'}) { |
|
288 |
$order_entry = $entry; |
|
289 |
} elsif ( defined $order_entry |
|
290 |
&& $entry->{raw_data}->{datatype} eq $self->settings->{'item_column'} |
|
291 |
&& scalar @{ $order_entry->{errors} } > 0 ) { |
|
292 |
push @{ $entry->{errors} }, $::locale->text('order not valid for this orderitem!'); |
|
293 |
} |
|
294 |
} |
|
295 |
|
|
296 |
} |
|
297 |
|
|
298 |
|
|
299 |
sub check_language { |
|
300 |
my ($self, $entry) = @_; |
|
301 |
|
|
302 |
my $object = $entry->{object}; |
|
303 |
|
|
304 |
# Check whether or not language ID is valid. |
|
305 |
if ($object->language_id && !$self->languages_by->{id}->{ $object->language_id }) { |
|
306 |
push @{ $entry->{errors} }, $::locale->text('Error: Invalid language'); |
|
307 |
return 0; |
|
308 |
} |
|
309 |
|
|
310 |
# Map name to ID if given. |
|
311 |
if (!$object->language_id && $entry->{raw_data}->{language}) { |
|
312 |
my $language = $self->languages_by->{description}->{ $entry->{raw_data}->{language} } |
|
313 |
|| $self->languages_by->{article_code}->{ $entry->{raw_data}->{language} }; |
|
314 |
|
|
315 |
if (!$language) { |
|
316 |
push @{ $entry->{errors} }, $::locale->text('Error: Invalid language'); |
|
317 |
return 0; |
|
318 |
} |
|
319 |
|
|
320 |
$object->language_id($language->id); |
|
321 |
} |
|
322 |
|
|
323 |
if ($object->language_id) { |
|
324 |
$entry->{info_data}->{language} = $self->languages_by->{id}->{ $object->language_id }->description; |
|
325 |
} |
|
326 |
|
|
327 |
return 1; |
|
328 |
} |
|
329 |
|
|
330 |
sub check_part { |
|
331 |
my ($self, $entry) = @_; |
|
332 |
|
|
333 |
my $object = $entry->{object}; |
|
334 |
|
|
335 |
# Check wether or non part ID is valid. |
|
336 |
if ($object->parts_id && !$self->parts_by->{id}->{ $object->parts_id }) { |
|
337 |
push @{ $entry->{errors} }, $::locale->text('Error: Invalid part'); |
|
338 |
return 0; |
|
339 |
} |
|
340 |
|
|
341 |
# Map number to ID if given. |
|
342 |
if (!$object->parts_id && $entry->{raw_data}->{partnumber}) { |
|
343 |
my $part = $self->parts_by->{partnumber}->{ $entry->{raw_data}->{partnumber} }; |
|
344 |
if (!$part) { |
|
345 |
push @{ $entry->{errors} }, $::locale->text('Error: Invalid part'); |
|
346 |
return 0; |
|
347 |
} |
|
348 |
|
|
349 |
$object->parts_id($part->id); |
|
350 |
} |
|
351 |
|
|
352 |
if ($object->parts_id) { |
|
353 |
$entry->{info_data}->{partnumber} = $self->parts_by->{id}->{ $object->parts_id }->partnumber; |
|
354 |
} else { |
|
355 |
push @{ $entry->{errors} }, $::locale->text('Error: Part not found'); |
|
356 |
return 0; |
|
357 |
} |
|
358 |
|
|
359 |
return 1; |
|
360 |
} |
|
361 |
|
|
362 |
sub check_contact { |
|
363 |
my ($self, $entry) = @_; |
|
364 |
|
|
365 |
my $object = $entry->{object}; |
|
366 |
|
|
367 |
# Check wether or non contact ID is valid. |
|
368 |
if ($object->cp_id && !$self->contacts_by->{cp_id}->{ $object->cp_id }) { |
|
369 |
push @{ $entry->{errors} }, $::locale->text('Error: Invalid contact'); |
|
370 |
return 0; |
|
371 |
} |
|
372 |
|
|
373 |
# Map number to ID if given. |
|
374 |
if (!$object->cp_id && $entry->{raw_data}->{contact}) { |
|
375 |
my $cp = $self->contacts_by->{cp_name}->{ $entry->{raw_data}->{contact} }; |
|
376 |
if (!$cp) { |
|
377 |
push @{ $entry->{errors} }, $::locale->text('Error: Invalid contact'); |
|
378 |
return 0; |
|
379 |
} |
|
380 |
|
|
381 |
$object->cp_id($cp->cp_id); |
|
382 |
} |
|
383 |
|
|
384 |
# Check if the contact belongs to this customer/vendor. |
|
385 |
if ($object->cp_id && $object->customer_id && !$self->contacts_by->{'cp_cv_id+cp_id'}) { |
|
386 |
push @{ $entry->{errors} }, $::locale->text('Error: Contact not found for this customer/vendor'); |
|
387 |
return 0; |
|
388 |
} |
|
389 |
|
|
390 |
if ($object->cp_id) { |
|
391 |
$entry->{info_data}->{contact} = $self->contacts_by->{cp_id}->{ $object->cp_id }->cp_name; |
|
392 |
} |
|
393 |
|
|
394 |
return 1; |
|
395 |
} |
|
396 |
|
|
397 |
sub save_objects { |
|
398 |
my ($self, %params) = @_; |
|
399 |
|
|
400 |
# set order number and collect to save |
|
401 |
my $objects_to_save; |
|
402 |
foreach my $entry (@{ $self->controller->data }) { |
|
403 |
next if @{ $entry->{errors} }; |
|
404 |
|
|
405 |
if ($entry->{raw_data}->{datatype} eq $self->settings->{'order_column'} && !$entry->{object}->ordnumber) { |
|
406 |
$entry->{object}->create_trans_number; |
|
407 |
} |
|
408 |
|
|
409 |
push @{ $objects_to_save }, $entry; |
|
410 |
} |
|
411 |
|
|
412 |
$self->SUPER::save_objects(data => $objects_to_save); |
|
413 |
} |
|
414 |
|
|
415 |
|
|
416 |
1; |
menus/erp.ini | ||
---|---|---|
636 | 636 |
action=CsvImport/new |
637 | 637 |
profile.type=projects |
638 | 638 |
|
639 |
[System--Import CSV--Orders] |
|
640 |
module=controller.pl |
|
641 |
action=CsvImport/new |
|
642 |
profile.type=orders |
|
643 |
|
|
639 | 644 |
[System--Templates] |
640 | 645 |
ACCESS=admin |
641 | 646 |
module=menu.pl |
templates/webpages/csv_import/_form_orders.html | ||
---|---|---|
1 |
[% USE LxERP %] |
|
2 |
[% USE L %] |
|
3 |
<tr> |
|
4 |
<th align="right">[%- LxERP.t8('Order/Item columns') %]:</th> |
|
5 |
<td colspan="10"> |
|
6 |
[% L.input_tag('settings.order_column', SELF.profile.get('order_column'), size => "5") %] |
|
7 |
[% L.input_tag('settings.item_column', SELF.profile.get('item_column'), size => "5") %] |
|
8 |
</td> |
|
9 |
</tr> |
templates/webpages/csv_import/form.html | ||
---|---|---|
57 | 57 |
<div class="help_toggle" style="display:none"> |
58 | 58 |
<p><a href="#" onClick="javascript:$('.help_toggle').toggle()">[% LxERP.t8("Hide help text") %]</a></p> |
59 | 59 |
|
60 |
<table> |
|
61 |
<tr class="listheading"> |
|
62 |
<th>[%- LxERP.t8('Column name') %]</th> |
|
63 |
<th>[%- LxERP.t8('Meaning') %]</th> |
|
64 |
</tr> |
|
65 |
|
|
66 |
[%- FOREACH row = SELF.displayable_columns %] |
|
67 |
<tr class="listrow[% loop.count % 2 %]"> |
|
68 |
<td>[%- HTML.escape(row.name) %]</td> |
|
69 |
<td>[%- HTML.escape(row.description) %]</td> |
|
70 |
</tr> |
|
71 |
[%- END %] |
|
72 |
</table> |
|
60 |
[%- IF SELF.worker.is_multiplexed %] |
|
61 |
<table> |
|
62 |
<tr class="listheading"> |
|
63 |
[%- FOREACH ri = SELF.displayable_columns.keys %] |
|
64 |
<th>[%- ri %]</th> |
|
65 |
[%- END %] |
|
66 |
</tr> |
|
67 |
<tr class="listrow[% loop.count % 2 %]"> |
|
68 |
[%- FOREACH ri = SELF.displayable_columns.keys %] |
|
69 |
<td> |
|
70 |
<table> |
|
71 |
<tr class="listheading"> |
|
72 |
<th>[%- LxERP.t8('Column name') %]</th> |
|
73 |
<th>[%- LxERP.t8('Meaning') %]</th> |
|
74 |
</tr> |
|
75 |
|
|
76 |
[%- FOREACH row = SELF.displayable_columns.$ri %] |
|
77 |
<tr class="listrow[% loop.count % 2 %]"> |
|
78 |
<td>[%- HTML.escape(row.name) %]</td> |
|
79 |
<td>[%- HTML.escape(row.description) %]</td> |
|
80 |
</tr> |
|
81 |
[%- END %] |
|
82 |
</table> |
|
83 |
</td> |
|
84 |
[%- END %] |
|
85 |
</tr> |
|
86 |
</table> |
|
87 |
[%- ELSE %] |
|
88 |
<table> |
|
89 |
<tr class="listheading"> |
|
90 |
<th>[%- LxERP.t8('Column name') %]</th> |
|
91 |
<th>[%- LxERP.t8('Meaning') %]</th> |
|
92 |
</tr> |
|
93 |
|
|
94 |
[%- FOREACH row = SELF.displayable_columns %] |
|
95 |
<tr class="listrow[% loop.count % 2 %]"> |
|
96 |
<td>[%- HTML.escape(row.name) %]</td> |
|
97 |
<td>[%- HTML.escape(row.description) %]</td> |
|
98 |
</tr> |
|
99 |
[%- END %] |
|
100 |
</table> |
|
101 |
[%- END %] |
|
73 | 102 |
|
74 | 103 |
[%- IF SELF.type == 'contacts' %] |
75 | 104 |
<p> |
... | ... | |
95 | 124 |
[% LxERP.t8('The items are imported accoring do their number "X" regardless of the column order inside the file.') %] |
96 | 125 |
[% LxERP.t8('The column "make_X" can contain either a vendor\'s database ID, a vendor number or a vendor\'s name.') %] |
97 | 126 |
</p> |
127 |
|
|
128 |
[%- ELSIF SELF.type == 'orders' %] |
|
129 |
<p> |
|
130 |
[%- LxERP.t8('Amount and net amount are calculated by kivitendo. verify_amount and verify_netamount can be used for sanity checks.') %] |
|
131 |
</p> |
|
98 | 132 |
[%- END %] |
99 | 133 |
|
100 | 134 |
<p> |
... | ... | |
206 | 240 |
[%- INCLUDE 'csv_import/_form_customers_vendors.html' %] |
207 | 241 |
[%- ELSIF SELF.type == 'contacts' %] |
208 | 242 |
[%- INCLUDE 'csv_import/_form_contacts.html' %] |
243 |
[%- ELSIF SELF.type == 'orders' %] |
|
244 |
[%- INCLUDE 'csv_import/_form_orders.html' %] |
|
209 | 245 |
[%- END %] |
210 | 246 |
|
211 | 247 |
<tr> |
templates/webpages/csv_import/report.html | ||
---|---|---|
6 | 6 |
[%- PROCESS 'common/paginate.html' pages=SELF.pages, base_url = SELF.base_url %] |
7 | 7 |
<table> |
8 | 8 |
[%- FOREACH rownum = SELF.display_rows %] |
9 |
[%- IF loop.first %]
|
|
9 |
[%- IF rownum < SELF.report_numheaders %]
|
|
10 | 10 |
<tr class="listheading"> |
11 | 11 |
[%- FOREACH value = SELF.report_rows.${rownum} %] |
12 | 12 |
<th>[% value | html %]</th> |
... | ... | |
21 | 21 |
[%- END %] |
22 | 22 |
<td> |
23 | 23 |
[%- FOREACH error = csv_import_report_errors %][%- error | html %][% UNLESS loop.last %]<br>[%- END %][%- END %] |
24 |
[%- FOREACH info = SELF.report_status.${rownum}.information %][% IF !loop.first || csv_import_report_errors.size %]<br>[%- END %][%- info | html %][%- END %]
|
|
24 |
[%- FOREACH info = SELF.report_status.${rownum}.information %][% IF rownum >= SELF.report_numheaders || csv_import_report_errors.size %]<br>[%- END %][%- info | html %][%- END %]
|
|
25 | 25 |
</td> |
26 | 26 |
</tr> |
27 | 27 |
[%- END %] |
Auch abrufbar als: Unified diff
Auftrags-Import