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;
 
 242   my $is_customer    = $::form->{vc} eq 'customer';
 
 244   for my $bar ($::request->layout->get('actionbar')) {
 
 248           submit    => [ '#form', { action => "update" } ],
 
 249           id        => 'update_button',
 
 250           accesskey => 'enter',
 
 256           submit   => [ '#form', { action => "save" } ],
 
 257           checks   => [ @req_trans_desc ],
 
 258           disabled => $::form->{delivered} ? t8('This record has already been delivered.') : undef,
 
 262           submit   => [ '#form', { action => "save_as_new" } ],
 
 263           checks   => [ @req_trans_desc ],
 
 264           disabled => !$::form->{id},
 
 267           t8('Mark as closed'),
 
 268           submit   => [ '#form', { action => "mark_closed" } ],
 
 269           checks   => [ @req_trans_desc ],
 
 270           confirm  => t8('This will remove the delivery order from showing as open even if contents are not delivered. Proceed?'),
 
 271           disabled => !$::form->{id}    ? t8('This record has not been saved yet.')
 
 272                     : $::form->{closed} ? t8('This record has already been closed.')
 
 275       ], # end of combobox "Save"
 
 279         submit   => [ '#form', { action => "delete" } ],
 
 280         confirm  => t8('Do you really want to delete this object?'),
 
 281         disabled => !$::form->{id}                                                                              ? t8('This record has not been saved yet.')
 
 282                   : $::form->{delivered}                                                                        ? t8('This record has already been delivered.')
 
 283                   : ($::form->{vc} eq 'customer' && !$::instance_conf->get_sales_delivery_order_show_delete)    ? t8('Deleting this type of record has been disabled in the configuration.')
 
 284                   : ($::form->{vc} eq 'vendor'   && !$::instance_conf->get_purchase_delivery_order_show_delete) ? t8('Deleting this type of record has been disabled in the configuration.')
 
 291           submit   => [ '#form', { action => "transfer_out" } ],
 
 292           checks   => [ @req_trans_desc, @transfer_qty ],
 
 293           disabled => $::form->{delivered} ? t8('This record has already been delivered.') : undef,
 
 294           only_if  => $is_customer,
 
 297           t8('Transfer out via default'),
 
 298           submit   => [ '#form', { action => "transfer_out_default" } ],
 
 299           checks   => [ @req_trans_desc, @transfer_qty ],
 
 300           disabled => $::form->{delivered} ? t8('This record has already been delivered.') : undef,
 
 301           only_if  => $is_customer && $::instance_conf->get_transfer_default,
 
 305           submit   => [ '#form', { action => "transfer_in"> 1 } ],
 
 306           checks   => [ @req_trans_desc, @transfer_qty ],
 
 307           disabled => $::form->{delivered} ? t8('This record has already been delivered.') : undef,
 
 308           only_if  => !$is_customer,
 
 311           t8('Transfer in via default'),
 
 312           submit   => [ '#form', { action => "transfer_in_default" } ],
 
 313           checks   => [ @req_trans_desc, @transfer_qty ],
 
 314           disabled => $::form->{delivered} ? t8('This record has already been delivered.') : undef,
 
 315           only_if  => !$is_customer && $::instance_conf->get_transfer_default,
 
 317       ], # end of combobox "Transfer out"
 
 324         submit => [ '#form', { action => "invoice" } ],
 
 325         disabled => !$::form->{id} ? t8('This record has not been saved yet.') : undef,
 
 329         action => [ t8('Export') ],
 
 332           call   => [ 'kivi.SalesPurchase.show_print_dialog' ],
 
 333           checks => [ @req_trans_desc ],
 
 337           call   => [ 'kivi.SalesPurchase.show_email_dialog' ],
 
 338           checks => [ @req_trans_desc ],
 
 340       ], # end of combobox "Export"
 
 343         action => [ t8('more') ],
 
 346           call     => [ 'set_history_window', $::form->{id} * 1, 'id' ],
 
 347           disabled => !$::form->{id} ? t8('This record has not been saved yet.') : undef,
 
 351           call     => [ 'follow_up_window' ],
 
 352           disabled => !$::form->{id} ? t8('This record has not been saved yet.') : undef,
 
 354       ], # end if combobox "more"
 
 359 sub setup_do_search_action_bar {
 
 362   for my $bar ($::request->layout->get('actionbar')) {
 
 366         submit    => [ '#form' ],
 
 367         accesskey => 'enter',
 
 373 sub setup_do_orders_action_bar {
 
 376   for my $bar ($::request->layout->get('actionbar')) {
 
 380         submit    => [ '#form', { action => 'invoice_multi' } ],
 
 381         checks    => [ [ 'kivi.check_if_entries_selected', '#form tbody input[type=checkbox]' ] ],
 
 382         accesskey => 'enter',
 
 386         call   => [ 'kivi.SalesPurchase.show_print_dialog', 'js:kivi.MassDeliveryOrderPrint.submitMultiOrders' ],
 
 387         checks => [ [ 'kivi.check_if_entries_selected', '#form tbody input[type=checkbox]' ] ],
 
 394   $main::lxdebug->enter_sub();
 
 398   my $form     = $main::form;
 
 399   my %myconfig = %main::myconfig;
 
 401   my $class       = "SL::DB::" . ($form->{vc} eq 'customer' ? 'Customer' : 'Vendor');
 
 402   $form->{VC_OBJ} = $class->load_cached($form->{ $form->{vc} . '_id' });
 
 404   $form->{employee_id} = $form->{old_employee_id} if $form->{old_employee_id};
 
 405   $form->{salesman_id} = $form->{old_salesman_id} if $form->{old_salesman_id};
 
 407   my $vc = $form->{vc} eq "customer" ? "customers" : "vendors";
 
 408   $form->get_lists("price_factors"  => "ALL_PRICE_FACTORS",
 
 409                    "business_types" => "ALL_BUSINESS_TYPES",
 
 411   $form->{ALL_DEPARTMENTS} = SL::DB::Manager::Department->get_all;
 
 414   my @old_project_ids = uniq grep { $_ } map { $_ * 1 } ($form->{"globalproject_id"}, map { $form->{"project_id_$_"} } 1..$form->{"rowcount"});
 
 415   my @old_ids_cond    = @old_project_ids ? (id => \@old_project_ids) : ();
 
 417   if (($vc eq 'customers') && $::instance_conf->get_customer_projects_only_in_sales) {
 
 420         customer_id          => $::form->{customer_id},
 
 421         billable_customer_id => $::form->{customer_id},
 
 426       and => [ active => 1, @customer_cond ],
 
 430   $::form->{ALL_PROJECTS}          = SL::DB::Manager::Project->get_all_sorted(query => \@conditions);
 
 431   $::form->{ALL_EMPLOYEES}         = SL::DB::Manager::Employee->get_all_sorted(query => [ or => [ id => $::form->{employee_id},  deleted => 0 ] ]);
 
 432   $::form->{ALL_SALESMEN}          = SL::DB::Manager::Employee->get_all_sorted(query => [ or => [ id => $::form->{salesman_id},  deleted => 0 ] ]);
 
 433   $::form->{ALL_SHIPTO}            = SL::DB::Manager::Shipto->get_all_sorted(query => [
 
 434     or => [ trans_id  => $::form->{"$::form->{vc}_id"} * 1, and => [ shipto_id => $::form->{shipto_id} * 1, trans_id => undef ] ]
 
 436   $::form->{ALL_CONTACTS}          = SL::DB::Manager::Contact->get_all_sorted(query => [
 
 438       cp_cv_id => $::form->{"$::form->{vc}_id"} * 1,
 
 441         cp_id    => $::form->{cp_id} * 1
 
 446   my $dispatch_to_popup = '';
 
 447   if ($form->{resubmit} && ($form->{format} eq "html")) {
 
 448     $dispatch_to_popup  = "window.open('about:blank','Beleg'); document.do.target = 'Beleg';";
 
 449     $dispatch_to_popup .= "document.do.submit();";
 
 450   } elsif ($form->{resubmit}) {
 
 451     # emulate click for resubmitting actions
 
 452     $dispatch_to_popup  = "document.do.${_}.click(); " for grep { /^action_/ } keys %$form;
 
 454   $::request->{layout}->add_javascripts_inline("\$(function(){$dispatch_to_popup});");
 
 457   $form->{follow_up_trans_info} = $form->{donumber} .'('. $form->{VC_OBJ}->name .')';
 
 459   $::request->{layout}->use_javascript(map { "${_}.js" } qw(kivi.File kivi.MassDeliveryOrderPrint kivi.SalesPurchase kivi.Part ckeditor/ckeditor ckeditor/adapters/jquery kivi.io autocomplete_customer));
 
 462   push @custom_hidden, map { "shiptocvar_" . $_->name } @{ SL::DB::Manager::CustomVariableConfig->get_all(where => [ module => 'ShipTo' ]) };
 
 464   $::form->{HIDDENS} = [ map { +{ name => $_, value => $::form->{$_} } } (@custom_hidden) ];
 
 466   setup_do_action_bar();
 
 469   # Fix für Bug 1082 Erwartet wird: 'abteilungsNAME--abteilungsID'
 
 470   # und Erweiterung für Bug 1760:
 
 471   # Das war leider nur ein Teil-Fix, da das Verhalten den 'Erneuern'-Knopf
 
 472   # nicht überlebt. Konsequent jetzt auf L umgestellt
 
 473   #   $ perldoc SL::Template::Plugin::L
 
 474   # Daher entsprechend nur die Anpassung in form_header
 
 475   # und in DO.pm gemacht. 4 Testfälle:
 
 476   # department_id speichern                 | i.O.
 
 477   # department_id lesen                     | i.O.
 
 478   # department leer überlebt erneuern       | i.O.
 
 479   # department nicht leer überlebt erneuern | i.O.
 
 480   # $main::lxdebug->message(0, 'ABTEILUNGS ID in form?' . $form->{department_id});
 
 481   print $form->parse_html_template('do/form_header');
 
 483   $main::lxdebug->leave_sub();
 
 487   $main::lxdebug->enter_sub();
 
 491   my $form     = $main::form;
 
 493   $form->{PRINT_OPTIONS}      = setup_sales_purchase_print_options();
 
 494   $form->{ALL_DELIVERY_TERMS} = SL::DB::Manager::DeliveryTerm->get_all_sorted();
 
 496   print $form->parse_html_template('do/form_footer',
 
 497     {transfer_default         => ($::instance_conf->get_transfer_default)});
 
 499   $main::lxdebug->leave_sub();
 
 502 sub update_delivery_order {
 
 503   $main::lxdebug->enter_sub();
 
 507   my $form     = $main::form;
 
 508   my %myconfig = %main::myconfig;
 
 510   set_headings($form->{"id"} ? "edit" : "add");
 
 512   $form->{insertdate} = SL::DB::DeliveryOrder->new(id => $form->{id})->load->itime_as_date if $form->{id};
 
 517   $payment_id = $form->{payment_id} if $form->{payment_id};
 
 519   my $vc = $form->{vc};
 
 520   if (($form->{"previous_${vc}_id"} || $form->{"${vc}_id"}) != $form->{"${vc}_id"}) {
 
 521     $::form->{salesman_id} = SL::DB::Manager::Employee->current->id if exists $::form->{salesman_id};
 
 523     IS->get_customer(\%myconfig, $form) if $vc eq 'customer';
 
 524     IR->get_vendor(\%myconfig, $form)   if $vc eq 'vendor';
 
 527   $form->{discount} =  $form->{"$form->{vc}_discount"} if defined $form->{"$form->{vc}_discount"};
 
 528   # Problem: Wenn man ohne Erneuern einen Kunden/Lieferanten
 
 529   # wechselt, wird der entsprechende Kunden/ Lieferantenrabatt
 
 530   # nicht übernommen. Grundproblem: In Commit 82574e78
 
 531   # hab ich aus discount customer_discount und vendor_discount
 
 532   # gemacht und entsprechend an den Oberflächen richtig hin-
 
 533   # geschoben. Die damals bessere Lösung wäre gewesen:
 
 534   # In den Templates nur die hidden für form-discount wieder ein-
 
 535   # setzen dann wäre die Verrenkung jetzt nicht notwendig.
 
 536   # TODO: Ggf. Bugfix 1284, 1575 und 817 wieder zusammenführen
 
 537   # Testfälle: Kunden mit Rabatt 0 -> Rabatt 20 i.O.
 
 538   #            Kunde mit Rabatt 20 -> Rabatt 0  i.O.
 
 539   #            Kunde mit Rabatt 20 -> Rabatt 5,5 i.O.
 
 540   $form->{payment_id} = $payment_id if $form->{payment_id} eq "";
 
 542   my $i = $form->{rowcount};
 
 544   if (   ($form->{"partnumber_$i"} eq "")
 
 545       && ($form->{"description_$i"} eq "")
 
 546       && ($form->{"partsgroup_$i"}  eq "")) {
 
 553     if ($form->{type} eq 'purchase_delivery_order') {
 
 554       IR->retrieve_item(\%myconfig, $form);
 
 557       IS->retrieve_item(\%myconfig, $form);
 
 561     my $rows = scalar @{ $form->{item_list} };
 
 564       $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
 
 565       if( !$form->{"qty_$i"} ) {
 
 566         $form->{"qty_$i"} = 1;
 
 571         select_item(mode => $mode, pre_entered_qty => $form->{"qty_$i"});
 
 572         $::dispatcher->end_request;
 
 576         my $sellprice = $form->parse_amount(\%myconfig, $form->{"sellprice_$i"});
 
 578         map { $form->{"${_}_$i"} = $form->{item_list}[0]{$_} } keys %{ $form->{item_list}[0] };
 
 580         $form->{"marge_price_factor_$i"} = $form->{item_list}->[0]->{price_factor};
 
 583           $form->{"sellprice_$i"} = $sellprice;
 
 585           my $record        = _make_record();
 
 586           my $price_source  = SL::PriceSource->new(record_item => $record->items->[$i-1], record => $record);
 
 587           my $best_price    = $price_source->best_price;
 
 588           my $best_discount = $price_source->best_discount;
 
 591             $::form->{"sellprice_$i"}           = $best_price->price;
 
 592             $::form->{"active_price_source_$i"} = $best_price->source;
 
 594           if ($best_discount) {
 
 595             $::form->{"discount_$i"}               = $best_discount->discount;
 
 596             $::form->{"active_discount_source_$i"} = $best_discount->source;
 
 600         $form->{"sellprice_$i"}          = $form->format_amount(\%myconfig, $form->{"sellprice_$i"});
 
 601         $form->{"lastcost_$i"}           = $form->format_amount(\%myconfig, $form->{"lastcost_$i"});
 
 602         $form->{"qty_$i"}                = $form->format_amount(\%myconfig, $form->{"qty_$i"});
 
 603         $form->{"discount_$i"}           = $form->format_amount(\%myconfig, $form->{"discount_$i"} * 100.0);
 
 610       # ok, so this is a new part
 
 611       # ask if it is a part or service item
 
 613       if (   $form->{"partsgroup_$i"}
 
 614           && ($form->{"partsnumber_$i"} eq "")
 
 615           && ($form->{"description_$i"} eq "")) {
 
 617         $form->{"discount_$i"} = "";
 
 618         $form->{"not_discountable_$i"} = "";
 
 622         $form->{"id_$i"}   = 0;
 
 628   $main::lxdebug->leave_sub();
 
 632   $main::lxdebug->enter_sub();
 
 636   my $form     = $main::form;
 
 637   my %myconfig = %main::myconfig;
 
 638   my $locale   = $main::locale;
 
 640   $form->{vc} = $form->{type} eq 'purchase_delivery_order' ? 'vendor' : 'customer';
 
 642   $form->get_lists("projects"       => { "key" => "ALL_PROJECTS",
 
 644                    "business_types" => "ALL_BUSINESS_TYPES");
 
 645   $form->{ALL_EMPLOYEES} = SL::DB::Manager::Employee->get_all_sorted(query => [ deleted => 0 ]);
 
 646   $form->{ALL_DEPARTMENTS} = SL::DB::Manager::Department->get_all;
 
 647   $form->{title}             = $locale->text('Delivery Orders');
 
 649   setup_do_search_action_bar();
 
 653   print $form->parse_html_template('do/search');
 
 655   $main::lxdebug->leave_sub();
 
 659   $main::lxdebug->enter_sub();
 
 663   my $form     = $main::form;
 
 664   my %myconfig = %main::myconfig;
 
 665   my $locale   = $main::locale;
 
 666   my $cgi      = $::request->{cgi};
 
 668   $::request->{layout}->use_javascript(map { "${_}.js" } qw(kivi.MassDeliveryOrderPrint kivi.SalesPurchase));
 
 669   ($form->{ $form->{vc} }, $form->{"$form->{vc}_id"}) = split(/--/, $form->{ $form->{vc} });
 
 671   report_generator_set_default_sort('transdate', 1);
 
 675   $form->{rowcount} = scalar @{ $form->{DO} };
 
 678     ids                     transdate               reqdate
 
 680     ordnumber               customernumber          cusordnumber
 
 681     name                    employee  salesman
 
 682     shipvia                 globalprojectnumber
 
 683     transaction_description department
 
 688   $form->{l_open}      = $form->{l_closed} = "Y" if ($form->{open}      && $form->{closed});
 
 689   $form->{l_delivered} = "Y"                     if ($form->{delivered} && $form->{notdelivered});
 
 691   $form->{title}       = $locale->text('Delivery Orders');
 
 693   my $attachment_basename = $form->{vc} eq 'vendor' ? $locale->text('purchase_delivery_order_list') : $locale->text('sales_delivery_order_list');
 
 695   my $report = SL::ReportGenerator->new(\%myconfig, $form);
 
 697   my @hidden_variables = map { "l_${_}" } @columns;
 
 698   push @hidden_variables, $form->{vc}, qw(l_closed l_notdelivered open closed delivered notdelivered donumber ordnumber serialnumber cusordnumber
 
 699                                           transaction_description transdatefrom transdateto reqdatefrom reqdateto
 
 700                                           type vc employee_id salesman_id project_id parts_partnumber parts_description
 
 701                                           insertdatefrom insertdateto business_id);
 
 703   my $href = build_std_url('action=orders', grep { $form->{$_} } @hidden_variables);
 
 706     'ids'                     => { 'text' => '<input type="checkbox" id="multi_all" value="1">', 'align' => 'center' },
 
 707     'transdate'               => { 'text' => $locale->text('Delivery Order Date'), },
 
 708     'reqdate'                 => { 'text' => $locale->text('Reqdate'), },
 
 709     'id'                      => { 'text' => $locale->text('ID'), },
 
 710     'donumber'                => { 'text' => $locale->text('Delivery Order'), },
 
 711     'ordnumber'               => { 'text' => $locale->text('Order'), },
 
 712     'customernumber'          => { 'text' => $locale->text('Customer Number'), },
 
 713     'cusordnumber'            => { 'text' => $locale->text('Customer Order Number'), },
 
 714     'name'                    => { 'text' => $form->{vc} eq 'customer' ? $locale->text('Customer') : $locale->text('Vendor'), },
 
 715     'employee'                => { 'text' => $locale->text('Employee'), },
 
 716     'salesman'                => { 'text' => $locale->text('Salesman'), },
 
 717     'shipvia'                 => { 'text' => $locale->text('Ship via'), },
 
 718     'globalprojectnumber'     => { 'text' => $locale->text('Project Number'), },
 
 719     'transaction_description' => { 'text' => $locale->text('Transaction description'), },
 
 720     'open'                    => { 'text' => $locale->text('Open'), },
 
 721     'delivered'               => { 'text' => $locale->text('Delivered'), },
 
 722     'department'              => { 'text' => $locale->text('Department'), },
 
 723     'insertdate'              => { 'text' => $locale->text('Insert Date'), },
 
 726   foreach my $name (qw(id transdate reqdate donumber ordnumber name employee salesman shipvia transaction_description department insertdate)) {
 
 727     my $sortdir                 = $form->{sort} eq $name ? 1 - $form->{sortdir} : $form->{sortdir};
 
 728     $column_defs{$name}->{link} = $href . "&sort=$name&sortdir=$sortdir";
 
 731   $form->{"l_type"} = "Y";
 
 732   map { $column_defs{$_}->{visible} = $form->{"l_${_}"} ? 1 : 0 } @columns;
 
 734   $column_defs{ids}->{visible} = 'HTML';
 
 736   $report->set_columns(%column_defs);
 
 737   $report->set_column_order(@columns);
 
 739   $report->set_export_options('orders', @hidden_variables, qw(sort sortdir));
 
 741   $report->set_sort_indicator($form->{sort}, $form->{sortdir});
 
 744   if ($form->{customer}) {
 
 745     push @options, $locale->text('Customer') . " : $form->{customer}";
 
 747   if ($form->{vendor}) {
 
 748     push @options, $locale->text('Vendor') . " : $form->{vendor}";
 
 750   if ($form->{cp_name}) {
 
 751     push @options, $locale->text('Contact Person') . " : $form->{cp_name}";
 
 753   if ($form->{department_id}) {
 
 754     push @options, $locale->text('Department') . " : " . SL::DB::Department->new(id => $form->{department_id})->load->description;
 
 756   if ($form->{donumber}) {
 
 757     push @options, $locale->text('Delivery Order Number') . " : $form->{donumber}";
 
 759   if ($form->{ordnumber}) {
 
 760     push @options, $locale->text('Order Number') . " : $form->{ordnumber}";
 
 762   push @options, $locale->text('Serial Number') . " : $form->{serialnumber}" if $form->{serialnumber};
 
 763   if ($form->{business_id}) {
 
 764     my $vc_type_label = $form->{vc} eq 'customer' ? $locale->text('Customer type') : $locale->text('Vendor type');
 
 765     push @options, $vc_type_label . " : " . SL::DB::Business->new(id => $form->{business_id})->load->description;
 
 767   if ($form->{transaction_description}) {
 
 768     push @options, $locale->text('Transaction description') . " : $form->{transaction_description}";
 
 770   if ($form->{parts_description}) {
 
 771     push @options, $locale->text('Part Description') . " : $form->{parts_description}";
 
 773   if ($form->{parts_partnumber}) {
 
 774     push @options, $locale->text('Part Number') . " : $form->{parts_partnumber}";
 
 776   if ( $form->{transdatefrom} or $form->{transdateto} ) {
 
 777     push @options, $locale->text('Delivery Order Date');
 
 778     push @options, $locale->text('From') . " " . $locale->date(\%myconfig, $form->{transdatefrom}, 1)     if $form->{transdatefrom};
 
 779     push @options, $locale->text('Bis')  . " " . $locale->date(\%myconfig, $form->{transdateto},   1)     if $form->{transdateto};
 
 781   if ( $form->{reqdatefrom} or $form->{reqdateto} ) {
 
 782     push @options, $locale->text('Reqdate');
 
 783     push @options, $locale->text('From') . " " . $locale->date(\%myconfig, $form->{reqdatefrom}, 1)       if $form->{reqdatefrom};
 
 784     push @options, $locale->text('Bis')  . " " . $locale->date(\%myconfig, $form->{reqdateto},   1)       if $form->{reqdateto};
 
 786   if ( $form->{insertdatefrom} or $form->{insertdateto} ) {
 
 787     push @options, $locale->text('Insert Date');
 
 788     push @options, $locale->text('From') . " " . $locale->date(\%myconfig, $form->{insertdatefrom}, 1)    if $form->{insertdatefrom};
 
 789     push @options, $locale->text('Bis')  . " " . $locale->date(\%myconfig, $form->{insertdateto},   1)    if $form->{insertdateto};
 
 792     push @options, $locale->text('Open');
 
 794   if ($form->{closed}) {
 
 795     push @options, $locale->text('Closed');
 
 797   if ($form->{delivered}) {
 
 798     push @options, $locale->text('Delivered');
 
 800   if ($form->{notdelivered}) {
 
 801     push @options, $locale->text('Not delivered');
 
 804   my $pr = SL::DB::Manager::Printer->find_by(
 
 805       printer_description => $::locale->text("sales_delivery_order_printer"));
 
 807       $form->{printer_id} = $pr->id;
 
 810   my $print_options = SL::Helper::PrintOptions->get_print_options(
 
 812       hide_language_id => 1,
 
 818   $report->set_options('top_info_text'        => join("\n", @options),
 
 819                        'raw_top_info_text'    => $form->parse_html_template('do/orders_top'),
 
 820                        'raw_bottom_info_text' => $form->parse_html_template('do/orders_bottom', { print_options => $print_options }),
 
 821                        'output_format'        => 'HTML',
 
 822                        'title'                => $form->{title},
 
 823                        'attachment_basename'  => $attachment_basename . strftime('_%Y%m%d', localtime time),
 
 825   $report->set_options_from_form();
 
 826   $locale->set_numberformat_wo_thousands_separator(\%myconfig) if lc($report->{options}->{output_format}) eq 'csv';
 
 828   # add sort and escape callback, this one we use for the add sub
 
 829   $form->{callback} = $href .= "&sort=$form->{sort}";
 
 831   # escape callback for href
 
 832   my $callback = $form->escape($href);
 
 834   my $edit_url       = build_std_url('action=edit', 'type', 'vc');
 
 835   my $edit_order_url = build_std_url('script=oe.pl', 'type=' . ($form->{type} eq 'sales_delivery_order' ? 'sales_order' : 'purchase_order'), 'action=edit');
 
 839   foreach my $dord (@{ $form->{DO} }) {
 
 840     $dord->{open}      = $dord->{closed}    ? $locale->text('No')  : $locale->text('Yes');
 
 841     $dord->{delivered} = $dord->{delivered} ? $locale->text('Yes') : $locale->text('No');
 
 843     my $row = { map { $_ => { 'data' => $dord->{$_} } } @columns };
 
 845     my $ord_id = $dord->{id};
 
 847       'raw_data' =>   $cgi->hidden('-name' => "trans_id_${idx}", '-value' => $ord_id)
 
 848                     . $cgi->checkbox('-name' => "multi_id_${idx}",' id' => "multi_id_id_".$ord_id, '-value' => 1, '-label' => ''),
 
 849       'valign'   => 'center',
 
 853     $row->{donumber}->{link}  = $edit_url       . "&id=" . E($dord->{id})      . "&callback=${callback}";
 
 854     $row->{ordnumber}->{link} = $edit_order_url . "&id=" . E($dord->{oe_id})   . "&callback=${callback}" if $dord->{oe_id};
 
 855     $report->add_data($row);
 
 860   setup_do_orders_action_bar();
 
 862   $report->generate_with_headers();
 
 864   $main::lxdebug->leave_sub();
 
 868   $main::lxdebug->enter_sub();
 
 874   my $form     = $main::form;
 
 875   my %myconfig = %main::myconfig;
 
 876   my $locale   = $main::locale;
 
 878   $form->mtime_ischanged('delivery_orders');
 
 880   $form->{defaultcurrency} = $form->get_default_currency(\%myconfig);
 
 882   $form->isblank("transdate", $locale->text('Delivery Order Date missing!'));
 
 884   $form->{donumber} =~ s/^\s*//g;
 
 885   $form->{donumber} =~ s/\s*$//g;
 
 887   my $msg = ucfirst $form->{vc};
 
 888   $form->isblank($form->{vc} . "_id", $locale->text($msg . " missing!"));
 
 890   # $locale->text('Customer missing!');
 
 891   # $locale->text('Vendor missing!');
 
 893   remove_emptied_rows();
 
 896   # if the name changed get new values
 
 897   my $vc = $form->{vc};
 
 898   if (($form->{"previous_${vc}_id"} || $form->{"${vc}_id"}) != $form->{"${vc}_id"}) {
 
 899     $::form->{salesman_id} = SL::DB::Manager::Employee->current->id if exists $::form->{salesman_id};
 
 901     IS->get_customer(\%myconfig, $form) if $vc eq 'customer';
 
 902     IR->get_vendor(\%myconfig, $form)   if $vc eq 'vendor';
 
 905     $::dispatcher->end_request;
 
 908   $form->{id} = 0 if $form->{saveasnew};
 
 912   if(!exists $form->{addition}) {
 
 913     $form->{snumbers} = qq|donumber_| . $form->{donumber};
 
 914     $form->{addition} = "SAVED";
 
 917   # /saving the history
 
 919   $form->{simple_save} = 1;
 
 920   if (!$params{no_redirect} && !$form->{print_and_save}) {
 
 921     delete @{$form}{ary_diff([keys %{ $form }], [qw(login id script type cursor_fokus)])};
 
 923     $::dispatcher->end_request;
 
 925   $main::lxdebug->leave_sub();
 
 929   $main::lxdebug->enter_sub();
 
 933   my $form     = $main::form;
 
 934   my %myconfig = %main::myconfig;
 
 935   my $locale   = $main::locale;
 
 939     if(!exists $form->{addition}) {
 
 940       $form->{snumbers} = qq|donumber_| . $form->{donumber};
 
 941       $form->{addition} = "DELETED";
 
 944     # /saving the history
 
 946     $form->info($locale->text('Delivery Order deleted!'));
 
 947     $::dispatcher->end_request;
 
 950   $form->error($locale->text('Cannot delete delivery order!'));
 
 952   $main::lxdebug->leave_sub();
 
 956   $main::lxdebug->enter_sub();
 
 958   my $form     = $main::form;
 
 959   my %myconfig = %main::myconfig;
 
 960   my $locale   = $main::locale;
 
 963   $form->mtime_ischanged('delivery_orders');
 
 965   $main::auth->assert($form->{type} eq 'purchase_delivery_order' ? 'vendor_invoice_edit' : 'invoice_edit');
 
 967   $form->{convert_from_do_ids} = $form->{id};
 
 968   $form->{deliverydate}        = $form->{transdate};
 
 969   $form->{transdate}           = $form->{invdate} = $form->current_date(\%myconfig);
 
 970   $form->{duedate}             = $form->current_date(\%myconfig, $form->{invdate}, $form->{terms} * 1);
 
 971   $form->{defaultcurrency}     = $form->get_default_currency(\%myconfig);
 
 975   delete @{$form}{qw(id closed delivered)};
 
 977   my ($script, $buysell);
 
 978   if ($form->{type} eq 'purchase_delivery_order') {
 
 979     $form->{title}  = $locale->text('Add Vendor Invoice');
 
 980     $form->{script} = 'ir.pl';
 
 985     $form->{title}  = $locale->text('Add Sales Invoice');
 
 986     $form->{script} = 'is.pl';
 
 991   for my $i (1 .. $form->{rowcount}) {
 
 992     map { $form->{"${_}_${i}"} = $form->parse_amount(\%myconfig, $form->{"${_}_${i}"}) if $form->{"${_}_${i}"} } qw(ship qty sellprice lastcost basefactor discount);
 
 994     # adds a customer/vendor discount, unless we have a workflow case
 
 995     # CAVEAT: has to be done, after the above parse_amount
 
 996     unless ($form->{"ordnumber"}) {
 
 997       if ($form->{discount}) { # Falls wir einen Lieferanten-/Kundenrabatt haben
 
 998         # und rabattfähig sind, dann
 
 999         unless ($form->{"not_discountable_$i"}) {
 
1000           $form->{"discount_$i"} = $form->{discount}*100; # ... nehmen wir diesen Rabatt
 
1004     $form->{"donumber_$i"} = $form->{donumber};
 
1005     $form->{"converted_from_delivery_order_items_id_$i"} = delete $form->{"delivery_order_items_id_$i"};
 
1008   $form->{type} = "invoice";
 
1011   $main::locale = Locale->new("$myconfig{countrycode}", "$script");
 
1012   $locale = $main::locale;
 
1014   require "bin/mozilla/$form->{script}";
 
1016   my $currency = $form->{currency};
 
1019   if ($form->{ordnumber}) {
 
1020     require SL::DB::Order;
 
1021     if (my $order = SL::DB::Manager::Order->find_by(ordnumber => $form->{ordnumber})) {
 
1023       $form->{orddate} = $order->transdate_as_date;
 
1024       $form->{$_}      = $order->$_ for qw(payment_id salesman_id taxzone_id quonumber);
 
1028   $form->{currency}     = $currency;
 
1029   $form->{exchangerate} = "";
 
1030   $form->{forex}        = $form->check_exchangerate(\%myconfig, $form->{currency}, $form->{invdate}, $buysell);
 
1031   $form->{exchangerate} = $form->{forex} if ($form->{forex});
 
1036   for my $i (1 .. $form->{rowcount}) {
 
1037     $form->{"discount_$i"} = $form->format_amount(\%myconfig, $form->{"discount_$i"});
 
1039     my ($dec) = ($form->{"sellprice_$i"} =~ /\.(\d+)/);
 
1041     my $decimalplaces = ($dec > 2) ? $dec : 2;
 
1043     # copy delivery date from reqdate for order -> invoice conversion
 
1044     $form->{"deliverydate_$i"} = $form->{"reqdate_$i"}
 
1045       unless $form->{"deliverydate_$i"};
 
1048     $form->{"sellprice_$i"} =
 
1049       $form->format_amount(\%myconfig, $form->{"sellprice_$i"},
 
1052     $form->{"lastcost_$i"} =
 
1053       $form->format_amount(\%myconfig, $form->{"lastcost_$i"},
 
1056     (my $dec_qty) = ($form->{"qty_$i"} =~ /\.(\d+)/);
 
1057     $dec_qty = length $dec_qty;
 
1059       $form->format_amount(\%myconfig, $form->{"qty_$i"}, $dec_qty);
 
1065   $main::lxdebug->leave_sub();
 
1069   $main::lxdebug->enter_sub();
 
1071   my $form     = $main::form;
 
1072   my %myconfig = %main::myconfig;
 
1073   my $locale   = $main::locale;
 
1076   $main::auth->assert($form->{type} eq 'sales_delivery_order' ? 'invoice_edit' : 'vendor_invoice_edit');
 
1078   my @do_ids = map { $form->{"trans_id_$_"} } grep { $form->{"multi_id_$_"} } (1..$form->{rowcount});
 
1080   if (!scalar @do_ids) {
 
1081     $form->show_generic_error($locale->text('You have not selected any delivery order.'));
 
1084   map { delete $form->{$_} } grep { m/^(?:trans|multi)_id_\d+/ } keys %{ $form };
 
1086   if (!DO->retrieve('vc' => $form->{vc}, 'ids' => \@do_ids)) {
 
1087     $form->show_generic_error($form->{vc} eq 'customer' ?
 
1088                               $locale->text('You cannot create an invoice for delivery orders for different customers.') :
 
1089                               $locale->text('You cannot create an invoice for delivery orders from different vendors.'),
 
1090                               'back_button' => 1);
 
1093   my $source_type              = $form->{type};
 
1094   $form->{convert_from_do_ids} = join ' ', @do_ids;
 
1095   # bei der auswahl von mehreren Lieferscheinen fuer eine Rechnung, die einfach in donumber_array
 
1096   # zwischenspeichern (DO.pm) und als ' '-separierte Liste wieder zurueckschreiben
 
1097   # Hinweis: delete gibt den wert zurueck und loescht danach das element (nett und einfach)
 
1098   # $shell: perldoc perlunc; /delete EXPR
 
1099   $form->{donumber}            = delete $form->{donumber_array};
 
1100   $form->{ordnumber}           = delete $form->{ordnumber_array};
 
1101   $form->{cusordnumber}        = delete $form->{cusordnumber_array};
 
1102   $form->{deliverydate}        = $form->{transdate};
 
1103   $form->{transdate}           = $form->current_date(\%myconfig);
 
1104   $form->{duedate}             = $form->current_date(\%myconfig, $form->{invdate}, $form->{terms} * 1);
 
1105   $form->{type}                = "invoice";
 
1106   $form->{closed}              = 0;
 
1107   $form->{defaultcurrency}     = $form->get_default_currency(\%myconfig);
 
1109   my ($script, $buysell);
 
1110   if ($source_type eq 'purchase_delivery_order') {
 
1111     $form->{title}  = $locale->text('Add Vendor Invoice');
 
1112     $form->{script} = 'ir.pl';
 
1117     $form->{title}  = $locale->text('Add Sales Invoice');
 
1118     $form->{script} = 'is.pl';
 
1123   map { delete $form->{$_} } qw(id subject message cc bcc printed emailed queued);
 
1125   # get vendor or customer discount
 
1127   my $saved_form = save_form();
 
1128   if ($form->{vc} eq 'vendor') {
 
1129     IR->get_vendor(\%myconfig, \%$form);
 
1130     $vc_discount = $form->{vendor_discount};
 
1132     IS->get_customer(\%myconfig, \%$form);
 
1133     $vc_discount = $form->{customer_discount};
 
1135   # use payment terms from customer or vendor
 
1136   restore_form($saved_form,0,qw(payment_id));
 
1138   $form->{rowcount} = 0;
 
1139   foreach my $ref (@{ $form->{form_details} }) {
 
1140     $form->{rowcount}++;
 
1141     $ref->{reqdate} ||= $ref->{dord_transdate}; # copy transdates into each invoice row
 
1142     map { $form->{"${_}_$form->{rowcount}"} = $ref->{$_} } keys %{ $ref };
 
1143     map { $form->{"${_}_$form->{rowcount}"} = $form->format_amount(\%myconfig, $ref->{$_}) } qw(qty sellprice lastcost);
 
1144     $form->{"converted_from_delivery_order_items_id_$form->{rowcount}"} = delete $form->{"delivery_order_items_id_$form->{rowcount}"};
 
1146     if ($vc_discount){ # falls wir einen Lieferanten/Kundenrabatt haben
 
1147       # und keinen anderen discount wert an $i ...
 
1148       $form->{"discount_$form->{rowcount}"} ||= $vc_discount; # ... nehmen wir diesen Rabatt
 
1151     $form->{"discount_$form->{rowcount}"}   = $form->{"discount_$form->{rowcount}"}  * 100; #s.a. Bug 1151
 
1152     # Anm.: Eine Änderung des discounts in der SL/DO.pm->retrieve (select (doi.discount * 100) as discount) ergibt in psql einen
 
1153     # Wert von 10.0000001490116. Ferner ist der Rabatt in der Rechnung dann bei 1.0 (?). Deswegen lasse ich das hier. jb 10.10.09
 
1155     $form->{"discount_$form->{rowcount}"} = $form->format_amount(\%myconfig, $form->{"discount_$form->{rowcount}"});
 
1157   delete $form->{form_details};
 
1159   $locale = Locale->new("$myconfig{countrycode}", "$script");
 
1161   require "bin/mozilla/$form->{script}";
 
1168   $main::lxdebug->leave_sub();
 
1172   $main::lxdebug->enter_sub();
 
1176   my $form     = $main::form;
 
1178   $form->{saveasnew} = 1;
 
1179   $form->{closed}    = 0;
 
1180   $form->{delivered} = 0;
 
1181   map { delete $form->{$_} } qw(printed emailed queued);
 
1182   delete @{ $form }{ grep { m/^stock_(?:in|out)_\d+/ } keys %{ $form } };
 
1183   $form->{"converted_from_delivery_order_items_id_$_"} = delete $form->{"delivery_order_items_id_$_"} for 1 .. $form->{"rowcount"};
 
1184   # Let kivitendo assign a new order number if the user hasn't changed the
 
1185   # previous one. If it has been changed manually then use it as-is.
 
1186   $form->{donumber} =~ s/^\s*//g;
 
1187   $form->{donumber} =~ s/\s*$//g;
 
1188   if ($form->{saved_donumber} && ($form->{saved_donumber} eq $form->{donumber})) {
 
1189     delete($form->{donumber});
 
1194   $main::lxdebug->leave_sub();
 
1197 sub calculate_stock_in_out {
 
1198   $main::lxdebug->enter_sub();
 
1200   my $form     = $main::form;
 
1204   if (!$form->{"id_${i}"}) {
 
1205     $main::lxdebug->leave_sub();
 
1209   my $all_units = AM->retrieve_all_units();
 
1211   my $in_out   = $form->{type} =~ /^sales/ ? 'out' : 'in';
 
1212   my $sinfo    = DO->unpack_stock_information('packed' => $form->{"stock_${in_out}_${i}"});
 
1214   my $do_qty   = AM->sum_with_unit($::form->{"qty_$i"}, $::form->{"unit_$i"});
 
1215   my $sum      = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $sinfo });
 
1216   my $matches  = $do_qty == $sum;
 
1218   my $content  = $form->format_amount_units('amount'      => $sum * 1,
 
1219                                             'part_unit'   => $form->{"partunit_$i"},
 
1220                                             'amount_unit' => $all_units->{$form->{"partunit_$i"}}->{base_unit},
 
1221                                             'conv_units'  => 'convertible_not_smaller',
 
1223   $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="?">|;
 
1225   $main::lxdebug->leave_sub();
 
1230 sub get_basic_bin_wh_info {
 
1231   $main::lxdebug->enter_sub();
 
1233   my $stock_info = shift;
 
1235   my $form     = $main::form;
 
1237   foreach my $sinfo (@{ $stock_info }) {
 
1238     next unless ($sinfo->{bin_id});
 
1240     my $bin_info = WH->get_basic_bin_info('id' => $sinfo->{bin_id});
 
1241     map { $sinfo->{"${_}_description"} = $sinfo->{"${_}description"} = $bin_info->{"${_}_description"} } qw(bin warehouse);
 
1244   $main::lxdebug->leave_sub();
 
1247 sub stock_in_out_form {
 
1248   $main::lxdebug->enter_sub();
 
1250   my $form     = $main::form;
 
1252   if ($form->{in_out} eq 'out') {
 
1258   $main::lxdebug->leave_sub();
 
1261 sub redo_stock_info {
 
1262   $main::lxdebug->enter_sub();
 
1266   my $form     = $main::form;
 
1268   my @non_empty = grep { $_->{qty} } @{ $params{stock_info} };
 
1270   if ($params{add_empty_row}) {
 
1272       'warehouse_id' => scalar(@non_empty) ? $non_empty[-1]->{warehouse_id} : undef,
 
1273       'bin_id'       => scalar(@non_empty) ? $non_empty[-1]->{bin_id}       : undef,
 
1277   @{ $params{stock_info} } = @non_empty;
 
1279   $main::lxdebug->leave_sub();
 
1282 sub update_stock_in {
 
1283   $main::lxdebug->enter_sub();
 
1285   my $form     = $main::form;
 
1286   my %myconfig = %main::myconfig;
 
1288   my $stock_info = [];
 
1290   foreach my $i (1..$form->{rowcount}) {
 
1291     $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
 
1292     push @{ $stock_info }, { map { $_ => $form->{"${_}_${i}"} } qw(warehouse_id bin_id chargenumber
 
1293                                                                    bestbefore qty unit delivery_order_items_stock_id) };
 
1296   display_stock_in_form($stock_info);
 
1298   $main::lxdebug->leave_sub();
 
1302   $main::lxdebug->enter_sub();
 
1304   my $form     = $main::form;
 
1306   my $stock_info = DO->unpack_stock_information('packed' => $form->{stock});
 
1308   display_stock_in_form($stock_info);
 
1310   $main::lxdebug->leave_sub();
 
1313 sub display_stock_in_form {
 
1314   $main::lxdebug->enter_sub();
 
1316   my $stock_info = shift;
 
1318   my $form     = $main::form;
 
1319   my %myconfig = %main::myconfig;
 
1320   my $locale   = $main::locale;
 
1322   $form->{title} = $locale->text('Stock');
 
1324   my $part_info  = IC->get_basic_part_info('id' => $form->{parts_id});
 
1326   # Standardlagerplatz für Standard-Auslagern verwenden, falls keiner für die Ware explizit definiert wurde
 
1327   if ($::instance_conf->get_transfer_default_use_master_default_bin) {
 
1328     $part_info->{warehouse_id} ||= $::instance_conf->get_warehouse_id;
 
1329     $part_info->{bin_id}       ||= $::instance_conf->get_bin_id;
 
1332   my $units      = AM->retrieve_units(\%myconfig, $form);
 
1333   # der zweite Parameter von unit_select_data gibt den default-Namen (selected) vor
 
1334   my $units_data = AM->unit_select_data($units, $form->{do_unit}, undef, $part_info->{unit});
 
1336   $form->get_lists('warehouses' => { 'key'    => 'WAREHOUSES',
 
1337                                      'bins'   => 'BINS' });
 
1339   redo_stock_info('stock_info' => $stock_info, 'add_empty_row' => !$form->{delivered});
 
1341   get_basic_bin_wh_info($stock_info);
 
1343   $form->header(no_layout => 1);
 
1344   print $form->parse_html_template('do/stock_in_form', { 'UNITS'      => $units_data,
 
1345                                                          'STOCK_INFO' => $stock_info,
 
1346                                                          'PART_INFO'  => $part_info, });
 
1348   $main::lxdebug->leave_sub();
 
1351 sub _stock_in_out_set_qty_display {
 
1352   my $stock_info       = shift;
 
1354   my $all_units        = AM->retrieve_all_units();
 
1355   my $sum              = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $stock_info });
 
1356   $form->{qty_display} = $form->format_amount_units(amount      => $sum * 1,
 
1357                                                     part_unit   => $form->{partunit},
 
1358                                                     amount_unit => $all_units->{ $form->{partunit} }->{base_unit},
 
1359                                                     conv_units  => 'convertible_not_smaller',
 
1364   $main::lxdebug->enter_sub();
 
1366   my $form     = $main::form;
 
1367   my %myconfig = %main::myconfig;
 
1369   my $stock_info = [];
 
1371   foreach my $i (1..$form->{rowcount}) {
 
1372     $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
 
1374     next if ($form->{"qty_$i"} <= 0);
 
1376     push @{ $stock_info }, { map { $_ => $form->{"${_}_${i}"} } qw(delivery_order_items_stock_id warehouse_id bin_id chargenumber bestbefore qty unit) };
 
1379   $form->{stock} = YAML::Dump($stock_info);
 
1381   _stock_in_out_set_qty_display($stock_info);
 
1383   my $do_qty       = AM->sum_with_unit($::form->parse_amount(\%::myconfig, $::form->{do_qty}), $::form->{do_unit});
 
1384   my $transfer_qty = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $stock_info });
 
1387   print $form->parse_html_template('do/set_stock_in_out', {
 
1388     qty_matches => $do_qty == $transfer_qty,
 
1391   $main::lxdebug->leave_sub();
 
1394 sub stock_out_form {
 
1395   $main::lxdebug->enter_sub();
 
1397   my $form     = $main::form;
 
1398   my %myconfig = %main::myconfig;
 
1399   my $locale   = $main::locale;
 
1401   $form->{title} = $locale->text('Release From Stock');
 
1403   my $part_info  = IC->get_basic_part_info('id' => $form->{parts_id});
 
1405   my $units      = AM->retrieve_units(\%myconfig, $form);
 
1406   my $units_data = AM->unit_select_data($units, undef, undef, $part_info->{unit});
 
1408   my @contents   = DO->get_item_availability('parts_id' => $form->{parts_id});
 
1410   my $stock_info = DO->unpack_stock_information('packed' => $form->{stock});
 
1412   if (!$form->{delivered}) {
 
1413     foreach my $row (@contents) {
 
1414       $row->{available_qty} = $form->format_amount_units('amount'      => $row->{qty} * 1,
 
1415                                                          'part_unit'   => $part_info->{unit},
 
1416                                                          'conv_units'  => 'convertible_not_smaller',
 
1419       foreach my $sinfo (@{ $stock_info }) {
 
1420         next if (($row->{bin_id}       != $sinfo->{bin_id}) ||
 
1421                  ($row->{warehouse_id} != $sinfo->{warehouse_id}) ||
 
1422                  ($row->{chargenumber} ne $sinfo->{chargenumber}) ||
 
1423                  ($row->{bestbefore}   ne $sinfo->{bestbefore}));
 
1425         map { $row->{"stock_$_"} = $sinfo->{$_} } qw(qty unit error delivery_order_items_stock_id);
 
1430     get_basic_bin_wh_info($stock_info);
 
1432     foreach my $sinfo (@{ $stock_info }) {
 
1433       map { $sinfo->{"stock_$_"} = $sinfo->{$_} } qw(qty unit);
 
1437   $form->header(no_layout => 1);
 
1438   print $form->parse_html_template('do/stock_out_form', { 'UNITS'      => $units_data,
 
1439                                                           'WHCONTENTS' => $form->{delivered} ? $stock_info : \@contents,
 
1440                                                           'PART_INFO'  => $part_info, });
 
1442   $main::lxdebug->leave_sub();
 
1446   $main::lxdebug->enter_sub();
 
1448   my $form     = $main::form;
 
1449   my %myconfig = %main::myconfig;
 
1450   my $locale   = $main::locale;
 
1452   my $stock_info = [];
 
1454   foreach my $i (1 .. $form->{rowcount}) {
 
1455     $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
 
1457     next if ($form->{"qty_$i"} <= 0);
 
1459     push @{ $stock_info }, {
 
1460       'warehouse_id' => $form->{"warehouse_id_$i"},
 
1461       'bin_id'       => $form->{"bin_id_$i"},
 
1462       'chargenumber' => $form->{"chargenumber_$i"},
 
1463       'bestbefore'   => $form->{"bestbefore_$i"},
 
1464       'qty'          => $form->{"qty_$i"},
 
1465       'unit'         => $form->{"unit_$i"},
 
1467       'delivery_order_items_stock_id'  => $form->{"delivery_order_items_stock_id_$i"},
 
1471   my @errors     = DO->check_stock_availability('requests' => $stock_info,
 
1472                                                 'parts_id' => $form->{parts_id});
 
1474   $form->{stock} = YAML::Dump($stock_info);
 
1477     $form->{ERRORS} = [];
 
1478     map { push @{ $form->{ERRORS} }, $locale->text('Error in row #1: The quantity you entered is bigger than the stocked quantity.', $_->{row}); } @errors;
 
1479     stock_in_out_form();
 
1482     _stock_in_out_set_qty_display($stock_info);
 
1484     my $do_qty       = AM->sum_with_unit($::form->parse_amount(\%::myconfig, $::form->{do_qty}), $::form->{do_unit});
 
1485     my $transfer_qty = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $stock_info });
 
1488     print $form->parse_html_template('do/set_stock_in_out', {
 
1489       qty_matches => $do_qty == $transfer_qty,
 
1493   $main::lxdebug->leave_sub();
 
1497   $main::lxdebug->enter_sub();
 
1499   my $form     = $main::form;
 
1500   my %myconfig = %main::myconfig;
 
1501   my $locale   = $main::locale;
 
1503   if ($form->{id} && DO->is_marked_as_delivered(id => $form->{id})) {
 
1504     $form->show_generic_error($locale->text('The parts for this delivery order have already been transferred in.'));
 
1507   save(no_redirect => 1);
 
1509   my @part_ids = map { $form->{"id_${_}"} } grep { $form->{"id_${_}"} && $form->{"stock_in_${_}"} } (1 .. $form->{rowcount});
 
1513     my $units         = AM->retrieve_units(\%myconfig, $form);
 
1514     my %part_info_map = IC->get_basic_part_info('id' => \@part_ids);
 
1517     $form->{ERRORS}   = [];
 
1519     foreach my $i (1 .. $form->{rowcount}) {
 
1520       next unless ($form->{"id_$i"} && $form->{"stock_in_$i"});
 
1522       my $row_sum_base_qty = 0;
 
1523       my $base_unit_factor = $units->{ $part_info_map{$form->{"id_$i"}}->{unit} }->{factor} || 1;
 
1525       foreach my $request (@{ DO->unpack_stock_information('packed' => $form->{"stock_in_$i"}) }) {
 
1526         $request->{parts_id}  = $form->{"id_$i"};
 
1527         $row_sum_base_qty    += $request->{qty} * $units->{$request->{unit}}->{factor} / $base_unit_factor;
 
1529         $request->{project_id} = $form->{"project_id_$i"} || $form->{globalproject_id};
 
1531         push @all_requests, $request;
 
1534       next if (0 == $row_sum_base_qty);
 
1536       my $do_base_qty = $form->parse_amount(\%myconfig, $form->{"qty_$i"}) * $units->{$form->{"unit_$i"}}->{factor} / $base_unit_factor;
 
1538 #      if ($do_base_qty != $row_sum_base_qty) {
 
1539 #        push @{ $form->{ERRORS} }, $locale->text('Error in position #1: You must either assign no stock at all or the full quantity of #2 #3.',
 
1540 #                                                 $i, $form->{"qty_$i"}, $form->{"unit_$i"});
 
1544     if (@{ $form->{ERRORS} }) {
 
1545       push @{ $form->{ERRORS} }, $locale->text('The delivery order has not been marked as delivered. The warehouse contents have not changed.');
 
1547       set_headings('edit');
 
1549       $main::lxdebug->leave_sub();
 
1551       $::dispatcher->end_request;
 
1555   DO->transfer_in_out('direction' => 'in',
 
1556                       'requests'  => \@all_requests);
 
1558   SL::DB::DeliveryOrder->new(id => $form->{id})->load->update_attributes(delivered => 1);
 
1560   $form->{callback} = 'do.pl?action=edit&type=purchase_delivery_order&id=' . $form->escape($form->{id});
 
1563   $main::lxdebug->leave_sub();
 
1567   $main::lxdebug->enter_sub();
 
1569   my $form     = $main::form;
 
1570   my %myconfig = %main::myconfig;
 
1571   my $locale   = $main::locale;
 
1573   if ($form->{id} && DO->is_marked_as_delivered(id => $form->{id})) {
 
1574     $form->show_generic_error($locale->text('The parts for this delivery order have already been transferred out.'));
 
1577   save(no_redirect => 1);
 
1579   my @part_ids = map { $form->{"id_${_}"} } grep { $form->{"id_${_}"} && $form->{"stock_out_${_}"} } (1 .. $form->{rowcount});
 
1583     my $units         = AM->retrieve_units(\%myconfig, $form);
 
1584     my %part_info_map = IC->get_basic_part_info('id' => \@part_ids);
 
1587     $form->{ERRORS}   = [];
 
1589     foreach my $i (1 .. $form->{rowcount}) {
 
1590       next unless ($form->{"id_$i"} && $form->{"stock_out_$i"});
 
1592       my $row_sum_base_qty = 0;
 
1593       my $base_unit_factor = $units->{ $part_info_map{$form->{"id_$i"}}->{unit} }->{factor} || 1;
 
1595       foreach my $request (@{ DO->unpack_stock_information('packed' => $form->{"stock_out_$i"}) }) {
 
1596         $request->{parts_id} = $form->{"id_$i"};
 
1597         $request->{base_qty} = $request->{qty} * $units->{$request->{unit}}->{factor} / $base_unit_factor;
 
1598         $request->{project_id} = $form->{"project_id_$i"} ? $form->{"project_id_$i"} : $form->{globalproject_id};
 
1600         my $map_key          = join '--', ($form->{"id_$i"}, @{$request}{qw(warehouse_id bin_id chargenumber bestbefore)});
 
1602         $request_map{$map_key}                 ||= $request;
 
1603         $request_map{$map_key}->{sum_base_qty} ||= 0;
 
1604         $request_map{$map_key}->{sum_base_qty}  += $request->{base_qty};
 
1605         $row_sum_base_qty                       += $request->{base_qty};
 
1607         push @all_requests, $request;
 
1610       next if (0 == $row_sum_base_qty);
 
1612       my $do_base_qty = $form->{"qty_$i"} * $units->{$form->{"unit_$i"}}->{factor} / $base_unit_factor;
 
1614 #      if ($do_base_qty != $row_sum_base_qty) {
 
1615 #        push @{ $form->{ERRORS} }, $locale->text('Error in position #1: You must either assign no transfer at all or the full quantity of #2 #3.',
 
1616 #                                                 $i, $form->{"qty_$i"}, $form->{"unit_$i"});
 
1621       my @bin_ids      = map { $_->{bin_id} } values %request_map;
 
1622       my %bin_info_map = WH->get_basic_bin_info('id' => \@bin_ids);
 
1623       my @contents     = DO->get_item_availability('parts_id' => \@part_ids);
 
1625       foreach my $inv (@contents) {
 
1626         my $map_key = join '--', @{$inv}{qw(parts_id warehouse_id bin_id chargenumber bestbefore)};
 
1628         next unless ($request_map{$map_key});
 
1630         my $request    = $request_map{$map_key};
 
1631         $request->{ok} = $request->{sum_base_qty} <= $inv->{qty};
 
1634       foreach my $request (values %request_map) {
 
1635         next if ($request->{ok});
 
1637         my $pinfo = $part_info_map{$request->{parts_id}};
 
1638         my $binfo = $bin_info_map{$request->{bin_id}};
 
1640         if ($::instance_conf->get_show_bestbefore) {
 
1641             push @{ $form->{ERRORS} }, $locale->text("There is not enough available of '#1' at warehouse '#2', bin '#3', #4, #5, for the transfer of #6.",
 
1642                                                      $pinfo->{description},
 
1643                                                      $binfo->{warehouse_description},
 
1644                                                      $binfo->{bin_description},
 
1645                                                      $request->{chargenumber} ? $locale->text('chargenumber #1', $request->{chargenumber}) : $locale->text('no chargenumber'),
 
1646                                                      $request->{bestbefore} ? $locale->text('bestbefore #1', $request->{bestbefore}) : $locale->text('no bestbefore'),
 
1647                                                      $form->format_amount_units('amount'      => $request->{sum_base_qty},
 
1648                                                                                 'part_unit'   => $pinfo->{unit},
 
1649                                                                                 'conv_units'  => 'convertible_not_smaller'));
 
1651             push @{ $form->{ERRORS} }, $locale->text("There is not enough available of '#1' at warehouse '#2', bin '#3', #4, for the transfer of #5.",
 
1652                                                      $pinfo->{description},
 
1653                                                      $binfo->{warehouse_description},
 
1654                                                      $binfo->{bin_description},
 
1655                                                      $request->{chargenumber} ? $locale->text('chargenumber #1', $request->{chargenumber}) : $locale->text('no chargenumber'),
 
1656                                                      $form->format_amount_units('amount'      => $request->{sum_base_qty},
 
1657                                                                                 'part_unit'   => $pinfo->{unit},
 
1658                                                                                 'conv_units'  => 'convertible_not_smaller'));
 
1663     if (@{ $form->{ERRORS} }) {
 
1664       push @{ $form->{ERRORS} }, $locale->text('The delivery order has not been marked as delivered. The warehouse contents have not changed.');
 
1666       set_headings('edit');
 
1668       $main::lxdebug->leave_sub();
 
1670       $::dispatcher->end_request;
 
1673   DO->transfer_in_out('direction' => 'out',
 
1674                       'requests'  => \@all_requests);
 
1676   SL::DB::DeliveryOrder->new(id => $form->{id})->load->update_attributes(delivered => 1);
 
1678   $form->{callback} = 'do.pl?action=edit&type=sales_delivery_order&id=' . $form->escape($form->{id});
 
1681   $main::lxdebug->leave_sub();
 
1685   $main::lxdebug->enter_sub();
 
1687   my $form     = $main::form;
 
1689   DO->close_orders('ids' => [ $form->{id} ]);
 
1691   $form->{closed} = 1;
 
1695   $main::lxdebug->leave_sub();
 
1699   $::lxdebug->enter_sub;
 
1701   $::auth->assert('purchase_delivery_order_edit | sales_delivery_order_edit');
 
1704   retrieve_partunits();
 
1706   my $new_rowcount = $::form->{"rowcount"} * 1 + 1;
 
1707   $::form->{"project_id_${new_rowcount}"} = $::form->{"globalproject_id"};
 
1709   $::form->language_payment(\%::myconfig);
 
1711   Common::webdav_folder($::form);
 
1714   display_row(++$::form->{rowcount});
 
1717   $::lxdebug->leave_sub;
 
1721   call_sub($main::form->{yes_nextsub});
 
1725   call_sub($main::form->{no_nextsub});
 
1729   call_sub($main::form->{update_nextsub} || $main::form->{nextsub} || 'update_delivery_order');
 
1733   my $form     = $main::form;
 
1734   my $locale   = $main::locale;
 
1736   foreach my $action (qw(update print save transfer_out transfer_out_default sort
 
1737                          transfer_in transfer_in_default mark_closed save_as_new invoice delete)) {
 
1738     if ($form->{"action_${action}"}) {
 
1744   $form->error($locale->text('No action defined.'));
 
1747 sub transfer_out_default {
 
1748   $main::lxdebug->enter_sub();
 
1750   my $form     = $main::form;
 
1752   transfer_in_out_default('direction' => 'out');
 
1754   $main::lxdebug->leave_sub();
 
1757 sub transfer_in_default {
 
1758   $main::lxdebug->enter_sub();
 
1760   my $form     = $main::form;
 
1762   transfer_in_out_default('direction' => 'in');
 
1764   $main::lxdebug->leave_sub();
 
1767 # Falls das Standardlagerverfahren aktiv ist, wird
 
1768 # geprüft, ob alle Standardlagerplätze für die Auslager-
 
1769 # artikel vorhanden sind UND ob die Warenmenge ausreicht zum
 
1770 # Auslagern. Falls nicht wird entsprechend eine Fehlermeldung
 
1771 # generiert. Offen Chargennummer / bestbefore wird nicht berücksichtigt
 
1772 sub transfer_in_out_default {
 
1773   $main::lxdebug->enter_sub();
 
1775   my $form     = $main::form;
 
1776   my %myconfig = %main::myconfig;
 
1777   my $locale   = $main::locale;
 
1780   my (%missing_default_bins, %qty_parts, @all_requests, %part_info_map, $default_warehouse_id, $default_bin_id);
 
1782   Common::check_params(\%params, qw(direction));
 
1784   # entsprechende defaults holen, falls standardlagerplatz verwendet werden soll
 
1785   if ($::instance_conf->get_transfer_default_use_master_default_bin) {
 
1786     $default_warehouse_id = $::instance_conf->get_warehouse_id;
 
1787     $default_bin_id       = $::instance_conf->get_bin_id;
 
1791   my @part_ids = map { $form->{"id_${_}"} } (1 .. $form->{rowcount});
 
1793     my $units         = AM->retrieve_units(\%myconfig, $form);
 
1794     %part_info_map = IC->get_basic_part_info('id' => \@part_ids);
 
1795     foreach my $i (1 .. $form->{rowcount}) {
 
1796       next unless ($form->{"id_$i"});
 
1797       my $base_unit_factor = $units->{ $part_info_map{$form->{"id_$i"}}->{unit} }->{factor} || 1;
 
1798       my $qty =   $form->parse_amount(\%myconfig, $form->{"qty_$i"}) * $units->{$form->{"unit_$i"}}->{factor} / $base_unit_factor;
 
1800       $form->show_generic_error($locale->text("Cannot transfer negative entries." )) if ($qty < 0);
 
1801       # if we do not want to transfer services and this part is a service, set qty to zero
 
1802       # ... and do not create a hash entry in %qty_parts below (will skip check for bins for the transfer == out case)
 
1803       # ... 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)
 
1805       $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});
 
1806       $qty_parts{$form->{"id_$i"}} += $qty;
 
1808         delete $qty_parts{$form->{"id_$i"}} unless $qty_parts{$form->{"id_$i"}};
 
1809         undef $form->{"stock_in_$i"};
 
1812       $part_info_map{$form->{"id_$i"}}{bin_id}       ||= $default_bin_id;
 
1813       $part_info_map{$form->{"id_$i"}}{warehouse_id} ||= $default_warehouse_id;
 
1815       push @all_requests, ($qty == 0) ? { } : {
 
1816                         'chargenumber' => '',  #?? die müsste entsprechend geholt werden
 
1817                         #'bestbefore' => undef, # TODO wird nicht berücksichtigt
 
1818                         'bin_id' => $part_info_map{$form->{"id_$i"}}{bin_id},
 
1820                         'parts_id' => $form->{"id_$i"},
 
1821                         'comment' => $locale->text("Default transfer delivery order"),
 
1822                         'unit' => $part_info_map{$form->{"id_$i"}}{unit},
 
1823                         'warehouse_id' => $part_info_map{$form->{"id_$i"}}{warehouse_id},
 
1824                         'oe_id' => $form->{id},
 
1825                         'project_id' => $form->{"project_id_$i"} ? $form->{"project_id_$i"} : $form->{globalproject_id}
 
1829     # jetzt wird erst überprüft, ob die Stückzahl entsprechend stimmt.
 
1830     # check if bin (transfer in and transfer out and qty (transfer out) is correct
 
1831     foreach my $key (keys %qty_parts) {
 
1833       $missing_default_bins{$key}{missing_bin} = 1 unless ($part_info_map{$key}{bin_id});
 
1834       next unless ($part_info_map{$key}{bin_id}); # abbruch
 
1836       if ($params{direction} eq 'out') {  # wird nur für ausgehende Mengen benötigt
 
1837         my ($max_qty, $error) = WH->get_max_qty_parts_bin(parts_id => $key, bin_id => $part_info_map{$key}{bin_id});
 
1839           # wir können nicht entscheiden, welche charge oder mhd (bestbefore) ausgewählt sein soll
 
1840           # deshalb rückmeldung nach oben geben, manuell auszulagern
 
1841           # TODO Bei nur einem Treffer mit Charge oder bestbefore wäre das noch möglich
 
1842           $missing_default_bins{$key}{chargenumber} = 1;
 
1844         if ($max_qty < $qty_parts{$key}){
 
1845           $missing_default_bins{$key}{missing_qty} = $max_qty - $qty_parts{$key};
 
1851   # Abfrage für Fehlerbehandlung (nur bei direction == out)
 
1852   if (scalar (keys %missing_default_bins)) {
 
1854     foreach my $fehler (keys %missing_default_bins) {
 
1856       my $ware = WH->get_part_description(parts_id => $fehler);
 
1857       if ($missing_default_bins{$fehler}{missing_bin}){
 
1858         $fehlertext .= "Kein Standardlagerplatz definiert bei $ware <br>";
 
1860       if ($missing_default_bins{$fehler}{missing_qty}) {  # missing_qty
 
1861         $fehlertext .= "Es fehlen " . $missing_default_bins{$fehler}{missing_qty}*-1 .
 
1862                        " von $ware auf dem Standard-Lagerplatz " . $part_info_map{$fehler}{bin} .   " zum Auslagern<br>";
 
1864       if ($missing_default_bins{$fehler}{chargenumber}){
 
1865         $fehlertext .= "Die Ware hat eine Chargennummer oder eine Mindesthaltbarkeit definiert.
 
1866                         Hier kann man nicht automatisch entscheiden.
 
1867                         Bitte diesen Lieferschein manuell auslagern.
 
1870       # auslagern soll immer gehen, auch wenn nicht genügend auf lager ist.
 
1871       # der lagerplatz ist hier extra konfigurierbar, bspw. Lager-Korrektur mit
 
1872       # Lagerplatz Lagerplatz-Korrektur
 
1873       my $default_warehouse_id_ignore_onhand = $::instance_conf->get_warehouse_id_ignore_onhand;
 
1874       my $default_bin_id_ignore_onhand       = $::instance_conf->get_bin_id_ignore_onhand;
 
1875       if ($::instance_conf->get_transfer_default_ignore_onhand && $default_bin_id_ignore_onhand) {
 
1876         # entsprechende defaults holen
 
1877         # falls chargenumber, bestbefore oder anzahl nicht stimmt, auf automatischen
 
1878         # lagerplatz wegbuchen!
 
1879         foreach (@all_requests) {
 
1880           if ($_->{parts_id} eq $fehler){
 
1881           $_->{bin_id}        = $default_bin_id_ignore_onhand;
 
1882           $_->{warehouse_id}  = $default_warehouse_id_ignore_onhand;
 
1886         #$main::lxdebug->message(0, 'Fehlertext: ' . $fehlertext);
 
1887         $form->show_generic_error($locale->text("Cannot transfer. <br> Reason:<br>#1", $fehlertext ));
 
1893   # hier der eigentliche fallunterschied für in oder out
 
1894   my $prefix   = $params{direction} eq 'in' ? 'in' : 'out';
 
1896   # dieser array_ref ist für DO->save da:
 
1897   # einmal die all_requests in YAML verwandeln, damit delivery_order_items_stock
 
1898   # gefüllt werden kann.
 
1899   # could be dumped to the form in the first loop,
 
1900   # but maybe bin_id and warehouse_id has changed to the "korrekturlager" with
 
1901   # allowed negative qty ($::instance_conf->get_warehouse_id_ignore_onhand) ...
 
1903   foreach (@all_requests){
 
1905     next unless scalar(%{ $_ });
 
1906     $form->{"stock_${prefix}_$i"} = YAML::Dump([$_]);
 
1909   save(no_redirect => 1); # Wir können auslagern, deshalb beleg speichern
 
1910                           # und in delivery_order_items_stock speichern
 
1912   # ... and fill back the persistent dois_id for inventory fk
 
1913   undef (@all_requests);
 
1914   foreach my $i (1 .. $form->{rowcount}) {
 
1915     next unless ($form->{"id_$i"} && $form->{"stock_${prefix}_$i"});
 
1916     push @all_requests, @{ DO->unpack_stock_information('packed' => $form->{"stock_${prefix}_$i"}) };
 
1918   DO->transfer_in_out('direction' => $prefix,
 
1919                       'requests'  => \@all_requests);
 
1921   SL::DB::DeliveryOrder->new(id => $form->{id})->load->update_attributes(delivered => 1);
 
1923   $form->{callback} = 'do.pl?action=edit&type=sales_delivery_order&id=' . $form->escape($form->{id}) if $params{direction} eq 'out';
 
1924   $form->{callback} = 'do.pl?action=edit&type=purchase_delivery_order&id=' . $form->escape($form->{id}) if $params{direction} eq 'in';
 
1930   $main::lxdebug->enter_sub();
 
1934   my $form     = $main::form;
 
1937   save(no_redirect => 1); # has to be done, at least for newly added positions
 
1939   # hashify partnumbers, positions. key is delivery_order_items_id
 
1940   for my $i (1 .. ($form->{rowcount}) ) {
 
1941     $temp_hash{$form->{"delivery_order_items_id_$i"}} = { runningnumber => $form->{"runningnumber_$i"}, partnumber => $form->{"partnumber_$i"} };
 
1942     if ($form->{id} && $form->{"discount_$i"}) {
 
1943       # prepare_order assumes a db value if there is a form->id and multiplies *100
 
1944       # We hope for new controller code (no more format_amount/parse_amount distinction)
 
1945       $form->{"discount_$i"} /=100;
 
1948   # naturally sort partnumbers and get a sorted array of doi_ids
 
1949   my @sorted_doi_ids =  sort { Sort::Naturally::ncmp($temp_hash{$a}->{"partnumber"}, $temp_hash{$b}->{"partnumber"}) }  keys %temp_hash;
 
1954   for (@sorted_doi_ids) {
 
1955     $form->{"runningnumber_$temp_hash{$_}->{runningnumber}"} = $new_number;
 
1958   # all parse_amounts changes are in form (i.e. , to .) therefore we need
 
1959   # another format_amount to change it back, for the next save ;-(
 
1960   # works great except for row discounts (see above comment)
 
1964     $main::lxdebug->leave_sub();
 
1976 do.pl - Script for all calls to delivery order
 
1984 Sorts all position with Natural Sort. Can be activated in form_footer.html like this
 
1985 C<E<lt>input class="submit" type="submit" name="action_sort" id="sort_button" value="[% 'Sort and Save' | $T8 %]"E<gt>>
 
1991 Sort and Save can be implemented as an optional button if configuration ca be set by client config.
 
1992 Example coding for database scripts and templates in (git show af2f24b8), check also
 
1993 autogeneration for rose (scripts/rose_auto_create_model.pl --h)