1 #=====================================================================
 
   4 # Based on SQL-Ledger Version 2.1.9
 
   5 # Web http://www.lx-office.org
 
   7 #=====================================================================
 
   8 # SQL-Ledger, Accounting
 
   9 # Copyright (c) 1998-2003
 
  11 #  Author: Dieter Simader
 
  12 #   Email: dsimader@sql-ledger.org
 
  13 #     Web: http://www.sql-ledger.org
 
  16 # This program is free software; you can redistribute it and/or modify
 
  17 # it under the terms of the GNU General Public License as published by
 
  18 # the Free Software Foundation; either version 2 of the License, or
 
  19 # (at your option) any later version.
 
  21 # This program is distributed in the hope that it will be useful,
 
  22 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  23 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  24 # GNU General Public License for more details.
 
  25 # You should have received a copy of the GNU General Public License
 
  26 # along with this program; if not, write to the Free Software
 
  27 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 
  29 #======================================================================
 
  32 #======================================================================
 
  35 use List::MoreUtils qw(uniq);
 
  36 use List::Util qw(max sum);
 
  37 use POSIX qw(strftime);
 
  40 use SL::DB::DeliveryOrder;
 
  44 use SL::MoreCommon qw(ary_diff restore_form save_form);
 
  45 use SL::ReportGenerator;
 
  47 use Sort::Naturally ();
 
  48 require "bin/mozilla/common.pl";
 
  49 require "bin/mozilla/io.pl";
 
  50 require "bin/mozilla/reportgenerator.pl";
 
  59   $main::auth->assert($main::form->{type} . '_edit');
 
  63   $main::lxdebug->enter_sub();
 
  69   my $form     = $main::form;
 
  70   my $locale   = $main::locale;
 
  72   if ($form->{type} eq 'purchase_delivery_order') {
 
  73     $form->{vc}    = 'vendor';
 
  74     $form->{title} = $action eq "edit" ? $locale->text('Edit Purchase Delivery Order') : $locale->text('Add Purchase Delivery Order');
 
  76     $form->{vc}    = 'customer';
 
  77     $form->{title} = $action eq "edit" ? $locale->text('Edit Sales Delivery Order') : $locale->text('Add Sales Delivery Order');
 
  80   $form->{heading} = $locale->text('Delivery Order');
 
  82   $main::lxdebug->leave_sub();
 
  86   $main::lxdebug->enter_sub();
 
  90   if (($::form->{type} =~ /purchase/) && !$::instance_conf->get_allow_new_purchase_invoice) {
 
  91     $::form->show_generic_error($::locale->text("You do not have the permissions to access this function."));
 
  94   my $form     = $main::form;
 
  98   $form->{show_details} = $::myconfig{show_form_details};
 
  99   $form->{callback} = build_std_url('action=add', 'type', 'vc') unless ($form->{callback});
 
 105   $main::lxdebug->leave_sub();
 
 109   $main::lxdebug->enter_sub();
 
 113   my $form     = $main::form;
 
 115   $form->{show_details} = $::myconfig{show_form_details};
 
 117   # show history button
 
 118   $form->{javascript} = qq|<script type="text/javascript" src="js/show_history.js"></script>|;
 
 119   #/show hhistory button
 
 121   $form->{simple_save} = 0;
 
 123   set_headings("edit");
 
 125   # editing without stuff to edit? try adding it first
 
 126   if ($form->{rowcount} && !$form->{print_and_save}) {
 
 127 #     map { $id++ if $form->{"multi_id_$_"} } (1 .. $form->{rowcount});
 
 131       undef $form->{rowcount};
 
 133       $main::lxdebug->leave_sub();
 
 136   } elsif (!$form->{id}) {
 
 138     $main::lxdebug->leave_sub();
 
 142   my ($language_id, $printer_id);
 
 143   if ($form->{print_and_save}) {
 
 144     $form->{action}   = "dispatcher";
 
 145     $form->{action_print} = "1";
 
 146     $form->{resubmit} = 1;
 
 147     $language_id      = $form->{language_id};
 
 148     $printer_id       = $form->{printer_id};
 
 151   set_headings("edit");
 
 156   if ($form->{print_and_save}) {
 
 157     $form->{language_id} = $language_id;
 
 158     $form->{printer_id}  = $printer_id;
 
 163   $main::lxdebug->leave_sub();
 
 167   $main::lxdebug->enter_sub();
 
 171   my $form     = $main::form;
 
 172   my %myconfig = %main::myconfig;
 
 174   # retrieve order/quotation
 
 175   my $editing = $form->{id};
 
 177   DO->retrieve('vc'  => $form->{vc},
 
 178                'ids' => $form->{id});
 
 180   $form->backup_vars(qw(payment_id language_id taxzone_id salesman_id taxincluded cp_id intnotes delivery_term_id currency));
 
 182   # get customer / vendor
 
 183   if ($form->{vc} eq 'vendor') {
 
 184     IR->get_vendor(\%myconfig, \%$form);
 
 185     $form->{discount} = $form->{vendor_discount};
 
 187     IS->get_customer(\%myconfig, \%$form);
 
 188     $form->{discount} = $form->{customer_discount};
 
 191   $form->restore_vars(qw(payment_id language_id taxzone_id intnotes cp_id delivery_term_id));
 
 192   $form->restore_vars(qw(currency)) if ($form->{id} || $form->{convert_from_oe_ids});
 
 193   $form->restore_vars(qw(taxincluded)) if $form->{id};
 
 194   $form->restore_vars(qw(salesman_id)) if $editing;
 
 196   $main::lxdebug->leave_sub();
 
 200   $main::lxdebug->enter_sub();
 
 204   my $form     = $main::form;
 
 205   my %myconfig = %main::myconfig;
 
 207   $form->{formname} = $form->{type} unless $form->{formname};
 
 210   foreach my $ref (@{ $form->{form_details} }) {
 
 211     $form->{rowcount} = ++$i;
 
 213     map { $form->{"${_}_$i"} = $ref->{$_} } keys %{$ref};
 
 215   for my $i (1 .. $form->{rowcount}) {
 
 217       $form->{"discount_$i"} = $form->format_amount(\%myconfig, $form->{"discount_$i"} * 100);
 
 219       $form->{"discount_$i"} = $form->format_amount(\%myconfig, $form->{"discount_$i"});
 
 221     my ($dec) = ($form->{"sellprice_$i"} =~ /\.(\d+)/);
 
 223     my $decimalplaces = ($dec > 2) ? $dec : 2;
 
 225     # copy reqdate from deliverydate for invoice -> order conversion
 
 226     $form->{"reqdate_$i"} = $form->{"deliverydate_$i"} unless $form->{"reqdate_$i"};
 
 228     $form->{"sellprice_$i"} = $form->format_amount(\%myconfig, $form->{"sellprice_$i"}, $decimalplaces);
 
 229     $form->{"lastcost_$i"} = $form->format_amount(\%myconfig, $form->{"lastcost_$i"}, $decimalplaces);
 
 231     (my $dec_qty) = ($form->{"qty_$i"} =~ /\.(\d+)/);
 
 232     $dec_qty = length $dec_qty;
 
 233     $form->{"qty_$i"} = $form->format_amount(\%myconfig, $form->{"qty_$i"}, $dec_qty);
 
 236   $main::lxdebug->leave_sub();
 
 239 sub setup_do_action_bar {
 
 240   my @transfer_qty   = qw(kivi.SalesPurchase.delivery_order_check_transfer_qty);
 
 241   my @req_trans_desc = qw(kivi.SalesPurchase.check_transaction_description) x!!$::instance_conf->get_require_transaction_description_ps;
 
 243   for my $bar ($::request->layout->get('actionbar')) {
 
 247           submit    => [ '#form', { action_update => 1 } ],
 
 248           accesskey => 'enter',
 
 254           submit   => [ '#form', { action_save => 1 } ],
 
 255           checks   => [ @req_trans_desc ],
 
 256           disabled => $::form->{delivered} ? t8('This record has already been delivered.') : undef,
 
 260           submit   => [ '#form', { action_save_as_new => 1 } ],
 
 261           checks   => [ @req_trans_desc ],
 
 262           disabled => !$::form->{id},
 
 265           t8('Mark as closed'),
 
 266           submit   => [ '#form', { action_mark_closed => 1 } ],
 
 267           checks   => [ @req_trans_desc ],
 
 268           confirm  => t8('This will remove the delivery order from showing as open even if contents are not delivered. Proceed?'),
 
 269           disabled => !$::form->{id}    ? t8('This record has not been saved yet.')
 
 270                     : $::form->{closed} ? t8('This record has already been closed.')
 
 273       ], # end of combobox "Save"
 
 277         submit   => [ '#form', { action_delete => 1 } ],
 
 278         confirm  => t8('Do you really want to delete this object?'),
 
 279         disabled => !$::form->{id}                                                                              ? t8('This record has not been saved yet.')
 
 280                   : $::form->{delivered}                                                                        ? t8('This record has already been delivered.')
 
 281                   : ($::form->{vc} eq 'customer' && !$::instance_conf->get_sales_delivery_order_show_delete)    ? t8('Deleting this type of record has been disabled in the configuration.')
 
 282                   : ($::form->{vc} eq 'vendor'   && !$::instance_conf->get_purchase_delivery_order_show_delete) ? t8('Deleting this type of record has been disabled in the configuration.')
 
 289           submit   => [ '#form', { action_transfer_out => 1 } ],
 
 290           checks   => [ @req_trans_desc, @transfer_qty ],
 
 291           disabled => $::form->{delivered} ? t8('This record has already been delivered.') : undef,
 
 292         ]) x ($::form->{vc} eq 'customer'),
 
 294           t8('Transfer out via default'),
 
 295           submit   => [ '#form', { action_transfer_out_default => 1 } ],
 
 296           checks   => [ @req_trans_desc, @transfer_qty ],
 
 297           disabled => $::form->{delivered} ? t8('This record has already been delivered.') : undef,
 
 298         ]) x ($::form->{vc} eq 'customer' && $::instance_conf->get_transfer_default),
 
 301           submit   => [ '#form', { action_transfer_in    => 1 } ],
 
 302           checks   => [ @req_trans_desc, @transfer_qty ],
 
 303           disabled => $::form->{delivered} ? t8('This record has already been delivered.') : undef,
 
 304         ]) x ($::form->{vc} eq 'vendor'),
 
 306           t8('Transfer in via default'),
 
 307           submit   => [ '#form', { action_transfer_in_default => 1 } ],
 
 308           checks   => [ @req_trans_desc, @transfer_qty ],
 
 309           disabled => $::form->{delivered} ? t8('This record has already been delivered.') : undef,
 
 310         ]) x ($::form->{vc} eq 'vendor' && $::instance_conf->get_transfer_default),
 
 311       ], # end of combobox "Transfer out"
 
 318         submit => [ '#form', { action_invoice => 1 } ],
 
 319         disabled => !$::form->{id} ? t8('This record has not been saved yet.') : undef,
 
 323         action => [ t8('Export') ],
 
 326           submit => [ '#form', { action_print => 1 } ],
 
 327           checks => [ @req_trans_desc ],
 
 331           submit => [ '#form', { action_print => 1 } ],
 
 332           checks => [ @req_trans_desc ],
 
 334       ], # end of combobox "Export"
 
 337         action => [ t8('more') ],
 
 340           call     => [ 'set_history_window', $::form->{id} * 1, 'id' ],
 
 341           disabled => !$::form->{id} ? t8('This record has not been saved yet.') : undef,
 
 345           call     => [ 'follow_up_window' ],
 
 346           disabled => !$::form->{id} ? t8('This record has not been saved yet.') : undef,
 
 348       ], # end if combobox "more"
 
 353 sub setup_do_search_action_bar {
 
 356   for my $bar ($::request->layout->get('actionbar')) {
 
 360         submit    => [ '#form' ],
 
 361         accesskey => 'enter',
 
 367 sub setup_do_orders_action_bar {
 
 370   for my $bar ($::request->layout->get('actionbar')) {
 
 374         submit    => [ '#orders_form' ],
 
 375         checks    => [ 'kivi.DeliveryOrder.multi_invoice_check_delivery_orders_selected' ],
 
 376         accesskey => 'enter',
 
 383   $main::lxdebug->enter_sub();
 
 387   my $form     = $main::form;
 
 388   my %myconfig = %main::myconfig;
 
 390   my $class       = "SL::DB::" . ($form->{vc} eq 'customer' ? 'Customer' : 'Vendor');
 
 391   $form->{VC_OBJ} = $class->load_cached($form->{ $form->{vc} . '_id' });
 
 393   $form->{employee_id} = $form->{old_employee_id} if $form->{old_employee_id};
 
 394   $form->{salesman_id} = $form->{old_salesman_id} if $form->{old_salesman_id};
 
 396   my $vc = $form->{vc} eq "customer" ? "customers" : "vendors";
 
 397   $form->get_lists("price_factors"  => "ALL_PRICE_FACTORS",
 
 398                    "business_types" => "ALL_BUSINESS_TYPES",
 
 400   $form->{ALL_DEPARTMENTS} = SL::DB::Manager::Department->get_all;
 
 403   my @old_project_ids = uniq grep { $_ } map { $_ * 1 } ($form->{"globalproject_id"}, map { $form->{"project_id_$_"} } 1..$form->{"rowcount"});
 
 404   my @old_ids_cond    = @old_project_ids ? (id => \@old_project_ids) : ();
 
 406   if (($vc eq 'customers') && $::instance_conf->get_customer_projects_only_in_sales) {
 
 409         customer_id          => $::form->{customer_id},
 
 410         billable_customer_id => $::form->{customer_id},
 
 415       and => [ active => 1, @customer_cond ],
 
 419   $::form->{ALL_PROJECTS}          = SL::DB::Manager::Project->get_all_sorted(query => \@conditions);
 
 420   $::form->{ALL_EMPLOYEES}         = SL::DB::Manager::Employee->get_all_sorted(query => [ or => [ id => $::form->{employee_id},  deleted => 0 ] ]);
 
 421   $::form->{ALL_SALESMEN}          = SL::DB::Manager::Employee->get_all_sorted(query => [ or => [ id => $::form->{salesman_id},  deleted => 0 ] ]);
 
 422   $::form->{ALL_SHIPTO}            = SL::DB::Manager::Shipto->get_all_sorted(query => [
 
 423     or => [ trans_id  => $::form->{"$::form->{vc}_id"} * 1, and => [ shipto_id => $::form->{shipto_id} * 1, trans_id => undef ] ]
 
 425   $::form->{ALL_CONTACTS}          = SL::DB::Manager::Contact->get_all_sorted(query => [
 
 427       cp_cv_id => $::form->{"$::form->{vc}_id"} * 1,
 
 430         cp_id    => $::form->{cp_id} * 1
 
 435   my $dispatch_to_popup = '';
 
 436   if ($form->{resubmit} && ($form->{format} eq "html")) {
 
 437     $dispatch_to_popup  = "window.open('about:blank','Beleg'); document.do.target = 'Beleg';";
 
 438     $dispatch_to_popup .= "document.do.submit();";
 
 439   } elsif ($form->{resubmit}) {
 
 440     # emulate click for resubmitting actions
 
 441     $dispatch_to_popup  = "document.do.${_}.click(); " for grep { /^action_/ } keys %$form;
 
 443   $::request->{layout}->add_javascripts_inline("\$(function(){$dispatch_to_popup});");
 
 446   $form->{follow_up_trans_info} = $form->{donumber} .'('. $form->{VC_OBJ}->name .')';
 
 448   $::request->{layout}->use_javascript(map { "${_}.js" } qw(kivi.File kivi.MassDeliveryOrderPrint kivi.SalesPurchase kivi.Part ckeditor/ckeditor ckeditor/adapters/jquery kivi.io autocomplete_customer));
 
 451   push @custom_hidden, map { "shiptocvar_" . $_->name } @{ SL::DB::Manager::CustomVariableConfig->get_all(where => [ module => 'ShipTo' ]) };
 
 453   $::form->{HIDDENS} = [ map { +{ name => $_, value => $::form->{$_} } } (@custom_hidden) ];
 
 455   setup_do_action_bar();
 
 458   # Fix für Bug 1082 Erwartet wird: 'abteilungsNAME--abteilungsID'
 
 459   # und Erweiterung für Bug 1760:
 
 460   # Das war leider nur ein Teil-Fix, da das Verhalten den 'Erneuern'-Knopf
 
 461   # nicht überlebt. Konsequent jetzt auf L umgestellt
 
 462   #   $ perldoc SL::Template::Plugin::L
 
 463   # Daher entsprechend nur die Anpassung in form_header
 
 464   # und in DO.pm gemacht. 4 Testfälle:
 
 465   # department_id speichern                 | i.O.
 
 466   # department_id lesen                     | i.O.
 
 467   # department leer überlebt erneuern       | i.O.
 
 468   # department nicht leer überlebt erneuern | i.O.
 
 469   # $main::lxdebug->message(0, 'ABTEILUNGS ID in form?' . $form->{department_id});
 
 470   print $form->parse_html_template('do/form_header');
 
 472   $main::lxdebug->leave_sub();
 
 476   $main::lxdebug->enter_sub();
 
 480   my $form     = $main::form;
 
 482   $form->{PRINT_OPTIONS} = print_options('inline' => 1);
 
 483   $form->{ALL_DELIVERY_TERMS} = SL::DB::Manager::DeliveryTerm->get_all_sorted();
 
 485   print $form->parse_html_template('do/form_footer',
 
 486     {transfer_default         => ($::instance_conf->get_transfer_default)});
 
 488   $main::lxdebug->leave_sub();
 
 491 sub update_delivery_order {
 
 492   $main::lxdebug->enter_sub();
 
 496   my $form     = $main::form;
 
 497   my %myconfig = %main::myconfig;
 
 499   set_headings($form->{"id"} ? "edit" : "add");
 
 501   $form->{insertdate} = SL::DB::DeliveryOrder->new(id => $form->{id})->load->itime_as_date if $form->{id};
 
 506   $payment_id = $form->{payment_id} if $form->{payment_id};
 
 508   my $vc = $form->{vc};
 
 509   if (($form->{"previous_${vc}_id"} || $form->{"${vc}_id"}) != $form->{"${vc}_id"}) {
 
 510     $::form->{salesman_id} = SL::DB::Manager::Employee->current->id if exists $::form->{salesman_id};
 
 512     IS->get_customer(\%myconfig, $form) if $vc eq 'customer';
 
 513     IR->get_vendor(\%myconfig, $form)   if $vc eq 'vendor';
 
 516   $form->{discount} =  $form->{"$form->{vc}_discount"} if defined $form->{"$form->{vc}_discount"};
 
 517   # Problem: Wenn man ohne Erneuern einen Kunden/Lieferanten
 
 518   # wechselt, wird der entsprechende Kunden/ Lieferantenrabatt
 
 519   # nicht übernommen. Grundproblem: In Commit 82574e78
 
 520   # hab ich aus discount customer_discount und vendor_discount
 
 521   # gemacht und entsprechend an den Oberflächen richtig hin-
 
 522   # geschoben. Die damals bessere Lösung wäre gewesen:
 
 523   # In den Templates nur die hidden für form-discount wieder ein-
 
 524   # setzen dann wäre die Verrenkung jetzt nicht notwendig.
 
 525   # TODO: Ggf. Bugfix 1284, 1575 und 817 wieder zusammenführen
 
 526   # Testfälle: Kunden mit Rabatt 0 -> Rabatt 20 i.O.
 
 527   #            Kunde mit Rabatt 20 -> Rabatt 0  i.O.
 
 528   #            Kunde mit Rabatt 20 -> Rabatt 5,5 i.O.
 
 529   $form->{payment_id} = $payment_id if $form->{payment_id} eq "";
 
 531   my $i = $form->{rowcount};
 
 533   if (   ($form->{"partnumber_$i"} eq "")
 
 534       && ($form->{"description_$i"} eq "")
 
 535       && ($form->{"partsgroup_$i"}  eq "")) {
 
 542     if ($form->{type} eq 'purchase_delivery_order') {
 
 543       IR->retrieve_item(\%myconfig, $form);
 
 546       IS->retrieve_item(\%myconfig, $form);
 
 550     my $rows = scalar @{ $form->{item_list} };
 
 553       $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
 
 554       if( !$form->{"qty_$i"} ) {
 
 555         $form->{"qty_$i"} = 1;
 
 560         select_item(mode => $mode, pre_entered_qty => $form->{"qty_$i"});
 
 561         $::dispatcher->end_request;
 
 565         my $sellprice = $form->parse_amount(\%myconfig, $form->{"sellprice_$i"});
 
 567         map { $form->{"${_}_$i"} = $form->{item_list}[0]{$_} } keys %{ $form->{item_list}[0] };
 
 569         $form->{"marge_price_factor_$i"} = $form->{item_list}->[0]->{price_factor};
 
 572           $form->{"sellprice_$i"} = $sellprice;
 
 574           my $record        = _make_record();
 
 575           my $price_source  = SL::PriceSource->new(record_item => $record->items->[$i-1], record => $record);
 
 576           my $best_price    = $price_source->best_price;
 
 577           my $best_discount = $price_source->best_discount;
 
 580             $::form->{"sellprice_$i"}           = $best_price->price;
 
 581             $::form->{"active_price_source_$i"} = $best_price->source;
 
 583           if ($best_discount) {
 
 584             $::form->{"discount_$i"}               = $best_discount->discount;
 
 585             $::form->{"active_discount_source_$i"} = $best_discount->source;
 
 589         $form->{"sellprice_$i"}          = $form->format_amount(\%myconfig, $form->{"sellprice_$i"});
 
 590         $form->{"lastcost_$i"}           = $form->format_amount(\%myconfig, $form->{"lastcost_$i"});
 
 591         $form->{"qty_$i"}                = $form->format_amount(\%myconfig, $form->{"qty_$i"});
 
 592         $form->{"discount_$i"}           = $form->format_amount(\%myconfig, $form->{"discount_$i"} * 100.0);
 
 599       # ok, so this is a new part
 
 600       # ask if it is a part or service item
 
 602       if (   $form->{"partsgroup_$i"}
 
 603           && ($form->{"partsnumber_$i"} eq "")
 
 604           && ($form->{"description_$i"} eq "")) {
 
 606         $form->{"discount_$i"} = "";
 
 607         $form->{"not_discountable_$i"} = "";
 
 611         $form->{"id_$i"}   = 0;
 
 617   $main::lxdebug->leave_sub();
 
 621   $main::lxdebug->enter_sub();
 
 625   my $form     = $main::form;
 
 626   my %myconfig = %main::myconfig;
 
 627   my $locale   = $main::locale;
 
 629   $form->{vc} = $form->{type} eq 'purchase_delivery_order' ? 'vendor' : 'customer';
 
 631   $form->get_lists("projects"       => { "key" => "ALL_PROJECTS",
 
 633                    "business_types" => "ALL_BUSINESS_TYPES");
 
 634   $form->{ALL_EMPLOYEES} = SL::DB::Manager::Employee->get_all_sorted(query => [ deleted => 0 ]);
 
 635   $form->{ALL_DEPARTMENTS} = SL::DB::Manager::Department->get_all;
 
 636   $form->{title}             = $locale->text('Delivery Orders');
 
 638   setup_do_search_action_bar();
 
 642   print $form->parse_html_template('do/search');
 
 644   $main::lxdebug->leave_sub();
 
 648   $main::lxdebug->enter_sub();
 
 652   my $form     = $main::form;
 
 653   my %myconfig = %main::myconfig;
 
 654   my $locale   = $main::locale;
 
 655   my $cgi      = $::request->{cgi};
 
 657   $::request->{layout}->use_javascript(map { "${_}.js" } qw(kivi.MassDeliveryOrderPrint));
 
 658   ($form->{ $form->{vc} }, $form->{"$form->{vc}_id"}) = split(/--/, $form->{ $form->{vc} });
 
 660   report_generator_set_default_sort('transdate', 1);
 
 664   $form->{rowcount} = scalar @{ $form->{DO} };
 
 667     ids                     transdate               reqdate
 
 669     ordnumber               customernumber          cusordnumber
 
 670     name                    employee  salesman
 
 671     shipvia                 globalprojectnumber
 
 672     transaction_description department
 
 677   $form->{l_open}      = $form->{l_closed} = "Y" if ($form->{open}      && $form->{closed});
 
 678   $form->{l_delivered} = "Y"                     if ($form->{delivered} && $form->{notdelivered});
 
 680   $form->{title}       = $locale->text('Delivery Orders');
 
 682   my $attachment_basename = $form->{vc} eq 'vendor' ? $locale->text('purchase_delivery_order_list') : $locale->text('sales_delivery_order_list');
 
 684   my $report = SL::ReportGenerator->new(\%myconfig, $form);
 
 686   my @hidden_variables = map { "l_${_}" } @columns;
 
 687   push @hidden_variables, $form->{vc}, qw(l_closed l_notdelivered open closed delivered notdelivered donumber ordnumber serialnumber cusordnumber
 
 688                                           transaction_description transdatefrom transdateto reqdatefrom reqdateto
 
 689                                           type vc employee_id salesman_id project_id parts_partnumber parts_description
 
 690                                           insertdatefrom insertdateto business_id);
 
 692   my $href = build_std_url('action=orders', grep { $form->{$_} } @hidden_variables);
 
 695     'ids'                     => { 'text' => '<input type="checkbox" id="multi_all" value="1">', 'align' => 'center' },
 
 696     'transdate'               => { 'text' => $locale->text('Delivery Order Date'), },
 
 697     'reqdate'                 => { 'text' => $locale->text('Reqdate'), },
 
 698     'id'                      => { 'text' => $locale->text('ID'), },
 
 699     'donumber'                => { 'text' => $locale->text('Delivery Order'), },
 
 700     'ordnumber'               => { 'text' => $locale->text('Order'), },
 
 701     'customernumber'          => { 'text' => $locale->text('Customer Number'), },
 
 702     'cusordnumber'            => { 'text' => $locale->text('Customer Order Number'), },
 
 703     'name'                    => { 'text' => $form->{vc} eq 'customer' ? $locale->text('Customer') : $locale->text('Vendor'), },
 
 704     'employee'                => { 'text' => $locale->text('Employee'), },
 
 705     'salesman'                => { 'text' => $locale->text('Salesman'), },
 
 706     'shipvia'                 => { 'text' => $locale->text('Ship via'), },
 
 707     'globalprojectnumber'     => { 'text' => $locale->text('Project Number'), },
 
 708     'transaction_description' => { 'text' => $locale->text('Transaction description'), },
 
 709     'open'                    => { 'text' => $locale->text('Open'), },
 
 710     'delivered'               => { 'text' => $locale->text('Delivered'), },
 
 711     'department'              => { 'text' => $locale->text('Department'), },
 
 712     'insertdate'              => { 'text' => $locale->text('Insert Date'), },
 
 715   foreach my $name (qw(id transdate reqdate donumber ordnumber name employee salesman shipvia transaction_description department insertdate)) {
 
 716     my $sortdir                 = $form->{sort} eq $name ? 1 - $form->{sortdir} : $form->{sortdir};
 
 717     $column_defs{$name}->{link} = $href . "&sort=$name&sortdir=$sortdir";
 
 720   $form->{"l_type"} = "Y";
 
 721   map { $column_defs{$_}->{visible} = $form->{"l_${_}"} ? 1 : 0 } @columns;
 
 723   $column_defs{ids}->{visible} = 'HTML';
 
 725   $report->set_columns(%column_defs);
 
 726   $report->set_column_order(@columns);
 
 728   $report->set_export_options('orders', @hidden_variables, qw(sort sortdir));
 
 730   $report->set_sort_indicator($form->{sort}, $form->{sortdir});
 
 733   if ($form->{customer}) {
 
 734     push @options, $locale->text('Customer') . " : $form->{customer}";
 
 736   if ($form->{vendor}) {
 
 737     push @options, $locale->text('Vendor') . " : $form->{vendor}";
 
 739   if ($form->{cp_name}) {
 
 740     push @options, $locale->text('Contact Person') . " : $form->{cp_name}";
 
 742   if ($form->{department_id}) {
 
 743     push @options, $locale->text('Department') . " : " . SL::DB::Department->new(id => $form->{department_id})->load->description;
 
 745   if ($form->{donumber}) {
 
 746     push @options, $locale->text('Delivery Order Number') . " : $form->{donumber}";
 
 748   if ($form->{ordnumber}) {
 
 749     push @options, $locale->text('Order Number') . " : $form->{ordnumber}";
 
 751   push @options, $locale->text('Serial Number') . " : $form->{serialnumber}" if $form->{serialnumber};
 
 752   if ($form->{business_id}) {
 
 753     my $vc_type_label = $form->{vc} eq 'customer' ? $locale->text('Customer type') : $locale->text('Vendor type');
 
 754     push @options, $vc_type_label . " : " . SL::DB::Business->new(id => $form->{business_id})->load->description;
 
 756   if ($form->{transaction_description}) {
 
 757     push @options, $locale->text('Transaction description') . " : $form->{transaction_description}";
 
 759   if ($form->{parts_description}) {
 
 760     push @options, $locale->text('Part Description') . " : $form->{parts_description}";
 
 762   if ($form->{parts_partnumber}) {
 
 763     push @options, $locale->text('Part Number') . " : $form->{parts_partnumber}";
 
 765   if ( $form->{transdatefrom} or $form->{transdateto} ) {
 
 766     push @options, $locale->text('Delivery Order Date');
 
 767     push @options, $locale->text('From') . " " . $locale->date(\%myconfig, $form->{transdatefrom}, 1)     if $form->{transdatefrom};
 
 768     push @options, $locale->text('Bis')  . " " . $locale->date(\%myconfig, $form->{transdateto},   1)     if $form->{transdateto};
 
 770   if ( $form->{reqdatefrom} or $form->{reqdateto} ) {
 
 771     push @options, $locale->text('Reqdate');
 
 772     push @options, $locale->text('From') . " " . $locale->date(\%myconfig, $form->{reqdatefrom}, 1)       if $form->{reqdatefrom};
 
 773     push @options, $locale->text('Bis')  . " " . $locale->date(\%myconfig, $form->{reqdateto},   1)       if $form->{reqdateto};
 
 775   if ( $form->{insertdatefrom} or $form->{insertdateto} ) {
 
 776     push @options, $locale->text('Insert Date');
 
 777     push @options, $locale->text('From') . " " . $locale->date(\%myconfig, $form->{insertdatefrom}, 1)    if $form->{insertdatefrom};
 
 778     push @options, $locale->text('Bis')  . " " . $locale->date(\%myconfig, $form->{insertdateto},   1)    if $form->{insertdateto};
 
 781     push @options, $locale->text('Open');
 
 783   if ($form->{closed}) {
 
 784     push @options, $locale->text('Closed');
 
 786   if ($form->{delivered}) {
 
 787     push @options, $locale->text('Delivered');
 
 789   if ($form->{notdelivered}) {
 
 790     push @options, $locale->text('Not delivered');
 
 793   my $pr = SL::DB::Manager::Printer->find_by(
 
 794       printer_description => $::locale->text("sales_delivery_order_printer"));
 
 796       $form->{printer_id} = $pr->id;
 
 799   $report->set_options('top_info_text'        => join("\n", @options),
 
 800                        'raw_top_info_text'    => $form->parse_html_template('do/orders_top'),
 
 801                        'raw_bottom_info_text' => $form->parse_html_template('do/orders_bottom', { print_options => print_options(inline => 1,hide_language_id => 1) }),
 
 802                        'output_format'        => 'HTML',
 
 803                        'title'                => $form->{title},
 
 804                        'attachment_basename'  => $attachment_basename . strftime('_%Y%m%d', localtime time),
 
 806   $report->set_options_from_form();
 
 807   $locale->set_numberformat_wo_thousands_separator(\%myconfig) if lc($report->{options}->{output_format}) eq 'csv';
 
 809   # add sort and escape callback, this one we use for the add sub
 
 810   $form->{callback} = $href .= "&sort=$form->{sort}";
 
 812   # escape callback for href
 
 813   my $callback = $form->escape($href);
 
 815   my $edit_url       = build_std_url('action=edit', 'type', 'vc');
 
 816   my $edit_order_url = build_std_url('script=oe.pl', 'type=' . ($form->{type} eq 'sales_delivery_order' ? 'sales_order' : 'purchase_order'), 'action=edit');
 
 820   foreach my $dord (@{ $form->{DO} }) {
 
 821     $dord->{open}      = $dord->{closed}    ? $locale->text('No')  : $locale->text('Yes');
 
 822     $dord->{delivered} = $dord->{delivered} ? $locale->text('Yes') : $locale->text('No');
 
 824     my $row = { map { $_ => { 'data' => $dord->{$_} } } @columns };
 
 826     my $ord_id = $dord->{id};
 
 828       'raw_data' =>   $cgi->hidden('-name' => "trans_id_${idx}", '-value' => $ord_id)
 
 829                     . $cgi->checkbox('-name' => "multi_id_${idx}",' id' => "multi_id_id_".$ord_id, '-value' => 1, '-label' => ''),
 
 830       'valign'   => 'center',
 
 834     $row->{donumber}->{link}  = $edit_url       . "&id=" . E($dord->{id})      . "&callback=${callback}";
 
 835     $row->{ordnumber}->{link} = $edit_order_url . "&id=" . E($dord->{oe_id})   . "&callback=${callback}" if $dord->{oe_id};
 
 836     $report->add_data($row);
 
 841   $::request->layout->add_javascripts('kivi.DeliveryOrder.js');
 
 843   setup_do_orders_action_bar();
 
 845   $report->generate_with_headers(action_bar => 1);
 
 847   $main::lxdebug->leave_sub();
 
 851   $main::lxdebug->enter_sub();
 
 857   my $form     = $main::form;
 
 858   my %myconfig = %main::myconfig;
 
 859   my $locale   = $main::locale;
 
 861   $form->mtime_ischanged('delivery_orders');
 
 863   $form->{defaultcurrency} = $form->get_default_currency(\%myconfig);
 
 865   $form->isblank("transdate", $locale->text('Delivery Order Date missing!'));
 
 867   $form->{donumber} =~ s/^\s*//g;
 
 868   $form->{donumber} =~ s/\s*$//g;
 
 870   my $msg = ucfirst $form->{vc};
 
 871   $form->isblank($form->{vc} . "_id", $locale->text($msg . " missing!"));
 
 873   # $locale->text('Customer missing!');
 
 874   # $locale->text('Vendor missing!');
 
 876   remove_emptied_rows();
 
 879   # if the name changed get new values
 
 880   my $vc = $form->{vc};
 
 881   if (($form->{"previous_${vc}_id"} || $form->{"${vc}_id"}) != $form->{"${vc}_id"}) {
 
 882     $::form->{salesman_id} = SL::DB::Manager::Employee->current->id if exists $::form->{salesman_id};
 
 884     IS->get_customer(\%myconfig, $form) if $vc eq 'customer';
 
 885     IR->get_vendor(\%myconfig, $form)   if $vc eq 'vendor';
 
 888     $::dispatcher->end_request;
 
 891   $form->{id} = 0 if $form->{saveasnew};
 
 895   if(!exists $form->{addition}) {
 
 896     $form->{snumbers} = qq|donumber_| . $form->{donumber};
 
 897     $form->{addition} = "SAVED";
 
 900   # /saving the history
 
 902   $form->{simple_save} = 1;
 
 903   if (!$params{no_redirect} && !$form->{print_and_save}) {
 
 904     delete @{$form}{ary_diff([keys %{ $form }], [qw(login id script type cursor_fokus)])};
 
 906     $::dispatcher->end_request;
 
 908   $main::lxdebug->leave_sub();
 
 912   $main::lxdebug->enter_sub();
 
 916   my $form     = $main::form;
 
 917   my %myconfig = %main::myconfig;
 
 918   my $locale   = $main::locale;
 
 922     if(!exists $form->{addition}) {
 
 923       $form->{snumbers} = qq|donumber_| . $form->{donumber};
 
 924       $form->{addition} = "DELETED";
 
 927     # /saving the history
 
 929     $form->info($locale->text('Delivery Order deleted!'));
 
 930     $::dispatcher->end_request;
 
 933   $form->error($locale->text('Cannot delete delivery order!'));
 
 935   $main::lxdebug->leave_sub();
 
 939   $main::lxdebug->enter_sub();
 
 941   my $form     = $main::form;
 
 942   my %myconfig = %main::myconfig;
 
 943   my $locale   = $main::locale;
 
 946   $form->mtime_ischanged('delivery_orders');
 
 948   $main::auth->assert($form->{type} eq 'purchase_delivery_order' ? 'vendor_invoice_edit' : 'invoice_edit');
 
 950   $form->{convert_from_do_ids} = $form->{id};
 
 951   $form->{deliverydate}        = $form->{transdate};
 
 952   $form->{transdate}           = $form->{invdate} = $form->current_date(\%myconfig);
 
 953   $form->{duedate}             = $form->current_date(\%myconfig, $form->{invdate}, $form->{terms} * 1);
 
 954   $form->{defaultcurrency}     = $form->get_default_currency(\%myconfig);
 
 958   delete @{$form}{qw(id closed delivered)};
 
 960   my ($script, $buysell);
 
 961   if ($form->{type} eq 'purchase_delivery_order') {
 
 962     $form->{title}  = $locale->text('Add Vendor Invoice');
 
 963     $form->{script} = 'ir.pl';
 
 968     $form->{title}  = $locale->text('Add Sales Invoice');
 
 969     $form->{script} = 'is.pl';
 
 974   for my $i (1 .. $form->{rowcount}) {
 
 975     map { $form->{"${_}_${i}"} = $form->parse_amount(\%myconfig, $form->{"${_}_${i}"}) if $form->{"${_}_${i}"} } qw(ship qty sellprice lastcost basefactor discount);
 
 977     # adds a customer/vendor discount, unless we have a workflow case
 
 978     # CAVEAT: has to be done, after the above parse_amount
 
 979     unless ($form->{"ordnumber"}) {
 
 980       if ($form->{discount}) { # Falls wir einen Lieferanten-/Kundenrabatt haben
 
 981         # und rabattfähig sind, dann
 
 982         unless ($form->{"not_discountable_$i"}) {
 
 983           $form->{"discount_$i"} = $form->{discount}*100; # ... nehmen wir diesen Rabatt
 
 987     $form->{"donumber_$i"} = $form->{donumber};
 
 988     $form->{"converted_from_delivery_order_items_id_$i"} = delete $form->{"delivery_order_items_id_$i"};
 
 991   $form->{type} = "invoice";
 
 994   $main::locale = Locale->new("$myconfig{countrycode}", "$script");
 
 995   $locale = $main::locale;
 
 997   require "bin/mozilla/$form->{script}";
 
 999   my $currency = $form->{currency};
 
1002   if ($form->{ordnumber}) {
 
1003     require SL::DB::Order;
 
1004     if (my $order = SL::DB::Manager::Order->find_by(ordnumber => $form->{ordnumber})) {
 
1006       $form->{orddate} = $order->transdate_as_date;
 
1007       $form->{$_}      = $order->$_ for qw(payment_id salesman_id taxzone_id quonumber);
 
1011   $form->{currency}     = $currency;
 
1012   $form->{exchangerate} = "";
 
1013   $form->{forex}        = $form->check_exchangerate(\%myconfig, $form->{currency}, $form->{invdate}, $buysell);
 
1014   $form->{exchangerate} = $form->{forex} if ($form->{forex});
 
1019   for my $i (1 .. $form->{rowcount}) {
 
1020     $form->{"discount_$i"} = $form->format_amount(\%myconfig, $form->{"discount_$i"});
 
1022     my ($dec) = ($form->{"sellprice_$i"} =~ /\.(\d+)/);
 
1024     my $decimalplaces = ($dec > 2) ? $dec : 2;
 
1026     # copy delivery date from reqdate for order -> invoice conversion
 
1027     $form->{"deliverydate_$i"} = $form->{"reqdate_$i"}
 
1028       unless $form->{"deliverydate_$i"};
 
1031     $form->{"sellprice_$i"} =
 
1032       $form->format_amount(\%myconfig, $form->{"sellprice_$i"},
 
1035     $form->{"lastcost_$i"} =
 
1036       $form->format_amount(\%myconfig, $form->{"lastcost_$i"},
 
1039     (my $dec_qty) = ($form->{"qty_$i"} =~ /\.(\d+)/);
 
1040     $dec_qty = length $dec_qty;
 
1042       $form->format_amount(\%myconfig, $form->{"qty_$i"}, $dec_qty);
 
1048   $main::lxdebug->leave_sub();
 
1052   $main::lxdebug->enter_sub();
 
1054   my $form     = $main::form;
 
1055   my %myconfig = %main::myconfig;
 
1056   my $locale   = $main::locale;
 
1059   $main::auth->assert($form->{type} eq 'sales_delivery_order' ? 'invoice_edit' : 'vendor_invoice_edit');
 
1061   my @do_ids = map { $form->{"trans_id_$_"} } grep { $form->{"multi_id_$_"} } (1..$form->{rowcount});
 
1063   if (!scalar @do_ids) {
 
1064     $form->show_generic_error($locale->text('You have not selected any delivery order.'));
 
1067   map { delete $form->{$_} } grep { m/^(?:trans|multi)_id_\d+/ } keys %{ $form };
 
1069   if (!DO->retrieve('vc' => $form->{vc}, 'ids' => \@do_ids)) {
 
1070     $form->show_generic_error($form->{vc} eq 'customer' ?
 
1071                               $locale->text('You cannot create an invoice for delivery orders for different customers.') :
 
1072                               $locale->text('You cannot create an invoice for delivery orders from different vendors.'),
 
1073                               'back_button' => 1);
 
1076   my $source_type              = $form->{type};
 
1077   $form->{convert_from_do_ids} = join ' ', @do_ids;
 
1078   # bei der auswahl von mehreren Lieferscheinen fuer eine Rechnung, die einfach in donumber_array
 
1079   # zwischenspeichern (DO.pm) und als ' '-separierte Liste wieder zurueckschreiben
 
1080   # Hinweis: delete gibt den wert zurueck und loescht danach das element (nett und einfach)
 
1081   # $shell: perldoc perlunc; /delete EXPR
 
1082   $form->{donumber}            = delete $form->{donumber_array};
 
1083   $form->{ordnumber}           = delete $form->{ordnumber_array};
 
1084   $form->{cusordnumber}        = delete $form->{cusordnumber_array};
 
1085   $form->{deliverydate}        = $form->{transdate};
 
1086   $form->{transdate}           = $form->current_date(\%myconfig);
 
1087   $form->{duedate}             = $form->current_date(\%myconfig, $form->{invdate}, $form->{terms} * 1);
 
1088   $form->{type}                = "invoice";
 
1089   $form->{closed}              = 0;
 
1090   $form->{defaultcurrency}     = $form->get_default_currency(\%myconfig);
 
1092   my ($script, $buysell);
 
1093   if ($source_type eq 'purchase_delivery_order') {
 
1094     $form->{title}  = $locale->text('Add Vendor Invoice');
 
1095     $form->{script} = 'ir.pl';
 
1100     $form->{title}  = $locale->text('Add Sales Invoice');
 
1101     $form->{script} = 'is.pl';
 
1106   map { delete $form->{$_} } qw(id subject message cc bcc printed emailed queued);
 
1108   # get vendor or customer discount
 
1110   my $saved_form = save_form();
 
1111   if ($form->{vc} eq 'vendor') {
 
1112     IR->get_vendor(\%myconfig, \%$form);
 
1113     $vc_discount = $form->{vendor_discount};
 
1115     IS->get_customer(\%myconfig, \%$form);
 
1116     $vc_discount = $form->{customer_discount};
 
1118   # use payment terms from customer or vendor
 
1119   restore_form($saved_form,0,qw(payment_id));
 
1121   $form->{rowcount} = 0;
 
1122   foreach my $ref (@{ $form->{form_details} }) {
 
1123     $form->{rowcount}++;
 
1124     $ref->{reqdate} ||= $ref->{dord_transdate}; # copy transdates into each invoice row
 
1125     map { $form->{"${_}_$form->{rowcount}"} = $ref->{$_} } keys %{ $ref };
 
1126     map { $form->{"${_}_$form->{rowcount}"} = $form->format_amount(\%myconfig, $ref->{$_}) } qw(qty sellprice lastcost);
 
1127     $form->{"converted_from_delivery_order_items_id_$form->{rowcount}"} = delete $form->{"delivery_order_items_id_$form->{rowcount}"};
 
1129     if ($vc_discount){ # falls wir einen Lieferanten/Kundenrabatt haben
 
1130       # und keinen anderen discount wert an $i ...
 
1131       $form->{"discount_$form->{rowcount}"} ||= $vc_discount; # ... nehmen wir diesen Rabatt
 
1134     $form->{"discount_$form->{rowcount}"}   = $form->{"discount_$form->{rowcount}"}  * 100; #s.a. Bug 1151
 
1135     # Anm.: Eine Änderung des discounts in der SL/DO.pm->retrieve (select (doi.discount * 100) as discount) ergibt in psql einen
 
1136     # Wert von 10.0000001490116. Ferner ist der Rabatt in der Rechnung dann bei 1.0 (?). Deswegen lasse ich das hier. jb 10.10.09
 
1138     $form->{"discount_$form->{rowcount}"} = $form->format_amount(\%myconfig, $form->{"discount_$form->{rowcount}"});
 
1140   delete $form->{form_details};
 
1142   $locale = Locale->new("$myconfig{countrycode}", "$script");
 
1144   require "bin/mozilla/$form->{script}";
 
1151   $main::lxdebug->leave_sub();
 
1155   $main::lxdebug->enter_sub();
 
1159   my $form     = $main::form;
 
1161   $form->{saveasnew} = 1;
 
1162   $form->{closed}    = 0;
 
1163   $form->{delivered} = 0;
 
1164   map { delete $form->{$_} } qw(printed emailed queued);
 
1165   delete @{ $form }{ grep { m/^stock_(?:in|out)_\d+/ } keys %{ $form } };
 
1166   $form->{"converted_from_delivery_order_items_id_$_"} = delete $form->{"delivery_order_items_id_$_"} for 1 .. $form->{"rowcount"};
 
1167   # Let kivitendo assign a new order number if the user hasn't changed the
 
1168   # previous one. If it has been changed manually then use it as-is.
 
1169   $form->{donumber} =~ s/^\s*//g;
 
1170   $form->{donumber} =~ s/\s*$//g;
 
1171   if ($form->{saved_donumber} && ($form->{saved_donumber} eq $form->{donumber})) {
 
1172     delete($form->{donumber});
 
1177   $main::lxdebug->leave_sub();
 
1181   $main::lxdebug->enter_sub();
 
1185   $::form->mtime_ischanged('delivery_orders','mail');
 
1187   $::form->{print_and_save} = 1;
 
1189   my $saved_form = save_form();
 
1193   restore_form($saved_form, 0, qw(id ordnumber quonumber));
 
1197   $main::lxdebug->leave_sub();
 
1200 sub calculate_stock_in_out {
 
1201   $main::lxdebug->enter_sub();
 
1203   my $form     = $main::form;
 
1207   if (!$form->{"id_${i}"}) {
 
1208     $main::lxdebug->leave_sub();
 
1212   my $all_units = AM->retrieve_all_units();
 
1214   my $in_out   = $form->{type} =~ /^sales/ ? 'out' : 'in';
 
1215   my $sinfo    = DO->unpack_stock_information('packed' => $form->{"stock_${in_out}_${i}"});
 
1217   my $do_qty   = AM->sum_with_unit($::form->{"qty_$i"}, $::form->{"unit_$i"});
 
1218   my $sum      = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $sinfo });
 
1219   my $matches  = $do_qty == $sum;
 
1221   my $content  = $form->format_amount_units('amount'      => $sum * 1,
 
1222                                             'part_unit'   => $form->{"partunit_$i"},
 
1223                                             'amount_unit' => $all_units->{$form->{"partunit_$i"}}->{base_unit},
 
1224                                             'conv_units'  => 'convertible_not_smaller',
 
1226   $content     = qq|<span id="stock_in_out_qty_display_${i}">${content}</span><input type=hidden id='stock_in_out_qty_matches_$i' value='$matches'> <input type="button" onclick="open_stock_in_out_window('${in_out}', $i);" value="?">|;
 
1228   $main::lxdebug->leave_sub();
 
1233 sub get_basic_bin_wh_info {
 
1234   $main::lxdebug->enter_sub();
 
1236   my $stock_info = shift;
 
1238   my $form     = $main::form;
 
1240   foreach my $sinfo (@{ $stock_info }) {
 
1241     next unless ($sinfo->{bin_id});
 
1243     my $bin_info = WH->get_basic_bin_info('id' => $sinfo->{bin_id});
 
1244     map { $sinfo->{"${_}_description"} = $sinfo->{"${_}description"} = $bin_info->{"${_}_description"} } qw(bin warehouse);
 
1247   $main::lxdebug->leave_sub();
 
1250 sub stock_in_out_form {
 
1251   $main::lxdebug->enter_sub();
 
1253   my $form     = $main::form;
 
1255   if ($form->{in_out} eq 'out') {
 
1261   $main::lxdebug->leave_sub();
 
1264 sub redo_stock_info {
 
1265   $main::lxdebug->enter_sub();
 
1269   my $form     = $main::form;
 
1271   my @non_empty = grep { $_->{qty} } @{ $params{stock_info} };
 
1273   if ($params{add_empty_row}) {
 
1275       'warehouse_id' => scalar(@non_empty) ? $non_empty[-1]->{warehouse_id} : undef,
 
1276       'bin_id'       => scalar(@non_empty) ? $non_empty[-1]->{bin_id}       : undef,
 
1280   @{ $params{stock_info} } = @non_empty;
 
1282   $main::lxdebug->leave_sub();
 
1285 sub update_stock_in {
 
1286   $main::lxdebug->enter_sub();
 
1288   my $form     = $main::form;
 
1289   my %myconfig = %main::myconfig;
 
1291   my $stock_info = [];
 
1293   foreach my $i (1..$form->{rowcount}) {
 
1294     $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
 
1295     push @{ $stock_info }, { map { $_ => $form->{"${_}_${i}"} } qw(warehouse_id bin_id chargenumber
 
1296                                                                    bestbefore qty unit delivery_order_items_stock_id) };
 
1299   display_stock_in_form($stock_info);
 
1301   $main::lxdebug->leave_sub();
 
1305   $main::lxdebug->enter_sub();
 
1307   my $form     = $main::form;
 
1309   my $stock_info = DO->unpack_stock_information('packed' => $form->{stock});
 
1311   display_stock_in_form($stock_info);
 
1313   $main::lxdebug->leave_sub();
 
1316 sub display_stock_in_form {
 
1317   $main::lxdebug->enter_sub();
 
1319   my $stock_info = shift;
 
1321   my $form     = $main::form;
 
1322   my %myconfig = %main::myconfig;
 
1323   my $locale   = $main::locale;
 
1325   $form->{title} = $locale->text('Stock');
 
1327   my $part_info  = IC->get_basic_part_info('id' => $form->{parts_id});
 
1329   # Standardlagerplatz für Standard-Auslagern verwenden, falls keiner für die Ware explizit definiert wurde
 
1330   if ($::instance_conf->get_transfer_default_use_master_default_bin) {
 
1331     $part_info->{warehouse_id} ||= $::instance_conf->get_warehouse_id;
 
1332     $part_info->{bin_id}       ||= $::instance_conf->get_bin_id;
 
1335   my $units      = AM->retrieve_units(\%myconfig, $form);
 
1336   # der zweite Parameter von unit_select_data gibt den default-Namen (selected) vor
 
1337   my $units_data = AM->unit_select_data($units, $form->{do_unit}, undef, $part_info->{unit});
 
1339   $form->get_lists('warehouses' => { 'key'    => 'WAREHOUSES',
 
1340                                      'bins'   => 'BINS' });
 
1342   redo_stock_info('stock_info' => $stock_info, 'add_empty_row' => !$form->{delivered});
 
1344   get_basic_bin_wh_info($stock_info);
 
1346   $form->header(no_layout => 1);
 
1347   print $form->parse_html_template('do/stock_in_form', { 'UNITS'      => $units_data,
 
1348                                                          'STOCK_INFO' => $stock_info,
 
1349                                                          'PART_INFO'  => $part_info, });
 
1351   $main::lxdebug->leave_sub();
 
1354 sub _stock_in_out_set_qty_display {
 
1355   my $stock_info       = shift;
 
1357   my $all_units        = AM->retrieve_all_units();
 
1358   my $sum              = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $stock_info });
 
1359   $form->{qty_display} = $form->format_amount_units(amount      => $sum * 1,
 
1360                                                     part_unit   => $form->{partunit},
 
1361                                                     amount_unit => $all_units->{ $form->{partunit} }->{base_unit},
 
1362                                                     conv_units  => 'convertible_not_smaller',
 
1367   $main::lxdebug->enter_sub();
 
1369   my $form     = $main::form;
 
1370   my %myconfig = %main::myconfig;
 
1372   my $stock_info = [];
 
1374   foreach my $i (1..$form->{rowcount}) {
 
1375     $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
 
1377     next if ($form->{"qty_$i"} <= 0);
 
1379     push @{ $stock_info }, { map { $_ => $form->{"${_}_${i}"} } qw(delivery_order_items_stock_id warehouse_id bin_id chargenumber bestbefore qty unit) };
 
1382   $form->{stock} = YAML::Dump($stock_info);
 
1384   _stock_in_out_set_qty_display($stock_info);
 
1386   my $do_qty       = AM->sum_with_unit($::form->parse_amount(\%::myconfig, $::form->{do_qty}), $::form->{do_unit});
 
1387   my $transfer_qty = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $stock_info });
 
1390   print $form->parse_html_template('do/set_stock_in_out', {
 
1391     qty_matches => $do_qty == $transfer_qty,
 
1394   $main::lxdebug->leave_sub();
 
1397 sub stock_out_form {
 
1398   $main::lxdebug->enter_sub();
 
1400   my $form     = $main::form;
 
1401   my %myconfig = %main::myconfig;
 
1402   my $locale   = $main::locale;
 
1404   $form->{title} = $locale->text('Release From Stock');
 
1406   my $part_info  = IC->get_basic_part_info('id' => $form->{parts_id});
 
1408   my $units      = AM->retrieve_units(\%myconfig, $form);
 
1409   my $units_data = AM->unit_select_data($units, undef, undef, $part_info->{unit});
 
1411   my @contents   = DO->get_item_availability('parts_id' => $form->{parts_id});
 
1413   my $stock_info = DO->unpack_stock_information('packed' => $form->{stock});
 
1415   if (!$form->{delivered}) {
 
1416     foreach my $row (@contents) {
 
1417       $row->{available_qty} = $form->format_amount_units('amount'      => $row->{qty} * 1,
 
1418                                                          'part_unit'   => $part_info->{unit},
 
1419                                                          'conv_units'  => 'convertible_not_smaller',
 
1422       foreach my $sinfo (@{ $stock_info }) {
 
1423         next if (($row->{bin_id}       != $sinfo->{bin_id}) ||
 
1424                  ($row->{warehouse_id} != $sinfo->{warehouse_id}) ||
 
1425                  ($row->{chargenumber} ne $sinfo->{chargenumber}) ||
 
1426                  ($row->{bestbefore}   ne $sinfo->{bestbefore}));
 
1428         map { $row->{"stock_$_"} = $sinfo->{$_} } qw(qty unit error delivery_order_items_stock_id);
 
1433     get_basic_bin_wh_info($stock_info);
 
1435     foreach my $sinfo (@{ $stock_info }) {
 
1436       map { $sinfo->{"stock_$_"} = $sinfo->{$_} } qw(qty unit);
 
1440   $form->header(no_layout => 1);
 
1441   print $form->parse_html_template('do/stock_out_form', { 'UNITS'      => $units_data,
 
1442                                                           'WHCONTENTS' => $form->{delivered} ? $stock_info : \@contents,
 
1443                                                           'PART_INFO'  => $part_info, });
 
1445   $main::lxdebug->leave_sub();
 
1449   $main::lxdebug->enter_sub();
 
1451   my $form     = $main::form;
 
1452   my %myconfig = %main::myconfig;
 
1453   my $locale   = $main::locale;
 
1455   my $stock_info = [];
 
1457   foreach my $i (1 .. $form->{rowcount}) {
 
1458     $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
 
1460     next if ($form->{"qty_$i"} <= 0);
 
1462     push @{ $stock_info }, {
 
1463       'warehouse_id' => $form->{"warehouse_id_$i"},
 
1464       'bin_id'       => $form->{"bin_id_$i"},
 
1465       'chargenumber' => $form->{"chargenumber_$i"},
 
1466       'bestbefore'   => $form->{"bestbefore_$i"},
 
1467       'qty'          => $form->{"qty_$i"},
 
1468       'unit'         => $form->{"unit_$i"},
 
1470       'delivery_order_items_stock_id'  => $form->{"delivery_order_items_stock_id_$i"},
 
1474   my @errors     = DO->check_stock_availability('requests' => $stock_info,
 
1475                                                 'parts_id' => $form->{parts_id});
 
1477   $form->{stock} = YAML::Dump($stock_info);
 
1480     $form->{ERRORS} = [];
 
1481     map { push @{ $form->{ERRORS} }, $locale->text('Error in row #1: The quantity you entered is bigger than the stocked quantity.', $_->{row}); } @errors;
 
1482     stock_in_out_form();
 
1485     _stock_in_out_set_qty_display($stock_info);
 
1487     my $do_qty       = AM->sum_with_unit($::form->parse_amount(\%::myconfig, $::form->{do_qty}), $::form->{do_unit});
 
1488     my $transfer_qty = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $stock_info });
 
1491     print $form->parse_html_template('do/set_stock_in_out', {
 
1492       qty_matches => $do_qty == $transfer_qty,
 
1496   $main::lxdebug->leave_sub();
 
1500   $main::lxdebug->enter_sub();
 
1502   my $form     = $main::form;
 
1503   my %myconfig = %main::myconfig;
 
1504   my $locale   = $main::locale;
 
1506   if ($form->{id} && DO->is_marked_as_delivered(id => $form->{id})) {
 
1507     $form->show_generic_error($locale->text('The parts for this delivery order have already been transferred in.'));
 
1510   save(no_redirect => 1);
 
1512   my @part_ids = map { $form->{"id_${_}"} } grep { $form->{"id_${_}"} && $form->{"stock_in_${_}"} } (1 .. $form->{rowcount});
 
1516     my $units         = AM->retrieve_units(\%myconfig, $form);
 
1517     my %part_info_map = IC->get_basic_part_info('id' => \@part_ids);
 
1520     $form->{ERRORS}   = [];
 
1522     foreach my $i (1 .. $form->{rowcount}) {
 
1523       next unless ($form->{"id_$i"} && $form->{"stock_in_$i"});
 
1525       my $row_sum_base_qty = 0;
 
1526       my $base_unit_factor = $units->{ $part_info_map{$form->{"id_$i"}}->{unit} }->{factor} || 1;
 
1528       foreach my $request (@{ DO->unpack_stock_information('packed' => $form->{"stock_in_$i"}) }) {
 
1529         $request->{parts_id}  = $form->{"id_$i"};
 
1530         $row_sum_base_qty    += $request->{qty} * $units->{$request->{unit}}->{factor} / $base_unit_factor;
 
1532         $request->{project_id} = $form->{"project_id_$i"} || $form->{globalproject_id};
 
1534         push @all_requests, $request;
 
1537       next if (0 == $row_sum_base_qty);
 
1539       my $do_base_qty = $form->parse_amount(\%myconfig, $form->{"qty_$i"}) * $units->{$form->{"unit_$i"}}->{factor} / $base_unit_factor;
 
1541 #      if ($do_base_qty != $row_sum_base_qty) {
 
1542 #        push @{ $form->{ERRORS} }, $locale->text('Error in position #1: You must either assign no stock at all or the full quantity of #2 #3.',
 
1543 #                                                 $i, $form->{"qty_$i"}, $form->{"unit_$i"});
 
1547     if (@{ $form->{ERRORS} }) {
 
1548       push @{ $form->{ERRORS} }, $locale->text('The delivery order has not been marked as delivered. The warehouse contents have not changed.');
 
1550       set_headings('edit');
 
1552       $main::lxdebug->leave_sub();
 
1554       $::dispatcher->end_request;
 
1558   DO->transfer_in_out('direction' => 'in',
 
1559                       'requests'  => \@all_requests);
 
1561   SL::DB::DeliveryOrder->new(id => $form->{id})->load->update_attributes(delivered => 1);
 
1563   $form->{callback} = 'do.pl?action=edit&type=purchase_delivery_order&id=' . $form->escape($form->{id});
 
1566   $main::lxdebug->leave_sub();
 
1570   $main::lxdebug->enter_sub();
 
1572   my $form     = $main::form;
 
1573   my %myconfig = %main::myconfig;
 
1574   my $locale   = $main::locale;
 
1576   if ($form->{id} && DO->is_marked_as_delivered(id => $form->{id})) {
 
1577     $form->show_generic_error($locale->text('The parts for this delivery order have already been transferred out.'));
 
1580   save(no_redirect => 1);
 
1582   my @part_ids = map { $form->{"id_${_}"} } grep { $form->{"id_${_}"} && $form->{"stock_out_${_}"} } (1 .. $form->{rowcount});
 
1586     my $units         = AM->retrieve_units(\%myconfig, $form);
 
1587     my %part_info_map = IC->get_basic_part_info('id' => \@part_ids);
 
1590     $form->{ERRORS}   = [];
 
1592     foreach my $i (1 .. $form->{rowcount}) {
 
1593       next unless ($form->{"id_$i"} && $form->{"stock_out_$i"});
 
1595       my $row_sum_base_qty = 0;
 
1596       my $base_unit_factor = $units->{ $part_info_map{$form->{"id_$i"}}->{unit} }->{factor} || 1;
 
1598       foreach my $request (@{ DO->unpack_stock_information('packed' => $form->{"stock_out_$i"}) }) {
 
1599         $request->{parts_id} = $form->{"id_$i"};
 
1600         $request->{base_qty} = $request->{qty} * $units->{$request->{unit}}->{factor} / $base_unit_factor;
 
1601         $request->{project_id} = $form->{"project_id_$i"} ? $form->{"project_id_$i"} : $form->{globalproject_id};
 
1603         my $map_key          = join '--', ($form->{"id_$i"}, @{$request}{qw(warehouse_id bin_id chargenumber bestbefore)});
 
1605         $request_map{$map_key}                 ||= $request;
 
1606         $request_map{$map_key}->{sum_base_qty} ||= 0;
 
1607         $request_map{$map_key}->{sum_base_qty}  += $request->{base_qty};
 
1608         $row_sum_base_qty                       += $request->{base_qty};
 
1610         push @all_requests, $request;
 
1613       next if (0 == $row_sum_base_qty);
 
1615       my $do_base_qty = $form->{"qty_$i"} * $units->{$form->{"unit_$i"}}->{factor} / $base_unit_factor;
 
1617 #      if ($do_base_qty != $row_sum_base_qty) {
 
1618 #        push @{ $form->{ERRORS} }, $locale->text('Error in position #1: You must either assign no transfer at all or the full quantity of #2 #3.',
 
1619 #                                                 $i, $form->{"qty_$i"}, $form->{"unit_$i"});
 
1624       my @bin_ids      = map { $_->{bin_id} } values %request_map;
 
1625       my %bin_info_map = WH->get_basic_bin_info('id' => \@bin_ids);
 
1626       my @contents     = DO->get_item_availability('parts_id' => \@part_ids);
 
1628       foreach my $inv (@contents) {
 
1629         my $map_key = join '--', @{$inv}{qw(parts_id warehouse_id bin_id chargenumber bestbefore)};
 
1631         next unless ($request_map{$map_key});
 
1633         my $request    = $request_map{$map_key};
 
1634         $request->{ok} = $request->{sum_base_qty} <= $inv->{qty};
 
1637       foreach my $request (values %request_map) {
 
1638         next if ($request->{ok});
 
1640         my $pinfo = $part_info_map{$request->{parts_id}};
 
1641         my $binfo = $bin_info_map{$request->{bin_id}};
 
1643         if ($::instance_conf->get_show_bestbefore) {
 
1644             push @{ $form->{ERRORS} }, $locale->text("There is not enough available of '#1' at warehouse '#2', bin '#3', #4, #5, for the transfer of #6.",
 
1645                                                      $pinfo->{description},
 
1646                                                      $binfo->{warehouse_description},
 
1647                                                      $binfo->{bin_description},
 
1648                                                      $request->{chargenumber} ? $locale->text('chargenumber #1', $request->{chargenumber}) : $locale->text('no chargenumber'),
 
1649                                                      $request->{bestbefore} ? $locale->text('bestbefore #1', $request->{bestbefore}) : $locale->text('no bestbefore'),
 
1650                                                      $form->format_amount_units('amount'      => $request->{sum_base_qty},
 
1651                                                                                 'part_unit'   => $pinfo->{unit},
 
1652                                                                                 'conv_units'  => 'convertible_not_smaller'));
 
1654             push @{ $form->{ERRORS} }, $locale->text("There is not enough available of '#1' at warehouse '#2', bin '#3', #4, for the transfer of #5.",
 
1655                                                      $pinfo->{description},
 
1656                                                      $binfo->{warehouse_description},
 
1657                                                      $binfo->{bin_description},
 
1658                                                      $request->{chargenumber} ? $locale->text('chargenumber #1', $request->{chargenumber}) : $locale->text('no chargenumber'),
 
1659                                                      $form->format_amount_units('amount'      => $request->{sum_base_qty},
 
1660                                                                                 'part_unit'   => $pinfo->{unit},
 
1661                                                                                 'conv_units'  => 'convertible_not_smaller'));
 
1666     if (@{ $form->{ERRORS} }) {
 
1667       push @{ $form->{ERRORS} }, $locale->text('The delivery order has not been marked as delivered. The warehouse contents have not changed.');
 
1669       set_headings('edit');
 
1671       $main::lxdebug->leave_sub();
 
1673       $::dispatcher->end_request;
 
1676   DO->transfer_in_out('direction' => 'out',
 
1677                       'requests'  => \@all_requests);
 
1679   SL::DB::DeliveryOrder->new(id => $form->{id})->load->update_attributes(delivered => 1);
 
1681   $form->{callback} = 'do.pl?action=edit&type=sales_delivery_order&id=' . $form->escape($form->{id});
 
1684   $main::lxdebug->leave_sub();
 
1688   $main::lxdebug->enter_sub();
 
1690   my $form     = $main::form;
 
1692   DO->close_orders('ids' => [ $form->{id} ]);
 
1694   $form->{closed} = 1;
 
1698   $main::lxdebug->leave_sub();
 
1702   $::lxdebug->enter_sub;
 
1704   $::auth->assert('purchase_delivery_order_edit | sales_delivery_order_edit');
 
1707   retrieve_partunits();
 
1709   my $new_rowcount = $::form->{"rowcount"} * 1 + 1;
 
1710   $::form->{"project_id_${new_rowcount}"} = $::form->{"globalproject_id"};
 
1712   $::form->language_payment(\%::myconfig);
 
1714   Common::webdav_folder($::form);
 
1717   display_row(++$::form->{rowcount});
 
1720   $::lxdebug->leave_sub;
 
1724   call_sub($main::form->{yes_nextsub});
 
1728   call_sub($main::form->{no_nextsub});
 
1732   call_sub($main::form->{update_nextsub} || $main::form->{nextsub} || 'update_delivery_order');
 
1736   my $form     = $main::form;
 
1737   my $locale   = $main::locale;
 
1739   foreach my $action (qw(update print e_mail save transfer_out transfer_out_default sort
 
1740                          transfer_in transfer_in_default mark_closed save_as_new invoice delete)) {
 
1741     if ($form->{"action_${action}"}) {
 
1747   $form->error($locale->text('No action defined.'));
 
1750 sub transfer_out_default {
 
1751   $main::lxdebug->enter_sub();
 
1753   my $form     = $main::form;
 
1755   transfer_in_out_default('direction' => 'out');
 
1757   $main::lxdebug->leave_sub();
 
1760 sub transfer_in_default {
 
1761   $main::lxdebug->enter_sub();
 
1763   my $form     = $main::form;
 
1765   transfer_in_out_default('direction' => 'in');
 
1767   $main::lxdebug->leave_sub();
 
1770 # Falls das Standardlagerverfahren aktiv ist, wird
 
1771 # geprüft, ob alle Standardlagerplätze für die Auslager-
 
1772 # artikel vorhanden sind UND ob die Warenmenge ausreicht zum
 
1773 # Auslagern. Falls nicht wird entsprechend eine Fehlermeldung
 
1774 # generiert. Offen Chargennummer / bestbefore wird nicht berücksichtigt
 
1775 sub transfer_in_out_default {
 
1776   $main::lxdebug->enter_sub();
 
1778   my $form     = $main::form;
 
1779   my %myconfig = %main::myconfig;
 
1780   my $locale   = $main::locale;
 
1783   my (%missing_default_bins, %qty_parts, @all_requests, %part_info_map, $default_warehouse_id, $default_bin_id);
 
1785   Common::check_params(\%params, qw(direction));
 
1787   # entsprechende defaults holen, falls standardlagerplatz verwendet werden soll
 
1788   if ($::instance_conf->get_transfer_default_use_master_default_bin) {
 
1789     $default_warehouse_id = $::instance_conf->get_warehouse_id;
 
1790     $default_bin_id       = $::instance_conf->get_bin_id;
 
1794   my @part_ids = map { $form->{"id_${_}"} } (1 .. $form->{rowcount});
 
1796     my $units         = AM->retrieve_units(\%myconfig, $form);
 
1797     %part_info_map = IC->get_basic_part_info('id' => \@part_ids);
 
1798     foreach my $i (1 .. $form->{rowcount}) {
 
1799       next unless ($form->{"id_$i"});
 
1800       my $base_unit_factor = $units->{ $part_info_map{$form->{"id_$i"}}->{unit} }->{factor} || 1;
 
1801       my $qty =   $form->parse_amount(\%myconfig, $form->{"qty_$i"}) * $units->{$form->{"unit_$i"}}->{factor} / $base_unit_factor;
 
1803       $form->show_generic_error($locale->text("Cannot transfer negative entries." )) if ($qty < 0);
 
1804       # if we do not want to transfer services and this part is a service, set qty to zero
 
1805       # ... and do not create a hash entry in %qty_parts below (will skip check for bins for the transfer == out case)
 
1806       # ... and push only a empty (undef) element to @all_requests (will skip check for bin_id and warehouse_id and will not alter the row)
 
1808       $qty = 0 if (!$::instance_conf->get_transfer_default_services && !defined($part_info_map{$form->{"id_$i"}}->{inventory_accno_id}) && !$part_info_map{$form->{"id_$i"}}->{assembly});
 
1809       $qty_parts{$form->{"id_$i"}} += $qty;
 
1811         delete $qty_parts{$form->{"id_$i"}} unless $qty_parts{$form->{"id_$i"}};
 
1812         undef $form->{"stock_in_$i"};
 
1815       $part_info_map{$form->{"id_$i"}}{bin_id}       ||= $default_bin_id;
 
1816       $part_info_map{$form->{"id_$i"}}{warehouse_id} ||= $default_warehouse_id;
 
1818       push @all_requests, ($qty == 0) ? { } : {
 
1819                         'chargenumber' => '',  #?? die müsste entsprechend geholt werden
 
1820                         #'bestbefore' => undef, # TODO wird nicht berücksichtigt
 
1821                         'bin_id' => $part_info_map{$form->{"id_$i"}}{bin_id},
 
1823                         'parts_id' => $form->{"id_$i"},
 
1824                         'comment' => $locale->text("Default transfer delivery order"),
 
1825                         'unit' => $part_info_map{$form->{"id_$i"}}{unit},
 
1826                         'warehouse_id' => $part_info_map{$form->{"id_$i"}}{warehouse_id},
 
1827                         'oe_id' => $form->{id},
 
1828                         'project_id' => $form->{"project_id_$i"} ? $form->{"project_id_$i"} : $form->{globalproject_id}
 
1832     # jetzt wird erst überprüft, ob die Stückzahl entsprechend stimmt.
 
1833     # check if bin (transfer in and transfer out and qty (transfer out) is correct
 
1834     foreach my $key (keys %qty_parts) {
 
1836       $missing_default_bins{$key}{missing_bin} = 1 unless ($part_info_map{$key}{bin_id});
 
1837       next unless ($part_info_map{$key}{bin_id}); # abbruch
 
1839       if ($params{direction} eq 'out') {  # wird nur für ausgehende Mengen benötigt
 
1840         my ($max_qty, $error) = WH->get_max_qty_parts_bin(parts_id => $key, bin_id => $part_info_map{$key}{bin_id});
 
1842           # wir können nicht entscheiden, welche charge oder mhd (bestbefore) ausgewählt sein soll
 
1843           # deshalb rückmeldung nach oben geben, manuell auszulagern
 
1844           # TODO Bei nur einem Treffer mit Charge oder bestbefore wäre das noch möglich
 
1845           $missing_default_bins{$key}{chargenumber} = 1;
 
1847         if ($max_qty < $qty_parts{$key}){
 
1848           $missing_default_bins{$key}{missing_qty} = $max_qty - $qty_parts{$key};
 
1854   # Abfrage für Fehlerbehandlung (nur bei direction == out)
 
1855   if (scalar (keys %missing_default_bins)) {
 
1857     foreach my $fehler (keys %missing_default_bins) {
 
1859       my $ware = WH->get_part_description(parts_id => $fehler);
 
1860       if ($missing_default_bins{$fehler}{missing_bin}){
 
1861         $fehlertext .= "Kein Standardlagerplatz definiert bei $ware <br>";
 
1863       if ($missing_default_bins{$fehler}{missing_qty}) {  # missing_qty
 
1864         $fehlertext .= "Es fehlen " . $missing_default_bins{$fehler}{missing_qty}*-1 .
 
1865                        " von $ware auf dem Standard-Lagerplatz " . $part_info_map{$fehler}{bin} .   " zum Auslagern<br>";
 
1867       if ($missing_default_bins{$fehler}{chargenumber}){
 
1868         $fehlertext .= "Die Ware hat eine Chargennummer oder eine Mindesthaltbarkeit definiert.
 
1869                         Hier kann man nicht automatisch entscheiden.
 
1870                         Bitte diesen Lieferschein manuell auslagern.
 
1873       # auslagern soll immer gehen, auch wenn nicht genügend auf lager ist.
 
1874       # der lagerplatz ist hier extra konfigurierbar, bspw. Lager-Korrektur mit
 
1875       # Lagerplatz Lagerplatz-Korrektur
 
1876       my $default_warehouse_id_ignore_onhand = $::instance_conf->get_warehouse_id_ignore_onhand;
 
1877       my $default_bin_id_ignore_onhand       = $::instance_conf->get_bin_id_ignore_onhand;
 
1878       if ($::instance_conf->get_transfer_default_ignore_onhand && $default_bin_id_ignore_onhand) {
 
1879         # entsprechende defaults holen
 
1880         # falls chargenumber, bestbefore oder anzahl nicht stimmt, auf automatischen
 
1881         # lagerplatz wegbuchen!
 
1882         foreach (@all_requests) {
 
1883           if ($_->{parts_id} eq $fehler){
 
1884           $_->{bin_id}        = $default_bin_id_ignore_onhand;
 
1885           $_->{warehouse_id}  = $default_warehouse_id_ignore_onhand;
 
1889         #$main::lxdebug->message(0, 'Fehlertext: ' . $fehlertext);
 
1890         $form->show_generic_error($locale->text("Cannot transfer. <br> Reason:<br>#1", $fehlertext ));
 
1896   # hier der eigentliche fallunterschied für in oder out
 
1897   my $prefix   = $params{direction} eq 'in' ? 'in' : 'out';
 
1899   # dieser array_ref ist für DO->save da:
 
1900   # einmal die all_requests in YAML verwandeln, damit delivery_order_items_stock
 
1901   # gefüllt werden kann.
 
1902   # could be dumped to the form in the first loop,
 
1903   # but maybe bin_id and warehouse_id has changed to the "korrekturlager" with
 
1904   # allowed negative qty ($::instance_conf->get_warehouse_id_ignore_onhand) ...
 
1906   foreach (@all_requests){
 
1908     next unless scalar(%{ $_ });
 
1909     $form->{"stock_${prefix}_$i"} = YAML::Dump([$_]);
 
1912   save(no_redirect => 1); # Wir können auslagern, deshalb beleg speichern
 
1913                           # und in delivery_order_items_stock speichern
 
1915   # ... and fill back the persistent dois_id for inventory fk
 
1916   undef (@all_requests);
 
1917   foreach my $i (1 .. $form->{rowcount}) {
 
1918     next unless ($form->{"id_$i"} && $form->{"stock_${prefix}_$i"});
 
1919     push @all_requests, @{ DO->unpack_stock_information('packed' => $form->{"stock_${prefix}_$i"}) };
 
1921   DO->transfer_in_out('direction' => $prefix,
 
1922                       'requests'  => \@all_requests);
 
1924   SL::DB::DeliveryOrder->new(id => $form->{id})->load->update_attributes(delivered => 1);
 
1926   $form->{callback} = 'do.pl?action=edit&type=sales_delivery_order&id=' . $form->escape($form->{id}) if $params{direction} eq 'out';
 
1927   $form->{callback} = 'do.pl?action=edit&type=purchase_delivery_order&id=' . $form->escape($form->{id}) if $params{direction} eq 'in';
 
1933   $main::lxdebug->enter_sub();
 
1937   my $form     = $main::form;
 
1940   save(no_redirect => 1); # has to be done, at least for newly added positions
 
1942   # hashify partnumbers, positions. key is delivery_order_items_id
 
1943   for my $i (1 .. ($form->{rowcount}) ) {
 
1944     $temp_hash{$form->{"delivery_order_items_id_$i"}} = { runningnumber => $form->{"runningnumber_$i"}, partnumber => $form->{"partnumber_$i"} };
 
1945     if ($form->{id} && $form->{"discount_$i"}) {
 
1946       # prepare_order assumes a db value if there is a form->id and multiplies *100
 
1947       # We hope for new controller code (no more format_amount/parse_amount distinction)
 
1948       $form->{"discount_$i"} /=100;
 
1951   # naturally sort partnumbers and get a sorted array of doi_ids
 
1952   my @sorted_doi_ids =  sort { Sort::Naturally::ncmp($temp_hash{$a}->{"partnumber"}, $temp_hash{$b}->{"partnumber"}) }  keys %temp_hash;
 
1957   for (@sorted_doi_ids) {
 
1958     $form->{"runningnumber_$temp_hash{$_}->{runningnumber}"} = $new_number;
 
1961   # all parse_amounts changes are in form (i.e. , to .) therefore we need
 
1962   # another format_amount to change it back, for the next save ;-(
 
1963   # works great except for row discounts (see above comment)
 
1967     $main::lxdebug->leave_sub();
 
1979 do.pl - Script for all calls to delivery order
 
1987 Sorts all position with Natural Sort. Can be activated in form_footer.html like this
 
1988 C<E<lt>input class="submit" type="submit" name="action_sort" id="sort_button" value="[% 'Sort and Save' | $T8 %]"E<gt>>
 
1994 Sort and Save can be implemented as an optional button if configuration ca be set by client config.
 
1995 Example coding for database scripts and templates in (git show af2f24b8), check also
 
1996 autogeneration for rose (scripts/rose_auto_create_model.pl --h)