Revision d7bd02dc
Von Tamino Steinert vor etwa 1 Jahr hinzugefügt
SL/Controller/DeliveryOrder.pm | ||
---|---|---|
58 | 58 |
use Cwd; |
59 | 59 |
use Sort::Naturally; |
60 | 60 |
|
61 |
use Rose::Object::MakeMethods::Generic |
|
62 |
( |
|
63 |
scalar => [ qw(item_ids_to_delete is_custom_shipto_to_delete) ], |
|
64 |
'scalar --get_set_init' => [ qw(order valid_types type cv p all_price_factors search_cvpartnumber show_update_button part_picker_classification_ids type_data) ], |
|
61 |
use Rose::Object::MakeMethods::Generic ( |
|
62 |
scalar => [qw(item_ids_to_delete is_custom_shipto_to_delete)], |
|
63 |
'scalar --get_set_init' => [ qw( |
|
64 |
order valid_types type cv p all_price_factors search_cvpartnumber |
|
65 |
show_update_button part_picker_classification_ids type_data |
|
66 |
) ], |
|
65 | 67 |
); |
66 | 68 |
|
67 | 69 |
|
68 | 70 |
# safety |
69 | 71 |
__PACKAGE__->run_before('check_auth', |
70 |
except => [ qw(update_stock_information) ]); |
|
72 |
except => [ qw( |
|
73 |
update_stock_information |
|
74 |
) ]); |
|
71 | 75 |
|
72 | 76 |
__PACKAGE__->run_before('check_auth_for_edit', |
73 |
except => [ qw(update_stock_information edit show_customer_vendor_details_dialog price_popup stock_in_out_dialog load_second_rows) ]); |
|
77 |
except => [ qw( |
|
78 |
update_stock_information edit show_customer_vendor_details_dialog |
|
79 |
price_popup stock_in_out_dialog load_second_rows |
|
80 |
) ]); |
|
74 | 81 |
|
75 | 82 |
__PACKAGE__->run_before('get_unalterable_data', |
76 |
only => [ qw(save save_as_new save_and_delivery_order save_and_invoice save_and_ap_transaction |
|
77 |
print send_email) ]); |
|
83 |
only => [ qw( |
|
84 |
save save_as_new save_and_delivery_order save_and_invoice |
|
85 |
save_and_ap_transaction print send_email |
|
86 |
) ]); |
|
78 | 87 |
|
79 | 88 |
# |
80 | 89 |
# actions |
... | ... | |
89 | 98 |
$self->pre_render(); |
90 | 99 |
|
91 | 100 |
if (!$::form->{form_validity_token}) { |
92 |
$::form->{form_validity_token} = SL::DB::ValidityToken->create(scope => SL::DB::ValidityToken::SCOPE_DELIVERY_ORDER_SAVE())->token; |
|
101 |
$::form->{form_validity_token} = SL::DB::ValidityToken->create( |
|
102 |
scope => SL::DB::ValidityToken::SCOPE_DELIVERY_ORDER_SAVE() |
|
103 |
)->token; |
|
93 | 104 |
} |
94 | 105 |
|
95 | 106 |
$self->render( |
... | ... | |
130 | 141 |
|
131 | 142 |
# set item ids to new fake id, to identify them as new items |
132 | 143 |
foreach my $item (@{$self->order->items_sorted}) { |
133 |
$item->{new_fake_id} = join('_', 'new', Time::HiRes::gettimeofday(), int rand 1000000000000); |
|
144 |
$item->{new_fake_id} = |
|
145 |
join('_', 'new', Time::HiRes::gettimeofday(), int rand 1000000000000); |
|
134 | 146 |
} |
135 | 147 |
# trigger rendering values for second row as hidden, because they |
136 | 148 |
# are loaded only on demand. So we need to keep the values from |
... | ... | |
138 | 150 |
$_->{render_second_row} = 1 for @{ $self->order->items_sorted }; |
139 | 151 |
|
140 | 152 |
if (!$::form->{form_validity_token}) { |
141 |
$::form->{form_validity_token} = SL::DB::ValidityToken->create(scope => SL::DB::ValidityToken::SCOPE_DELIVERY_ORDER_SAVE())->token; |
|
153 |
$::form->{form_validity_token} = SL::DB::ValidityToken->create( |
|
154 |
scope => SL::DB::ValidityToken::SCOPE_DELIVERY_ORDER_SAVE() |
|
155 |
)->token; |
|
142 | 156 |
} |
143 | 157 |
} |
144 | 158 |
|
... | ... | |
200 | 214 |
$self->order($new_order); |
201 | 215 |
|
202 | 216 |
if (!$::form->{form_validity_token}) { |
203 |
$::form->{form_validity_token} = SL::DB::ValidityToken->create(scope => SL::DB::ValidityToken::SCOPE_DELIVERY_ORDER_SAVE())->token; |
|
217 |
$::form->{form_validity_token} = SL::DB::ValidityToken->create( |
|
218 |
scope => SL::DB::ValidityToken::SCOPE_DELIVERY_ORDER_SAVE() |
|
219 |
)->token; |
|
204 | 220 |
} |
205 | 221 |
|
206 | 222 |
# save |
... | ... | |
229 | 245 |
|
230 | 246 |
# only pdf and opendocument by now |
231 | 247 |
if (none { $format eq $_ } qw(pdf opendocument opendocument_pdf)) { |
232 |
return $self->js->flash('error', t8('Format \'#1\' is not supported yet/anymore.', $format))->render; |
|
248 |
return $self->js->flash('error', |
|
249 |
t8('Format \'#1\' is not supported yet/anymore.', $format) |
|
250 |
)->render; |
|
233 | 251 |
} |
234 | 252 |
|
235 | 253 |
# only screen or printer by now |
236 | 254 |
if (none { $media eq $_ } qw(screen printer)) { |
237 |
return $self->js->flash('error', t8('Media \'#1\' is not supported yet/anymore.', $media))->render; |
|
255 |
return $self->js->flash('error', |
|
256 |
t8('Media \'#1\' is not supported yet/anymore.', $media) |
|
257 |
)->render; |
|
238 | 258 |
} |
239 | 259 |
|
240 | 260 |
# create a form for generate_attachment_filename |
... | ... | |
243 | 263 |
$form->{type} = $self->type; |
244 | 264 |
$form->{format} = $format; |
245 | 265 |
$form->{formname} = $formname; |
246 |
$form->{language} = '_' . $self->order->language->template_code if $self->order->language; |
|
266 |
$form->{language} = |
|
267 |
'_' . $self->order->language->template_code if $self->order->language; |
|
247 | 268 |
my $pdf_filename = $form->generate_attachment_filename(); |
248 | 269 |
$main::lxdebug->dump(0, 'WH: FoRM ', $form); |
249 | 270 |
$main::lxdebug->dump(0, 'WH: PDF ', $pdf_filename); |
250 | 271 |
my $pdf; |
251 |
my @errors = generate_pdf($self->order, \$pdf, { format => $format, |
|
252 |
formname => $formname, |
|
253 |
language => $self->order->language, |
|
254 |
printer_id => $printer_id, |
|
255 |
groupitems => $groupitems }); |
|
272 |
my @errors = generate_pdf($self->order, \$pdf, { |
|
273 |
format => $format, |
|
274 |
formname => $formname, |
|
275 |
language => $self->order->language, |
|
276 |
printer_id => $printer_id, |
|
277 |
groupitems => $groupitems |
|
278 |
}); |
|
256 | 279 |
if (scalar @errors) { |
257 |
return $self->js->flash('error', t8('Conversion to PDF failed: #1', $errors[0]))->render; |
|
280 |
return $self->js->flash('error', |
|
281 |
t8('Conversion to PDF failed: #1', $errors[0]) |
|
282 |
)->render; |
|
258 | 283 |
} |
259 | 284 |
|
260 | 285 |
if ($media eq 'screen') { |
... | ... | |
278 | 303 |
$self->js->flash('info', t8('The PDF has been printed')); |
279 | 304 |
} |
280 | 305 |
|
281 |
my @warnings = store_pdf_to_webdav_and_filemanagement($self->order, $pdf, $pdf_filename); |
|
306 |
my @warnings = store_pdf_to_webdav_and_filemanagement( |
|
307 |
$self->order, $pdf, $pdf_filename |
|
308 |
); |
|
282 | 309 |
if (scalar @warnings) { |
283 | 310 |
$self->js->flash('warning', $_) for @warnings; |
284 | 311 |
} |
... | ... | |
308 | 335 |
$form->{type} = $self->type; |
309 | 336 |
$form->{format} = $format; |
310 | 337 |
$form->{formname} = $formname; |
311 |
$form->{language} = '_' . $self->order->language->template_code if $self->order->language; |
|
338 |
$form->{language} = |
|
339 |
'_' . $self->order->language->template_code if $self->order->language; |
|
312 | 340 |
my $pdf_filename = $form->generate_attachment_filename(); |
313 | 341 |
|
314 | 342 |
my $pdf; |
315 |
my @errors = generate_pdf($self->order, \$pdf, { format => $format, |
|
316 |
formname => $formname, |
|
317 |
language => $self->order->language, |
|
318 |
}); |
|
343 |
my @errors = generate_pdf($self->order, \$pdf, { |
|
344 |
format => $format, |
|
345 |
formname => $formname, |
|
346 |
language => $self->order->language, |
|
347 |
}); |
|
319 | 348 |
if (scalar @errors) { |
320 |
return $self->js->flash('error', t8('Conversion to PDF failed: #1', $errors[0]))->render; |
|
349 |
return $self->js->flash('error', |
|
350 |
t8('Conversion to PDF failed: #1', $errors[0]) |
|
351 |
)->render; |
|
321 | 352 |
} |
322 | 353 |
$self->save_history('PREVIEWED'); |
323 | 354 |
$self->js->flash('info', t8('The PDF has been previewed')); |
... | ... | |
341 | 372 |
my $cv_method = $self->cv; |
342 | 373 |
|
343 | 374 |
if (!$self->order->$cv_method) { |
344 |
return $self->js->flash('error', $self->cv eq 'customer' ? t8('Cannot send E-mail without customer given') : t8('Cannot send E-mail without vendor given')) |
|
345 |
->render($self); |
|
375 |
return $self->js->flash('error', |
|
376 |
$self->cv eq 'customer' ? |
|
377 |
t8('Cannot send E-mail without customer given') |
|
378 |
: t8('Cannot send E-mail without vendor given') |
|
379 |
)->render($self); |
|
346 | 380 |
} |
347 | 381 |
|
348 | 382 |
my $email_form; |
... | ... | |
357 | 391 |
$form->{cusordnumber} = $self->order->cusordnumber; |
358 | 392 |
$form->{formname} = $self->type; |
359 | 393 |
$form->{type} = $self->type; |
360 |
$form->{language} = '_' . $self->order->language->template_code if $self->order->language; |
|
361 |
$form->{language_id} = $self->order->language->id if $self->order->language; |
|
394 |
$form->{language} = |
|
395 |
'_' . $self->order->language->template_code if $self->order->language; |
|
396 |
$form->{language_id} = |
|
397 |
$self->order->language->id if $self->order->language; |
|
362 | 398 |
$form->{format} = 'pdf'; |
363 |
$form->{cp_id} = $self->order->contact->cp_id if $self->order->contact; |
|
399 |
$form->{cp_id} = |
|
400 |
$self->order->contact->cp_id if $self->order->contact; |
|
364 | 401 |
|
365 | 402 |
$email_form->{subject} = $form->generate_email_subject(); |
366 | 403 |
$email_form->{attachment_filename} = $form->generate_attachment_filename(); |
... | ... | |
368 | 405 |
$email_form->{js_send_function} = 'kivi.DeliveryOrder.send_email()'; |
369 | 406 |
|
370 | 407 |
my %files = $self->get_files_for_email_dialog(); |
371 |
$self->{all_employees} = SL::DB::Manager::Employee->get_all(query => [ deleted => 0 ]); |
|
372 |
my $dialog_html = $self->render('common/_send_email_dialog', { output => 0 }, |
|
373 |
email_form => $email_form, |
|
374 |
show_bcc => $::auth->assert('email_bcc', 'may fail'), |
|
375 |
FILES => \%files, |
|
376 |
is_customer => $self->type_data->properties("is_customer"), |
|
377 |
ALL_EMPLOYEES => $self->{all_employees}, |
|
408 |
$self->{all_employees} = SL::DB::Manager::Employee->get_all( |
|
409 |
query => [ deleted => 0 ] |
|
410 |
); |
|
411 |
my $dialog_html = $self->render( |
|
412 |
'common/_send_email_dialog', { output => 0 }, |
|
413 |
email_form => $email_form, |
|
414 |
show_bcc => $::auth->assert('email_bcc', 'may fail'), |
|
415 |
FILES => \%files, |
|
416 |
is_customer => $self->type_data->properties("is_customer"), |
|
417 |
ALL_EMPLOYEES => $self->{all_employees}, |
|
378 | 418 |
); |
379 | 419 |
|
380 | 420 |
$self->js |
... | ... | |
408 | 448 |
|
409 | 449 |
if (($::form->{attachment_policy} // '') !~ m{^(?:old_file|no_file)$}) { |
410 | 450 |
my $pdf; |
411 |
my @errors = generate_pdf($self->order, \$pdf, {media => $::form->{media}, |
|
412 |
format => $::form->{print_options}->{format}, |
|
413 |
formname => $::form->{print_options}->{formname}, |
|
414 |
language => $self->order->language, |
|
415 |
printer_id => $::form->{print_options}->{printer_id}, |
|
416 |
groupitems => $::form->{print_options}->{groupitems}}); |
|
451 |
my @errors = generate_pdf($self->order, \$pdf, { |
|
452 |
media => $::form->{media}, |
|
453 |
format => $::form->{print_options}->{format}, |
|
454 |
formname => $::form->{print_options}->{formname}, |
|
455 |
language => $self->order->language, |
|
456 |
printer_id => $::form->{print_options}->{printer_id}, |
|
457 |
groupitems => $::form->{print_options}->{groupitems}}, |
|
458 |
); |
|
417 | 459 |
if (scalar @errors) { |
418 |
return $self->js->flash('error', t8('Conversion to PDF failed: #1', $errors[0]))->render($self); |
|
460 |
return $self->js->flash('error', |
|
461 |
t8('Conversion to PDF failed: #1', $errors[0]) |
|
462 |
)->render($self); |
|
419 | 463 |
} |
420 | 464 |
|
421 |
my @warnings = store_pdf_to_webdav_and_filemanagement($self->order, $pdf, $::form->{attachment_filename}); |
|
465 |
my @warnings = store_pdf_to_webdav_and_filemanagement( |
|
466 |
$self->order, $pdf, $::form->{attachment_filename} |
|
467 |
); |
|
422 | 468 |
if (scalar @warnings) { |
423 | 469 |
flash_later('warning', $_) for @warnings; |
424 | 470 |
} |
... | ... | |
428 | 474 |
$sfile->fh->close; |
429 | 475 |
|
430 | 476 |
$::form->{tmpfile} = $sfile->file_name; |
431 |
$::form->{tmpdir} = $sfile->get_path; # for Form::cleanup which may be called in Form::send_email |
|
477 |
$::form->{tmpdir} = $sfile->get_path; # for Form::cleanup which may be |
|
478 |
# called in Form::send_email |
|
432 | 479 |
} |
433 | 480 |
|
434 |
$::form->{id} = $self->order->id; # this is used in SL::Mailer to create a linked record to the mail |
|
481 |
$::form->{id} = $self->order->id; # this is used in SL::Mailer to create a |
|
482 |
# linked record to the mail |
|
435 | 483 |
$::form->send_email(\%::myconfig, 'pdf'); |
436 | 484 |
|
437 | 485 |
# internal notes unless no email journal |
... | ... | |
439 | 487 |
|
440 | 488 |
my $intnotes = $self->order->intnotes; |
441 | 489 |
$intnotes .= "\n\n" if $self->order->intnotes; |
442 |
$intnotes .= t8('[email]') . "\n"; |
|
443 |
$intnotes .= t8('Date') . ": " . $::locale->format_date_object(DateTime->now_local, precision => 'seconds') . "\n"; |
|
444 |
$intnotes .= t8('To (email)') . ": " . $::form->{email} . "\n"; |
|
445 |
$intnotes .= t8('Cc') . ": " . $::form->{cc} . "\n" if $::form->{cc}; |
|
446 |
$intnotes .= t8('Bcc') . ": " . $::form->{bcc} . "\n" if $::form->{bcc}; |
|
447 |
$intnotes .= t8('Subject') . ": " . $::form->{subject} . "\n\n"; |
|
490 |
$intnotes .= t8('[email]') . "\n"; |
|
491 |
$intnotes .= t8('Date') . ": " . |
|
492 |
$::locale->format_date_object( |
|
493 |
DateTime->now_local, precision => 'seconds' |
|
494 |
) . "\n"; |
|
495 |
$intnotes .= t8('To (email)') . ": " . $::form->{email} . "\n"; |
|
496 |
$intnotes .= t8('Cc') . ": " . $::form->{cc} . "\n" if $::form->{cc}; |
|
497 |
$intnotes .= t8('Bcc') . ": " . $::form->{bcc} . "\n" if $::form->{bcc}; |
|
498 |
$intnotes .= t8('Subject') . ": " . $::form->{subject} . "\n\n"; |
|
448 | 499 |
$intnotes .= t8('Message') . ": " . $::form->{message}; |
449 | 500 |
|
450 | 501 |
$self->order->update_attributes(intnotes => $intnotes); |
... | ... | |
507 | 558 |
sub action_customer_vendor_changed { |
508 | 559 |
my ($self) = @_; |
509 | 560 |
|
510 |
$self->order(SL::Model::Record->update_after_customer_vendor_change($self->order)); |
|
561 |
$self->order( |
|
562 |
SL::Model::Record->update_after_customer_vendor_change($self->order) |
|
563 |
); |
|
511 | 564 |
|
512 | 565 |
my $cv_method = $self->cv; |
513 | 566 |
|
514 |
if ($self->order->$cv_method->contacts && scalar @{ $self->order->$cv_method->contacts } > 0) { |
|
567 |
if ( $self->order->$cv_method->contacts |
|
568 |
&& scalar @{ $self->order->$cv_method->contacts } > 0) { |
|
515 | 569 |
$self->js->show('#cp_row'); |
516 | 570 |
} else { |
517 | 571 |
$self->js->hide('#cp_row'); |
... | ... | |
575 | 629 |
$_[0]->render('common/show_vc_details', { layout => 0 }, |
576 | 630 |
is_customer => $is_customer, |
577 | 631 |
%details); |
578 |
|
|
579 | 632 |
} |
580 | 633 |
|
581 | 634 |
# called if a unit in an existing item row is changed |
... | ... | |
588 | 641 |
my $old_unit_obj = SL::DB::Unit->new(name => $::form->{old_unit})->load; |
589 | 642 |
$item->sellprice($item->unit_obj->convert_to($item->sellprice, $old_unit_obj)); |
590 | 643 |
|
591 |
$self->js |
|
592 |
->run('kivi.DeliveryOrder.update_sellprice', $::form->{item_id}, $item->sellprice_as_number); |
|
644 |
$self->js->run( |
|
645 |
'kivi.DeliveryOrder.update_sellprice', |
|
646 |
$::form->{item_id}, |
|
647 |
$item->sellprice_as_number |
|
648 |
); |
|
593 | 649 |
$self->js_redisplay_line_values; |
594 | 650 |
$self->js->render(); |
595 | 651 |
} |
... | ... | |
620 | 676 |
|
621 | 677 |
if ($::form->{insert_before_item_id}) { |
622 | 678 |
$self->js |
623 |
->before ('.row_entry:has(#item_' . $::form->{insert_before_item_id} . ')', $row_as_html); |
|
679 |
->before( |
|
680 |
'.row_entry:has(#item_' . $::form->{insert_before_item_id} . ')', |
|
681 |
$row_as_html |
|
682 |
); |
|
624 | 683 |
} else { |
625 | 684 |
$self->js |
626 | 685 |
->append('#row_table_id', $row_as_html); |
... | ... | |
629 | 688 |
if ( $item->part->is_assortment ) { |
630 | 689 |
$form_attr->{qty_as_number} = 1 unless $form_attr->{qty_as_number}; |
631 | 690 |
foreach my $assortment_item ( @{$item->part->assortment_items} ) { |
632 |
my $attr = { parts_id => $assortment_item->parts_id, |
|
633 |
qty => $assortment_item->qty * $::form->parse_amount(\%::myconfig, $form_attr->{qty_as_number}), # TODO $form_attr->{unit} |
|
634 |
unit => $assortment_item->unit, |
|
635 |
description => $assortment_item->part->description, |
|
636 |
}; |
|
691 |
my $attr = { |
|
692 |
parts_id => $assortment_item->parts_id, |
|
693 |
qty => $assortment_item->qty * |
|
694 |
$::form->parse_amount(\%::myconfig, $form_attr->{qty_as_number}), # TODO $form_attr->{unit} |
|
695 |
unit => $assortment_item->unit, |
|
696 |
description => $assortment_item->part->description, |
|
697 |
}; |
|
637 | 698 |
my $item = new_item($self->order, $attr); |
638 | 699 |
|
639 |
# set discount to 100% if item isn't supposed to be charged, overwriting any customer discount |
|
700 |
# set discount to 100% if item isn't supposed to be charged, overwriting |
|
701 |
# any customer discount |
|
640 | 702 |
$item->discount(1) unless $assortment_item->charge; |
641 | 703 |
|
642 | 704 |
$self->order->add_items( $item ); |
643 | 705 |
$self->get_item_cvpartnumber($item); |
644 |
my $item_id = join('_', 'new', Time::HiRes::gettimeofday(), int rand 1000000000000); |
|
706 |
my $item_id = join('_', |
|
707 |
'new', |
|
708 |
Time::HiRes::gettimeofday(), |
|
709 |
int rand 1000000000000 |
|
710 |
); |
|
645 | 711 |
my $row_as_html = $self->p->render('delivery_order/tabs/_row', |
646 | 712 |
ITEM => $item, |
647 | 713 |
ID => $item_id, |
... | ... | |
649 | 715 |
); |
650 | 716 |
if ($::form->{insert_before_item_id}) { |
651 | 717 |
$self->js |
652 |
->before ('.row_entry:has(#item_' . $::form->{insert_before_item_id} . ')', $row_as_html); |
|
718 |
->before( |
|
719 |
'.row_entry:has(#item_' . $::form->{insert_before_item_id} . ')', |
|
720 |
$row_as_html |
|
721 |
); |
|
653 | 722 |
} else { |
654 | 723 |
$self->js |
655 | 724 |
->append('#row_table_id', $row_as_html); |
... | ... | |
681 | 750 |
push @items, $item; |
682 | 751 |
if ( $item->part->is_assortment ) { |
683 | 752 |
foreach my $assortment_item ( @{$item->part->assortment_items} ) { |
684 |
my $attr = { parts_id => $assortment_item->parts_id, |
|
685 |
qty => $assortment_item->qty * $item->qty, # TODO $form_attr->{unit} |
|
686 |
unit => $assortment_item->unit, |
|
687 |
description => $assortment_item->part->description, |
|
688 |
}; |
|
753 |
my $attr = { |
|
754 |
parts_id => $assortment_item->parts_id, |
|
755 |
qty => $assortment_item->qty * $item->qty, # TODO $form_attr->{unit} |
|
756 |
unit => $assortment_item->unit, |
|
757 |
description => $assortment_item->part->description, |
|
758 |
}; |
|
689 | 759 |
my $item = new_item($self->order, $attr); |
690 | 760 |
|
691 |
# set discount to 100% if item isn't supposed to be charged, overwriting any customer discount |
|
761 |
# set discount to 100% if item isn't supposed to be charged, overwriting |
|
762 |
# any customer discount |
|
692 | 763 |
$item->discount(1) unless $assortment_item->charge; |
693 | 764 |
push @items, $item; |
694 | 765 |
} |
... | ... | |
698 | 769 |
|
699 | 770 |
foreach my $item (@items) { |
700 | 771 |
$self->get_item_cvpartnumber($item); |
701 |
my $item_id = join('_', 'new', Time::HiRes::gettimeofday(), int rand 1000000000000); |
|
772 |
my $item_id = join('_', |
|
773 |
'new', |
|
774 |
Time::HiRes::gettimeofday(), |
|
775 |
int rand 1000000000000 |
|
776 |
); |
|
702 | 777 |
my $row_as_html = $self->p->render('delivery_order/tabs/_row', |
703 |
ITEM => $item,
|
|
704 |
ID => $item_id,
|
|
705 |
SELF => $self,
|
|
706 |
in_out => $self->type_data->properties("transfer"),
|
|
778 |
ITEM => $item, |
|
779 |
ID => $item_id, |
|
780 |
SELF => $self, |
|
781 |
in_out => $self->type_data->properties("transfer"), |
|
707 | 782 |
); |
708 | 783 |
|
709 | 784 |
if ($::form->{insert_before_item_id}) { |
710 | 785 |
$self->js |
711 |
->before ('.row_entry:has(#item_' . $::form->{insert_before_item_id} . ')', $row_as_html); |
|
786 |
->before( |
|
787 |
'.row_entry:has(#item_' . $::form->{insert_before_item_id} . ')', |
|
788 |
$row_as_html |
|
789 |
); |
|
712 | 790 |
} else { |
713 | 791 |
$self->js |
714 | 792 |
->append('#row_table_id', $row_as_html); |
... | ... | |
753 | 831 |
$self->get_item_cvpartnumber($_) for @{$self->order->items_sorted}; |
754 | 832 |
|
755 | 833 |
my $method = $sort_keys{$::form->{order_by}}; |
756 |
my @to_sort = map { { old_pos => $_->position, order_by => $method->($_) } } @{ $self->order->items_sorted }; |
|
834 |
my @to_sort = |
|
835 |
map { { old_pos => $_->position, order_by => $method->($_) } } |
|
836 |
@{ $self->order->items_sorted }; |
|
757 | 837 |
if ($::form->{sort_dir}) { |
758 | 838 |
if ( $::form->{order_by} =~ m/qty|sellprice|discount/ ){ |
759 | 839 |
@to_sort = sort { $a->{order_by} <=> $b->{order_by} } @to_sort; |
... | ... | |
784 | 864 |
previousform => $previousform, |
785 | 865 |
); |
786 | 866 |
|
787 |
flash_later('info', t8('You are adding a new part while you are editing another document. You will be redirected to your document when saving the new part or aborting this form.')); |
|
867 |
flash_later('info', |
|
868 |
t8('You are adding a new part while you are editing another document. You will be redirected to your document when saving the new part or aborting this form.') |
|
869 |
); |
|
788 | 870 |
|
789 | 871 |
my @redirect_params = ( |
790 | 872 |
controller => 'Part', |
... | ... | |
800 | 882 |
sub action_return_from_create_part { |
801 | 883 |
my ($self) = @_; |
802 | 884 |
|
803 |
$self->{created_part} = SL::DB::Part->new(id => delete $::form->{new_parts_id})->load if $::form->{new_parts_id}; |
|
885 |
$self->{created_part} = |
|
886 |
SL::DB::Part->new(id => delete $::form->{new_parts_id})->load |
|
887 |
if $::form->{new_parts_id}; |
|
804 | 888 |
|
805 | 889 |
$::auth->restore_form_from_session(delete $::form->{previousform}); |
806 | 890 |
|
807 | 891 |
# set item ids to new fake id, to identify them as new items |
808 | 892 |
foreach my $item (@{$self->order->items_sorted}) { |
809 |
$item->{new_fake_id} = join('_', 'new', Time::HiRes::gettimeofday(), int rand 1000000000000); |
|
893 |
$item->{new_fake_id} = join('_', |
|
894 |
'new', |
|
895 |
Time::HiRes::gettimeofday(), |
|
896 |
int rand 1000000000000 |
|
897 |
); |
|
810 | 898 |
} |
811 | 899 |
|
812 | 900 |
$self->get_unalterable_data(); |
... | ... | |
823 | 911 |
title => $self->get_title_for('edit'), |
824 | 912 |
%{$self->{template_args}} |
825 | 913 |
); |
826 |
|
|
827 | 914 |
} |
828 | 915 |
|
829 | 916 |
sub action_stock_in_out_dialog { |
... | ... | |
887 | 974 |
stock_info => $yaml, |
888 | 975 |
stock_qty => $stock_qty, |
889 | 976 |
}; |
890 |
$self->render(\ SL::JSON::to_json($response), { layout => 0, type => 'json', process => 0 }); |
|
977 |
$self->render( |
|
978 |
\ SL::JSON::to_json($response), |
|
979 |
{ layout => 0, type => 'json', process => 0 } |
|
980 |
); |
|
891 | 981 |
} |
892 | 982 |
|
893 | 983 |
sub merge_stock_data { |
... | ... | |
897 | 987 |
if (!$self->order->delivered) { |
898 | 988 |
for my $row (@$contents) { |
899 | 989 |
# row here is in parts units. stock is in item units |
900 |
$row->{available_qty} = _format_number($part->unit_obj->convert_to($row->{qty}, $unit)); |
|
990 |
$row->{available_qty} = _format_number( |
|
991 |
$part->unit_obj->convert_to($row->{qty}, $unit) |
|
992 |
); |
|
901 | 993 |
|
902 | 994 |
for my $sinfo (@{ $stock_info }) { |
903 | 995 |
next if $row->{bin_id} != $sinfo->{bin_id} || |
... | ... | |
934 | 1026 |
$self->js_load_second_row($item, $item_id, 0); |
935 | 1027 |
} |
936 | 1028 |
|
937 |
$self->js->run('kivi.DeliveryOrder.init_row_handlers') if $self->order->is_sales; # for lastcosts change-callback |
|
1029 |
# for lastcosts change-callback |
|
1030 |
$self->js->run('kivi.DeliveryOrder.init_row_handlers') if $self->order->is_sales; |
|
938 | 1031 |
|
939 | 1032 |
$self->js->render(); |
940 | 1033 |
} |
... | ... | |
962 | 1055 |
$price_src = $price_source->best_price |
963 | 1056 |
? $price_source->best_price |
964 | 1057 |
: $price_source->price_from_source(""); |
965 |
$price_src->price($::form->round_amount($price_src->price / $self->order->exchangerate, 5)) if $self->order->exchangerate; |
|
1058 |
$price_src->price( |
|
1059 |
$::form->round_amount($price_src->price / $self->order->exchangerate, 5) |
|
1060 |
) if $self->order->exchangerate; |
|
966 | 1061 |
$price_src->price(0) if !$price_source->best_price; |
967 | 1062 |
} |
968 | 1063 |
|
969 |
|
|
970 | 1064 |
$item->sellprice($price_src->price); |
971 | 1065 |
$item->active_price_source($price_src); |
972 | 1066 |
|
... | ... | |
991 | 1085 |
my ($self) = @_; |
992 | 1086 |
|
993 | 1087 |
if ($self->order->delivered) { |
994 |
return $self->js->flash("error", t8('The parts for this order have already been transferred'))->render; |
|
1088 |
return $self->js->flash("error", |
|
1089 |
t8('The parts for this order have already been transferred') |
|
1090 |
)->render; |
|
995 | 1091 |
} |
996 | 1092 |
|
997 | 1093 |
my $inout = $self->type_data->properties('transfer'); |
... | ... | |
1007 | 1103 |
|
1008 | 1104 |
# TODO move to type data |
1009 | 1105 |
my $trans_type = $inout eq 'in' |
1010 |
? SL::DB::Manager::TransferType->find_by(direction => "in", description => "stock") |
|
1011 |
: SL::DB::Manager::TransferType->find_by(direction => "out", description => "shipped"); |
|
1106 |
? SL::DB::Manager::TransferType->find_by( |
|
1107 |
direction => "in", description => "stock") |
|
1108 |
: SL::DB::Manager::TransferType->find_by( |
|
1109 |
direction => "out", description => "shipped"); |
|
1012 | 1110 |
|
1013 | 1111 |
|
1014 | 1112 |
my @transfer_requests; |
... | ... | |
1036 | 1134 |
|
1037 | 1135 |
$self->js |
1038 | 1136 |
->flash("info", t8("Stock transfered")) |
1039 |
->run('kivi.ActionBar.setDisabled', '#transfer_out_action', t8('The parts for this order have already been transferred')) |
|
1040 |
->run('kivi.ActionBar.setDisabled', '#transfer_in_action', t8('The parts for this order have already been transferred')) |
|
1041 |
->run('kivi.ActionBar.setDisabled', '#delete_action', t8('The parts for this order have already been transferred')) |
|
1042 |
->run('kivi.ActionBar.setEnabled', '#undo_transfer_action', t8('The parts for this order have already been transferred')) |
|
1137 |
->run('kivi.ActionBar.setDisabled', '#transfer_out_action', |
|
1138 |
t8('The parts for this order have already been transferred')) |
|
1139 |
->run('kivi.ActionBar.setDisabled', '#transfer_in_action', |
|
1140 |
t8('The parts for this order have already been transferred')) |
|
1141 |
->run('kivi.ActionBar.setDisabled', '#delete_action', |
|
1142 |
t8('The parts for this order have already been transferred')) |
|
1143 |
->run('kivi.ActionBar.setEnabled', '#undo_transfer_action', |
|
1144 |
t8('The parts for this order have already been transferred')) |
|
1043 | 1145 |
->replaceWith('#data-status-line', delivery_order_status_line($self->order)) |
1044 | 1146 |
->render; |
1045 |
|
|
1046 | 1147 |
} |
1047 | 1148 |
|
1048 | 1149 |
sub action_undo_transfers { |
... | ... | |
1067 | 1168 |
); |
1068 | 1169 |
|
1069 | 1170 |
$self->redirect_to(@redirect_params); |
1070 |
|
|
1071 | 1171 |
} |
1072 | 1172 |
|
1073 | 1173 |
sub js_load_second_row { |
... | ... | |
1140 | 1240 |
next if !$self->order->items_sorted->[$idx]->id; |
1141 | 1241 |
next if $form_item_id !~ m{^new}; |
1142 | 1242 |
$self->js |
1143 |
->val ('[name="orderitem_ids[+]"][value="' . $form_item_id . '"]', $self->order->items_sorted->[$idx]->id) |
|
1243 |
->val ( |
|
1244 |
'[name="orderitem_ids[+]"][value="' . $form_item_id . '"]', |
|
1245 |
$self->order->items_sorted->[$idx]->id) |
|
1144 | 1246 |
->val ('#item_' . $form_item_id, $self->order->items_sorted->[$idx]->id) |
1145 |
->attr('#item_' . $form_item_id, "id", 'item_' . $self->order->items_sorted->[$idx]->id); |
|
1247 |
->attr('#item_' . $form_item_id, "id", |
|
1248 |
'item_' . $self->order->items_sorted->[$idx]->id); |
|
1146 | 1249 |
} continue { |
1147 | 1250 |
$idx++; |
1148 | 1251 |
} |
... | ... | |
1198 | 1301 |
sub init_part_picker_classification_ids { |
1199 | 1302 |
my ($self) = @_; |
1200 | 1303 |
|
1201 |
return [ map { $_->id } @{ SL::DB::Manager::PartClassification->get_all(where => $self->type_data->part_classification_query) } ]; |
|
1304 |
return [ map { $_->id } @{ SL::DB::Manager::PartClassification->get_all( |
|
1305 |
where => $self->type_data->part_classification_query |
|
1306 |
) } ]; |
|
1202 | 1307 |
} |
1203 | 1308 |
|
1204 | 1309 |
sub check_auth { |
... | ... | |
1235 | 1340 |
my ($self) = @_; |
1236 | 1341 |
|
1237 | 1342 |
select_tag('order.shipto_id', |
1238 |
[ {displayable_id => t8("No/individual shipping address"), shipto_id => ''}, $self->order->{$self->cv}->shipto ], |
|
1343 |
[ { |
|
1344 |
displayable_id => t8("No/individual shipping address"), |
|
1345 |
shipto_id => '' |
|
1346 |
}, |
|
1347 |
$self->order->{$self->cv}->shipto ], |
|
1239 | 1348 |
value_key => 'shipto_id', |
1240 | 1349 |
title_key => 'displayable_id', |
1241 | 1350 |
default => $self->order->shipto_id, |
... | ... | |
1297 | 1406 |
# be retrieved via items until the order is saved. Adding empty items to new |
1298 | 1407 |
# order here solves this problem. |
1299 | 1408 |
my $order; |
1300 |
$order = SL::DB::DeliveryOrder->new(id => $::form->{id})->load(with => [ 'orderitems', 'orderitems.part' ]) if $::form->{id}; |
|
1301 |
$order ||= SL::DB::DeliveryOrder->new(orderitems => [], currency_id => $::instance_conf->get_currency_id(), order_type => $self->type_data->validate); |
|
1409 |
if ($::form->{id}) { |
|
1410 |
$order = SL::DB::DeliveryOrder->new( |
|
1411 |
id => $::form->{id} |
|
1412 |
)->load(with => [ 'orderitems', 'orderitems.part' ]); |
|
1413 |
} else { |
|
1414 |
$order = SL::DB::DeliveryOrder->new( |
|
1415 |
orderitems => [], |
|
1416 |
currency_id => $::instance_conf->get_currency_id(), |
|
1417 |
order_type => $self->type_data->validate |
|
1418 |
); |
|
1419 |
} |
|
1302 | 1420 |
|
1303 | 1421 |
my $cv_id_method = $self->cv . '_id'; |
1304 | 1422 |
if (!$::form->{id} && $::form->{$cv_id_method}) { |
... | ... | |
1306 | 1424 |
$order = SL::Model::Record->update_after_customer_vendor_change($order); |
1307 | 1425 |
} |
1308 | 1426 |
|
1309 |
my $form_orderitems = delete $::form->{order}->{orderitems};
|
|
1427 |
my $form_orderitems = delete $::form->{order}->{orderitems}; |
|
1310 | 1428 |
|
1311 | 1429 |
$order->assign_attributes(%{$::form->{order}}); |
1312 | 1430 |
|
... | ... | |
1403 | 1521 |
$item->assign_attributes(%$attr); |
1404 | 1522 |
|
1405 | 1523 |
my $part = SL::DB::Part->new(id => $attr->{parts_id})->load; |
1406 |
my $price_source = SL::PriceSource->new(record_item => $item, record => $record); |
|
1524 |
my $price_source = SL::PriceSource->new( |
|
1525 |
record_item => $item, |
|
1526 |
record => $record, |
|
1527 |
); |
|
1407 | 1528 |
|
1408 | 1529 |
$item->unit($part->unit) if !$item->unit; |
1409 | 1530 |
|
... | ... | |
1451 | 1572 |
# saved. Adding empty custom_variables to new orderitem here solves this problem. |
1452 | 1573 |
$new_attr{custom_variables} = []; |
1453 | 1574 |
|
1454 |
my $texts = get_part_texts($part, $record->language_id, description => $new_attr{description}, longdescription => $new_attr{longdescription}); |
|
1575 |
my $texts = get_part_texts( |
|
1576 |
$part, $record->language_id, |
|
1577 |
description => $new_attr{description}, |
|
1578 |
longdescription => $new_attr{longdescription} |
|
1579 |
); |
|
1455 | 1580 |
|
1456 | 1581 |
$item->assign_attributes(%new_attr, %{ $texts }); |
1457 | 1582 |
|
... | ... | |
1472 | 1597 |
if ($order->shipto) { |
1473 | 1598 |
$self->is_custom_shipto_to_delete(1); |
1474 | 1599 |
} else { |
1475 |
my $custom_shipto = $order->custom_shipto || $order->custom_shipto(SL::DB::Shipto->new(module => 'OE', custom_variables => [])); |
|
1476 |
|
|
1477 |
my $shipto_cvars = {map { my ($key) = m{^shiptocvar_(.+)}; $key => delete $form->{$_}} grep { m{^shiptocvar_} } keys %$form}; |
|
1478 |
my $shipto_attrs = {map { $_ => delete $form->{$_}} grep { m{^shipto} } keys %$form}; |
|
1600 |
my $custom_shipto = $order->custom_shipto ? |
|
1601 |
$order->custom_shipto |
|
1602 |
: $order->custom_shipto( |
|
1603 |
SL::DB::Shipto->new(module => 'OE', custom_variables => []) |
|
1604 |
); |
|
1605 |
|
|
1606 |
my $shipto_cvars = { |
|
1607 |
map { my ($key) = m{^shiptocvar_(.+)}; $key => delete $form->{$_}} |
|
1608 |
grep { m{^shiptocvar_} } |
|
1609 |
keys %$form |
|
1610 |
}; |
|
1611 |
my $shipto_attrs = { |
|
1612 |
map { $_ => delete $form->{$_}} |
|
1613 |
grep { m{^shipto} } |
|
1614 |
keys %$form |
|
1615 |
}; |
|
1479 | 1616 |
|
1480 | 1617 |
$custom_shipto->assign_attributes(%$shipto_attrs); |
1481 | 1618 |
$custom_shipto->cvar_by_name($_)->value($shipto_cvars->{$_}) for keys %$shipto_cvars; |
... | ... | |
1518 | 1655 |
); |
1519 | 1656 |
} |
1520 | 1657 |
if ($::form->{converted_from_reclamation_id}) { |
1521 |
my @converted_from_reclamation_ids = split ' ', $::form->{converted_from_reclamation_id}; |
|
1658 |
my @converted_from_reclamation_ids = |
|
1659 |
split ' ', $::form->{converted_from_reclamation_id}; |
|
1522 | 1660 |
set_record_link_conversions( |
1523 | 1661 |
$self->order, |
1524 | 1662 |
'SL::DB::Reclamation' => \@converted_from_reclamation_ids, |
... | ... | |
1531 | 1669 |
: undef; |
1532 | 1670 |
|
1533 | 1671 |
SL::Model::Record->save($self->order, |
1534 |
with_validity_token => { scope => SL::DB::ValidityToken::SCOPE_DELIVERY_ORDER_SAVE(), token => $::form->{form_validity_token} }, |
|
1535 |
delete_custom_shipto => $self->is_custom_shipto_to_delete || $self->order->custom_shipto->is_empty, |
|
1536 |
items_to_delete => $items_to_delete, |
|
1672 |
with_validity_token => { |
|
1673 |
scope => SL::DB::ValidityToken::SCOPE_DELIVERY_ORDER_SAVE(), |
|
1674 |
token => $::form->{form_validity_token} |
|
1675 |
}, |
|
1676 |
delete_custom_shipto => $self->is_custom_shipto_to_delete || $self->order->custom_shipto->is_empty, |
|
1677 |
items_to_delete => $items_to_delete, |
|
1537 | 1678 |
); |
1538 | 1679 |
|
1539 | 1680 |
delete $::form->{form_validity_token}; |
... | ... | |
1545 | 1686 |
# always save |
1546 | 1687 |
$self->save(); |
1547 | 1688 |
|
1548 |
my $delivery_order = SL::Model::Record->new_from_workflow($self->order, $destination_type); |
|
1549 |
$self->order($delivery_order); |
|
1550 |
$self->{converted_from_oe_id} = delete $::form->{id}; |
|
1551 |
|
|
1552 |
# set item ids to new fake id, to identify them as new items |
|
1553 |
foreach my $item (@{$self->order->items_sorted}) { |
|
1554 |
$item->{new_fake_id} = join('_', 'new', Time::HiRes::gettimeofday(), int rand 1000000000000); |
|
1555 |
} |
|
1556 |
|
|
1557 |
# change form type |
|
1558 |
$::form->{type} = $destination_type; |
|
1559 |
$self->type($self->init_type); |
|
1560 |
$self->cv ($self->init_cv); |
|
1561 |
$self->check_auth; |
|
1562 |
|
|
1563 |
$self->get_unalterable_data(); |
|
1564 |
$self->pre_render(); |
|
1565 |
|
|
1566 |
# trigger rendering values for second row as hidden, because they |
|
1567 |
# are loaded only on demand. So we need to keep the values from the |
|
1568 |
# source. |
|
1569 |
$_->{render_second_row} = 1 for @{ $self->order->items_sorted }; |
|
1570 |
|
|
1571 |
$self->render( |
|
1572 |
'delivery_order/form', |
|
1573 |
title => $self->get_title_for('edit'), |
|
1574 |
%{$self->{template_args}} |
|
1689 |
my $delivery_order = SL::Model::Record->new_from_workflow( |
|
1690 |
$self->order, $destination_type |
|
1575 | 1691 |
); |
1576 |
} |
|
1577 |
|
|
1578 |
sub workflow_sales_or_purchase_order { |
|
1579 |
my ($self, $destination_type) = @_; |
|
1580 |
|
|
1581 |
# always save |
|
1582 |
$self->save(); |
|
1583 |
|
|
1584 |
my $delivery_order = SL::Model::Record->new_from_workflow($self->order, $destination_type); |
|
1585 | 1692 |
$self->order($delivery_order); |
1586 | 1693 |
$self->{converted_from_oe_id} = delete $::form->{id}; |
1587 | 1694 |
|
1588 | 1695 |
# set item ids to new fake id, to identify them as new items |
1589 | 1696 |
foreach my $item (@{$self->order->items_sorted}) { |
1590 |
$item->{new_fake_id} = join('_', 'new', Time::HiRes::gettimeofday(), int rand 1000000000000); |
|
1697 |
$item->{new_fake_id} = join('_', |
|
1698 |
'new', |
|
1699 |
Time::HiRes::gettimeofday(), |
|
1700 |
int rand 1000000000000 |
|
1701 |
); |
|
1591 | 1702 |
} |
1592 | 1703 |
|
1593 | 1704 |
# change form type |
... | ... | |
1667 | 1778 |
|
1668 | 1779 |
$self->get_item_cvpartnumber($_) for @{$self->order->items_sorted}; |
1669 | 1780 |
|
1670 |
$::request->{layout}->use_javascript("${_}.js") for qw(kivi.SalesPurchase kivi.DeliveryOrder kivi.File |
|
1671 |
calculate_qty kivi.Validator follow_up show_history); |
|
1781 |
$::request->{layout}->use_javascript("${_}.js") for qw( |
|
1782 |
kivi.SalesPurchase kivi.DeliveryOrder kivi.File calculate_qty kivi.Validator |
|
1783 |
follow_up show_history |
|
1784 |
); |
|
1672 | 1785 |
$self->setup_edit_action_bar; |
1673 | 1786 |
} |
1674 | 1787 |
|
... | ... | |
1676 | 1789 |
my ($self, %params) = @_; |
1677 | 1790 |
|
1678 | 1791 |
my $deletion_allowed = $self->type_data->show_menu("delete"); |
1679 |
my $may_edit_create = $::auth->assert($self->type_data->rights('edit') || 'DOES_NOT_EXIST', 1); |
|
1792 |
my $may_edit_create = $::auth->assert( |
|
1793 |
$self->type_data->rights('edit') || 'DOES_NOT_EXIST', 1 |
|
1794 |
); |
|
1680 | 1795 |
|
1681 | 1796 |
for my $bar ($::request->layout->get('actionbar')) { |
1682 | 1797 |
$bar->add( |
... | ... | |
1747 | 1862 |
], |
1748 | 1863 |
action => [ |
1749 | 1864 |
t8('Save and print'), |
1750 |
call => [ 'kivi.DeliveryOrder.show_print_options', { warn_on_duplicates => $::instance_conf->get_order_warn_duplicate_parts, |
|
1751 |
warn_on_reqdate => $::instance_conf->get_order_warn_no_deliverydate }, |
|
1865 |
call => [ 'kivi.DeliveryOrder.show_print_options', { |
|
1866 |
warn_on_duplicates => $::instance_conf->get_order_warn_duplicate_parts, |
|
1867 |
warn_on_reqdate => $::instance_conf->get_order_warn_no_deliverydate }, |
|
1752 | 1868 |
], |
1753 | 1869 |
disabled => !$may_edit_create ? t8('You do not have the permissions to access this function.') : undef, |
1754 | 1870 |
], |
... | ... | |
1881 | 1997 |
); |
1882 | 1998 |
|
1883 | 1999 |
if (!defined $template_file) { |
1884 |
push @errors, $::locale->text('Cannot find matching template for this print request. Please contact your template maintainer. I tried these: #1.', join ', ', map { "'$_'"} @template_files); |
|
2000 |
push @errors, $::locale->text( |
|
2001 |
'Cannot find matching template for this print request. Please contact your template maintainer. I tried these: #1.', |
|
2002 |
join ', ', map { "'$_'"} @template_files |
|
2003 |
); |
|
1885 | 2004 |
} |
1886 | 2005 |
|
1887 | 2006 |
return @errors if scalar @errors; |
... | ... | |
1955 | 2074 |
return if !$self->order->customervendor; |
1956 | 2075 |
|
1957 | 2076 |
if ($self->cv eq 'vendor') { |
1958 |
my @mms = grep { $_->make eq $self->order->customervendor->id } @{$item->part->makemodels}; |
|
2077 |
my @mms = |
|
2078 |
grep { $_->make eq $self->order->customervendor->id } |
|
2079 |
@{$item->part->makemodels}; |
|
1959 | 2080 |
$item->{cvpartnumber} = $mms[0]->model if scalar @mms; |
1960 | 2081 |
} elsif ($self->cv eq 'customer') { |
1961 |
my @cps = grep { $_->customer_id eq $self->order->customervendor->id } @{$item->part->customerprices}; |
|
2082 |
my @cps = |
|
2083 |
grep { $_->customer_id eq $self->order->customervendor->id } |
|
2084 |
@{$item->part->customerprices}; |
|
1962 | 2085 |
$item->{cvpartnumber} = $cps[0]->customer_partnumber if scalar @cps; |
1963 | 2086 |
} |
1964 | 2087 |
} |
Auch abrufbar als: Unified diff
DeliveryOrder: Code formatiert (80 Zeichen Breite)