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           accesskey => 'enter',
 
 255           submit   => [ '#form', { action => "save" } ],
 
 256           checks   => [ @req_trans_desc ],
 
 257           disabled => $::form->{delivered} ? t8('This record has already been delivered.') : undef,
 
 261           submit   => [ '#form', { action => "save_as_new" } ],
 
 262           checks   => [ @req_trans_desc ],
 
 263           disabled => !$::form->{id},
 
 266           t8('Mark as closed'),
 
 267           submit   => [ '#form', { action => "mark_closed" } ],
 
 268           checks   => [ @req_trans_desc ],
 
 269           confirm  => t8('This will remove the delivery order from showing as open even if contents are not delivered. Proceed?'),
 
 270           disabled => !$::form->{id}    ? t8('This record has not been saved yet.')
 
 271                     : $::form->{closed} ? t8('This record has already been closed.')
 
 274       ], # end of combobox "Save"
 
 278         submit   => [ '#form', { action => "delete" } ],
 
 279         confirm  => t8('Do you really want to delete this object?'),
 
 280         disabled => !$::form->{id}                                                                              ? t8('This record has not been saved yet.')
 
 281                   : $::form->{delivered}                                                                        ? t8('This record has already been delivered.')
 
 282                   : ($::form->{vc} eq 'customer' && !$::instance_conf->get_sales_delivery_order_show_delete)    ? t8('Deleting this type of record has been disabled in the configuration.')
 
 283                   : ($::form->{vc} eq 'vendor'   && !$::instance_conf->get_purchase_delivery_order_show_delete) ? t8('Deleting this type of record has been disabled in the configuration.')
 
 290           submit   => [ '#form', { action => "transfer_out" } ],
 
 291           checks   => [ @req_trans_desc, @transfer_qty ],
 
 292           disabled => $::form->{delivered} ? t8('This record has already been delivered.') : undef,
 
 293           only_if  => $is_customer,
 
 296           t8('Transfer out via default'),
 
 297           submit   => [ '#form', { action => "transfer_out_default" } ],
 
 298           checks   => [ @req_trans_desc, @transfer_qty ],
 
 299           disabled => $::form->{delivered} ? t8('This record has already been delivered.') : undef,
 
 300           only_if  => $is_customer && $::instance_conf->get_transfer_default,
 
 304           submit   => [ '#form', { action => "transfer_in"> 1 } ],
 
 305           checks   => [ @req_trans_desc, @transfer_qty ],
 
 306           disabled => $::form->{delivered} ? t8('This record has already been delivered.') : undef,
 
 307           only_if  => !$is_customer,
 
 310           t8('Transfer in via default'),
 
 311           submit   => [ '#form', { action => "transfer_in_default" } ],
 
 312           checks   => [ @req_trans_desc, @transfer_qty ],
 
 313           disabled => $::form->{delivered} ? t8('This record has already been delivered.') : undef,
 
 314           only_if  => !$is_customer && $::instance_conf->get_transfer_default,
 
 316       ], # end of combobox "Transfer out"
 
 323         submit => [ '#form', { action => "invoice" } ],
 
 324         disabled => !$::form->{id} ? t8('This record has not been saved yet.') : undef,
 
 328         action => [ t8('Export') ],
 
 331           call   => [ 'kivi.SalesPurchase.show_print_dialog' ],
 
 332           checks => [ @req_trans_desc ],
 
 336           call   => [ 'kivi.SalesPurchase.show_email_dialog' ],
 
 337           checks => [ @req_trans_desc ],
 
 339       ], # end of combobox "Export"
 
 342         action => [ t8('more') ],
 
 345           call     => [ 'set_history_window', $::form->{id} * 1, 'id' ],
 
 346           disabled => !$::form->{id} ? t8('This record has not been saved yet.') : undef,
 
 350           call     => [ 'follow_up_window' ],
 
 351           disabled => !$::form->{id} ? t8('This record has not been saved yet.') : undef,
 
 353       ], # end if combobox "more"
 
 358 sub setup_do_search_action_bar {
 
 361   for my $bar ($::request->layout->get('actionbar')) {
 
 365         submit    => [ '#form' ],
 
 366         accesskey => 'enter',
 
 372 sub setup_do_orders_action_bar {
 
 375   for my $bar ($::request->layout->get('actionbar')) {
 
 379         submit    => [ '#form', { action => 'invoice_multi' } ],
 
 380         checks    => [ [ 'kivi.check_if_entries_selected', '#form tbody input[type=checkbox]' ] ],
 
 381         accesskey => 'enter',
 
 385         call   => [ 'kivi.SalesPurchase.show_print_dialog', 'js:kivi.MassDeliveryOrderPrint.submitMultiOrders' ],
 
 386         checks => [ [ 'kivi.check_if_entries_selected', '#form tbody input[type=checkbox]' ] ],
 
 393   $main::lxdebug->enter_sub();
 
 397   my $form     = $main::form;
 
 398   my %myconfig = %main::myconfig;
 
 400   my $class       = "SL::DB::" . ($form->{vc} eq 'customer' ? 'Customer' : 'Vendor');
 
 401   $form->{VC_OBJ} = $class->load_cached($form->{ $form->{vc} . '_id' });
 
 403   $form->{employee_id} = $form->{old_employee_id} if $form->{old_employee_id};
 
 404   $form->{salesman_id} = $form->{old_salesman_id} if $form->{old_salesman_id};
 
 406   my $vc = $form->{vc} eq "customer" ? "customers" : "vendors";
 
 407   $form->get_lists("price_factors"  => "ALL_PRICE_FACTORS",
 
 408                    "business_types" => "ALL_BUSINESS_TYPES",
 
 410   $form->{ALL_DEPARTMENTS} = SL::DB::Manager::Department->get_all;
 
 413   my @old_project_ids = uniq grep { $_ } map { $_ * 1 } ($form->{"globalproject_id"}, map { $form->{"project_id_$_"} } 1..$form->{"rowcount"});
 
 414   my @old_ids_cond    = @old_project_ids ? (id => \@old_project_ids) : ();
 
 416   if (($vc eq 'customers') && $::instance_conf->get_customer_projects_only_in_sales) {
 
 419         customer_id          => $::form->{customer_id},
 
 420         billable_customer_id => $::form->{customer_id},
 
 425       and => [ active => 1, @customer_cond ],
 
 429   $::form->{ALL_PROJECTS}          = SL::DB::Manager::Project->get_all_sorted(query => \@conditions);
 
 430   $::form->{ALL_EMPLOYEES}         = SL::DB::Manager::Employee->get_all_sorted(query => [ or => [ id => $::form->{employee_id},  deleted => 0 ] ]);
 
 431   $::form->{ALL_SALESMEN}          = SL::DB::Manager::Employee->get_all_sorted(query => [ or => [ id => $::form->{salesman_id},  deleted => 0 ] ]);
 
 432   $::form->{ALL_SHIPTO}            = SL::DB::Manager::Shipto->get_all_sorted(query => [
 
 433     or => [ trans_id  => $::form->{"$::form->{vc}_id"} * 1, and => [ shipto_id => $::form->{shipto_id} * 1, trans_id => undef ] ]
 
 435   $::form->{ALL_CONTACTS}          = SL::DB::Manager::Contact->get_all_sorted(query => [
 
 437       cp_cv_id => $::form->{"$::form->{vc}_id"} * 1,
 
 440         cp_id    => $::form->{cp_id} * 1
 
 445   my $dispatch_to_popup = '';
 
 446   if ($form->{resubmit} && ($form->{format} eq "html")) {
 
 447     $dispatch_to_popup  = "window.open('about:blank','Beleg'); document.do.target = 'Beleg';";
 
 448     $dispatch_to_popup .= "document.do.submit();";
 
 449   } elsif ($form->{resubmit}) {
 
 450     # emulate click for resubmitting actions
 
 451     $dispatch_to_popup  = "document.do.${_}.click(); " for grep { /^action_/ } keys %$form;
 
 453   $::request->{layout}->add_javascripts_inline("\$(function(){$dispatch_to_popup});");
 
 456   $form->{follow_up_trans_info} = $form->{donumber} .'('. $form->{VC_OBJ}->name .')';
 
 458   $::request->{layout}->use_javascript(map { "${_}.js" } qw(kivi.File kivi.MassDeliveryOrderPrint kivi.SalesPurchase kivi.Part ckeditor/ckeditor ckeditor/adapters/jquery kivi.io autocomplete_customer));
 
 461   push @custom_hidden, map { "shiptocvar_" . $_->name } @{ SL::DB::Manager::CustomVariableConfig->get_all(where => [ module => 'ShipTo' ]) };
 
 463   $::form->{HIDDENS} = [ map { +{ name => $_, value => $::form->{$_} } } (@custom_hidden) ];
 
 465   setup_do_action_bar();
 
 468   # Fix für Bug 1082 Erwartet wird: 'abteilungsNAME--abteilungsID'
 
 469   # und Erweiterung für Bug 1760:
 
 470   # Das war leider nur ein Teil-Fix, da das Verhalten den 'Erneuern'-Knopf
 
 471   # nicht überlebt. Konsequent jetzt auf L umgestellt
 
 472   #   $ perldoc SL::Template::Plugin::L
 
 473   # Daher entsprechend nur die Anpassung in form_header
 
 474   # und in DO.pm gemacht. 4 Testfälle:
 
 475   # department_id speichern                 | i.O.
 
 476   # department_id lesen                     | i.O.
 
 477   # department leer überlebt erneuern       | i.O.
 
 478   # department nicht leer überlebt erneuern | i.O.
 
 479   # $main::lxdebug->message(0, 'ABTEILUNGS ID in form?' . $form->{department_id});
 
 480   print $form->parse_html_template('do/form_header');
 
 482   $main::lxdebug->leave_sub();
 
 486   $main::lxdebug->enter_sub();
 
 490   my $form     = $main::form;
 
 492   $form->{PRINT_OPTIONS}      = setup_sales_purchase_print_options();
 
 493   $form->{ALL_DELIVERY_TERMS} = SL::DB::Manager::DeliveryTerm->get_all_sorted();
 
 495   print $form->parse_html_template('do/form_footer',
 
 496     {transfer_default         => ($::instance_conf->get_transfer_default)});
 
 498   $main::lxdebug->leave_sub();
 
 501 sub update_delivery_order {
 
 502   $main::lxdebug->enter_sub();
 
 506   my $form     = $main::form;
 
 507   my %myconfig = %main::myconfig;
 
 509   set_headings($form->{"id"} ? "edit" : "add");
 
 511   $form->{insertdate} = SL::DB::DeliveryOrder->new(id => $form->{id})->load->itime_as_date if $form->{id};
 
 516   $payment_id = $form->{payment_id} if $form->{payment_id};
 
 518   my $vc = $form->{vc};
 
 519   if (($form->{"previous_${vc}_id"} || $form->{"${vc}_id"}) != $form->{"${vc}_id"}) {
 
 520     $::form->{salesman_id} = SL::DB::Manager::Employee->current->id if exists $::form->{salesman_id};
 
 522     IS->get_customer(\%myconfig, $form) if $vc eq 'customer';
 
 523     IR->get_vendor(\%myconfig, $form)   if $vc eq 'vendor';
 
 526   $form->{discount} =  $form->{"$form->{vc}_discount"} if defined $form->{"$form->{vc}_discount"};
 
 527   # Problem: Wenn man ohne Erneuern einen Kunden/Lieferanten
 
 528   # wechselt, wird der entsprechende Kunden/ Lieferantenrabatt
 
 529   # nicht übernommen. Grundproblem: In Commit 82574e78
 
 530   # hab ich aus discount customer_discount und vendor_discount
 
 531   # gemacht und entsprechend an den Oberflächen richtig hin-
 
 532   # geschoben. Die damals bessere Lösung wäre gewesen:
 
 533   # In den Templates nur die hidden für form-discount wieder ein-
 
 534   # setzen dann wäre die Verrenkung jetzt nicht notwendig.
 
 535   # TODO: Ggf. Bugfix 1284, 1575 und 817 wieder zusammenführen
 
 536   # Testfälle: Kunden mit Rabatt 0 -> Rabatt 20 i.O.
 
 537   #            Kunde mit Rabatt 20 -> Rabatt 0  i.O.
 
 538   #            Kunde mit Rabatt 20 -> Rabatt 5,5 i.O.
 
 539   $form->{payment_id} = $payment_id if $form->{payment_id} eq "";
 
 541   my $i = $form->{rowcount};
 
 543   if (   ($form->{"partnumber_$i"} eq "")
 
 544       && ($form->{"description_$i"} eq "")
 
 545       && ($form->{"partsgroup_$i"}  eq "")) {
 
 552     if ($form->{type} eq 'purchase_delivery_order') {
 
 553       IR->retrieve_item(\%myconfig, $form);
 
 556       IS->retrieve_item(\%myconfig, $form);
 
 560     my $rows = scalar @{ $form->{item_list} };
 
 563       $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
 
 564       if( !$form->{"qty_$i"} ) {
 
 565         $form->{"qty_$i"} = 1;
 
 570         select_item(mode => $mode, pre_entered_qty => $form->{"qty_$i"});
 
 571         $::dispatcher->end_request;
 
 575         my $sellprice = $form->parse_amount(\%myconfig, $form->{"sellprice_$i"});
 
 577         map { $form->{"${_}_$i"} = $form->{item_list}[0]{$_} } keys %{ $form->{item_list}[0] };
 
 579         $form->{"marge_price_factor_$i"} = $form->{item_list}->[0]->{price_factor};
 
 582           $form->{"sellprice_$i"} = $sellprice;
 
 584           my $record        = _make_record();
 
 585           my $price_source  = SL::PriceSource->new(record_item => $record->items->[$i-1], record => $record);
 
 586           my $best_price    = $price_source->best_price;
 
 587           my $best_discount = $price_source->best_discount;
 
 590             $::form->{"sellprice_$i"}           = $best_price->price;
 
 591             $::form->{"active_price_source_$i"} = $best_price->source;
 
 593           if ($best_discount) {
 
 594             $::form->{"discount_$i"}               = $best_discount->discount;
 
 595             $::form->{"active_discount_source_$i"} = $best_discount->source;
 
 599         $form->{"sellprice_$i"}          = $form->format_amount(\%myconfig, $form->{"sellprice_$i"});
 
 600         $form->{"lastcost_$i"}           = $form->format_amount(\%myconfig, $form->{"lastcost_$i"});
 
 601         $form->{"qty_$i"}                = $form->format_amount(\%myconfig, $form->{"qty_$i"});
 
 602         $form->{"discount_$i"}           = $form->format_amount(\%myconfig, $form->{"discount_$i"} * 100.0);
 
 609       # ok, so this is a new part
 
 610       # ask if it is a part or service item
 
 612       if (   $form->{"partsgroup_$i"}
 
 613           && ($form->{"partsnumber_$i"} eq "")
 
 614           && ($form->{"description_$i"} eq "")) {
 
 616         $form->{"discount_$i"} = "";
 
 617         $form->{"not_discountable_$i"} = "";
 
 621         $form->{"id_$i"}   = 0;
 
 627   $main::lxdebug->leave_sub();
 
 631   $main::lxdebug->enter_sub();
 
 635   my $form     = $main::form;
 
 636   my %myconfig = %main::myconfig;
 
 637   my $locale   = $main::locale;
 
 639   $form->{vc} = $form->{type} eq 'purchase_delivery_order' ? 'vendor' : 'customer';
 
 641   $form->get_lists("projects"       => { "key" => "ALL_PROJECTS",
 
 643                    "business_types" => "ALL_BUSINESS_TYPES");
 
 644   $form->{ALL_EMPLOYEES} = SL::DB::Manager::Employee->get_all_sorted(query => [ deleted => 0 ]);
 
 645   $form->{ALL_DEPARTMENTS} = SL::DB::Manager::Department->get_all;
 
 646   $form->{title}             = $locale->text('Delivery Orders');
 
 648   setup_do_search_action_bar();
 
 652   print $form->parse_html_template('do/search');
 
 654   $main::lxdebug->leave_sub();
 
 658   $main::lxdebug->enter_sub();
 
 662   my $form     = $main::form;
 
 663   my %myconfig = %main::myconfig;
 
 664   my $locale   = $main::locale;
 
 665   my $cgi      = $::request->{cgi};
 
 667   $::request->{layout}->use_javascript(map { "${_}.js" } qw(kivi.MassDeliveryOrderPrint kivi.SalesPurchase));
 
 668   ($form->{ $form->{vc} }, $form->{"$form->{vc}_id"}) = split(/--/, $form->{ $form->{vc} });
 
 670   report_generator_set_default_sort('transdate', 1);
 
 674   $form->{rowcount} = scalar @{ $form->{DO} };
 
 677     ids                     transdate               reqdate
 
 679     ordnumber               customernumber          cusordnumber
 
 680     name                    employee  salesman
 
 681     shipvia                 globalprojectnumber
 
 682     transaction_description department
 
 687   $form->{l_open}      = $form->{l_closed} = "Y" if ($form->{open}      && $form->{closed});
 
 688   $form->{l_delivered} = "Y"                     if ($form->{delivered} && $form->{notdelivered});
 
 690   $form->{title}       = $locale->text('Delivery Orders');
 
 692   my $attachment_basename = $form->{vc} eq 'vendor' ? $locale->text('purchase_delivery_order_list') : $locale->text('sales_delivery_order_list');
 
 694   my $report = SL::ReportGenerator->new(\%myconfig, $form);
 
 696   my @hidden_variables = map { "l_${_}" } @columns;
 
 697   push @hidden_variables, $form->{vc}, qw(l_closed l_notdelivered open closed delivered notdelivered donumber ordnumber serialnumber cusordnumber
 
 698                                           transaction_description transdatefrom transdateto reqdatefrom reqdateto
 
 699                                           type vc employee_id salesman_id project_id parts_partnumber parts_description
 
 700                                           insertdatefrom insertdateto business_id);
 
 702   my $href = build_std_url('action=orders', grep { $form->{$_} } @hidden_variables);
 
 705     'ids'                     => { 'text' => '<input type="checkbox" id="multi_all" value="1">', 'align' => 'center' },
 
 706     'transdate'               => { 'text' => $locale->text('Delivery Order Date'), },
 
 707     'reqdate'                 => { 'text' => $locale->text('Reqdate'), },
 
 708     'id'                      => { 'text' => $locale->text('ID'), },
 
 709     'donumber'                => { 'text' => $locale->text('Delivery Order'), },
 
 710     'ordnumber'               => { 'text' => $locale->text('Order'), },
 
 711     'customernumber'          => { 'text' => $locale->text('Customer Number'), },
 
 712     'cusordnumber'            => { 'text' => $locale->text('Customer Order Number'), },
 
 713     'name'                    => { 'text' => $form->{vc} eq 'customer' ? $locale->text('Customer') : $locale->text('Vendor'), },
 
 714     'employee'                => { 'text' => $locale->text('Employee'), },
 
 715     'salesman'                => { 'text' => $locale->text('Salesman'), },
 
 716     'shipvia'                 => { 'text' => $locale->text('Ship via'), },
 
 717     'globalprojectnumber'     => { 'text' => $locale->text('Project Number'), },
 
 718     'transaction_description' => { 'text' => $locale->text('Transaction description'), },
 
 719     'open'                    => { 'text' => $locale->text('Open'), },
 
 720     'delivered'               => { 'text' => $locale->text('Delivered'), },
 
 721     'department'              => { 'text' => $locale->text('Department'), },
 
 722     'insertdate'              => { 'text' => $locale->text('Insert Date'), },
 
 725   foreach my $name (qw(id transdate reqdate donumber ordnumber name employee salesman shipvia transaction_description department insertdate)) {
 
 726     my $sortdir                 = $form->{sort} eq $name ? 1 - $form->{sortdir} : $form->{sortdir};
 
 727     $column_defs{$name}->{link} = $href . "&sort=$name&sortdir=$sortdir";
 
 730   $form->{"l_type"} = "Y";
 
 731   map { $column_defs{$_}->{visible} = $form->{"l_${_}"} ? 1 : 0 } @columns;
 
 733   $column_defs{ids}->{visible} = 'HTML';
 
 735   $report->set_columns(%column_defs);
 
 736   $report->set_column_order(@columns);
 
 738   $report->set_export_options('orders', @hidden_variables, qw(sort sortdir));
 
 740   $report->set_sort_indicator($form->{sort}, $form->{sortdir});
 
 743   if ($form->{customer}) {
 
 744     push @options, $locale->text('Customer') . " : $form->{customer}";
 
 746   if ($form->{vendor}) {
 
 747     push @options, $locale->text('Vendor') . " : $form->{vendor}";
 
 749   if ($form->{cp_name}) {
 
 750     push @options, $locale->text('Contact Person') . " : $form->{cp_name}";
 
 752   if ($form->{department_id}) {
 
 753     push @options, $locale->text('Department') . " : " . SL::DB::Department->new(id => $form->{department_id})->load->description;
 
 755   if ($form->{donumber}) {
 
 756     push @options, $locale->text('Delivery Order Number') . " : $form->{donumber}";
 
 758   if ($form->{ordnumber}) {
 
 759     push @options, $locale->text('Order Number') . " : $form->{ordnumber}";
 
 761   push @options, $locale->text('Serial Number') . " : $form->{serialnumber}" if $form->{serialnumber};
 
 762   if ($form->{business_id}) {
 
 763     my $vc_type_label = $form->{vc} eq 'customer' ? $locale->text('Customer type') : $locale->text('Vendor type');
 
 764     push @options, $vc_type_label . " : " . SL::DB::Business->new(id => $form->{business_id})->load->description;
 
 766   if ($form->{transaction_description}) {
 
 767     push @options, $locale->text('Transaction description') . " : $form->{transaction_description}";
 
 769   if ($form->{parts_description}) {
 
 770     push @options, $locale->text('Part Description') . " : $form->{parts_description}";
 
 772   if ($form->{parts_partnumber}) {
 
 773     push @options, $locale->text('Part Number') . " : $form->{parts_partnumber}";
 
 775   if ( $form->{transdatefrom} or $form->{transdateto} ) {
 
 776     push @options, $locale->text('Delivery Order Date');
 
 777     push @options, $locale->text('From') . " " . $locale->date(\%myconfig, $form->{transdatefrom}, 1)     if $form->{transdatefrom};
 
 778     push @options, $locale->text('Bis')  . " " . $locale->date(\%myconfig, $form->{transdateto},   1)     if $form->{transdateto};
 
 780   if ( $form->{reqdatefrom} or $form->{reqdateto} ) {
 
 781     push @options, $locale->text('Reqdate');
 
 782     push @options, $locale->text('From') . " " . $locale->date(\%myconfig, $form->{reqdatefrom}, 1)       if $form->{reqdatefrom};
 
 783     push @options, $locale->text('Bis')  . " " . $locale->date(\%myconfig, $form->{reqdateto},   1)       if $form->{reqdateto};
 
 785   if ( $form->{insertdatefrom} or $form->{insertdateto} ) {
 
 786     push @options, $locale->text('Insert Date');
 
 787     push @options, $locale->text('From') . " " . $locale->date(\%myconfig, $form->{insertdatefrom}, 1)    if $form->{insertdatefrom};
 
 788     push @options, $locale->text('Bis')  . " " . $locale->date(\%myconfig, $form->{insertdateto},   1)    if $form->{insertdateto};
 
 791     push @options, $locale->text('Open');
 
 793   if ($form->{closed}) {
 
 794     push @options, $locale->text('Closed');
 
 796   if ($form->{delivered}) {
 
 797     push @options, $locale->text('Delivered');
 
 799   if ($form->{notdelivered}) {
 
 800     push @options, $locale->text('Not delivered');
 
 803   my $pr = SL::DB::Manager::Printer->find_by(
 
 804       printer_description => $::locale->text("sales_delivery_order_printer"));
 
 806       $form->{printer_id} = $pr->id;
 
 809   my $print_options = SL::Helper::PrintOptions->get_print_options(
 
 811       hide_language_id => 1,
 
 817   $report->set_options('top_info_text'        => join("\n", @options),
 
 818                        'raw_top_info_text'    => $form->parse_html_template('do/orders_top'),
 
 819                        'raw_bottom_info_text' => $form->parse_html_template('do/orders_bottom', { print_options => $print_options }),
 
 820                        'output_format'        => 'HTML',
 
 821                        'title'                => $form->{title},
 
 822                        'attachment_basename'  => $attachment_basename . strftime('_%Y%m%d', localtime time),
 
 824   $report->set_options_from_form();
 
 825   $locale->set_numberformat_wo_thousands_separator(\%myconfig) if lc($report->{options}->{output_format}) eq 'csv';
 
 827   # add sort and escape callback, this one we use for the add sub
 
 828   $form->{callback} = $href .= "&sort=$form->{sort}";
 
 830   # escape callback for href
 
 831   my $callback = $form->escape($href);
 
 833   my $edit_url       = build_std_url('action=edit', 'type', 'vc');
 
 834   my $edit_order_url = build_std_url('script=oe.pl', 'type=' . ($form->{type} eq 'sales_delivery_order' ? 'sales_order' : 'purchase_order'), 'action=edit');
 
 838   foreach my $dord (@{ $form->{DO} }) {
 
 839     $dord->{open}      = $dord->{closed}    ? $locale->text('No')  : $locale->text('Yes');
 
 840     $dord->{delivered} = $dord->{delivered} ? $locale->text('Yes') : $locale->text('No');
 
 842     my $row = { map { $_ => { 'data' => $dord->{$_} } } @columns };
 
 844     my $ord_id = $dord->{id};
 
 846       'raw_data' =>   $cgi->hidden('-name' => "trans_id_${idx}", '-value' => $ord_id)
 
 847                     . $cgi->checkbox('-name' => "multi_id_${idx}",' id' => "multi_id_id_".$ord_id, '-value' => 1, '-label' => ''),
 
 848       'valign'   => 'center',
 
 852     $row->{donumber}->{link}  = $edit_url       . "&id=" . E($dord->{id})      . "&callback=${callback}";
 
 853     $row->{ordnumber}->{link} = $edit_order_url . "&id=" . E($dord->{oe_id})   . "&callback=${callback}" if $dord->{oe_id};
 
 854     $report->add_data($row);
 
 859   setup_do_orders_action_bar();
 
 861   $report->generate_with_headers();
 
 863   $main::lxdebug->leave_sub();
 
 867   $main::lxdebug->enter_sub();
 
 873   my $form     = $main::form;
 
 874   my %myconfig = %main::myconfig;
 
 875   my $locale   = $main::locale;
 
 877   $form->mtime_ischanged('delivery_orders');
 
 879   $form->{defaultcurrency} = $form->get_default_currency(\%myconfig);
 
 881   $form->isblank("transdate", $locale->text('Delivery Order Date missing!'));
 
 883   $form->{donumber} =~ s/^\s*//g;
 
 884   $form->{donumber} =~ s/\s*$//g;
 
 886   my $msg = ucfirst $form->{vc};
 
 887   $form->isblank($form->{vc} . "_id", $locale->text($msg . " missing!"));
 
 889   # $locale->text('Customer missing!');
 
 890   # $locale->text('Vendor missing!');
 
 892   remove_emptied_rows();
 
 895   # if the name changed get new values
 
 896   my $vc = $form->{vc};
 
 897   if (($form->{"previous_${vc}_id"} || $form->{"${vc}_id"}) != $form->{"${vc}_id"}) {
 
 898     $::form->{salesman_id} = SL::DB::Manager::Employee->current->id if exists $::form->{salesman_id};
 
 900     IS->get_customer(\%myconfig, $form) if $vc eq 'customer';
 
 901     IR->get_vendor(\%myconfig, $form)   if $vc eq 'vendor';
 
 904     $::dispatcher->end_request;
 
 907   $form->{id} = 0 if $form->{saveasnew};
 
 911   if(!exists $form->{addition}) {
 
 912     $form->{snumbers} = qq|donumber_| . $form->{donumber};
 
 913     $form->{addition} = "SAVED";
 
 916   # /saving the history
 
 918   $form->{simple_save} = 1;
 
 919   if (!$params{no_redirect} && !$form->{print_and_save}) {
 
 920     delete @{$form}{ary_diff([keys %{ $form }], [qw(login id script type cursor_fokus)])};
 
 922     $::dispatcher->end_request;
 
 924   $main::lxdebug->leave_sub();
 
 928   $main::lxdebug->enter_sub();
 
 932   my $form     = $main::form;
 
 933   my %myconfig = %main::myconfig;
 
 934   my $locale   = $main::locale;
 
 938     if(!exists $form->{addition}) {
 
 939       $form->{snumbers} = qq|donumber_| . $form->{donumber};
 
 940       $form->{addition} = "DELETED";
 
 943     # /saving the history
 
 945     $form->info($locale->text('Delivery Order deleted!'));
 
 946     $::dispatcher->end_request;
 
 949   $form->error($locale->text('Cannot delete delivery order!'));
 
 951   $main::lxdebug->leave_sub();
 
 955   $main::lxdebug->enter_sub();
 
 957   my $form     = $main::form;
 
 958   my %myconfig = %main::myconfig;
 
 959   my $locale   = $main::locale;
 
 962   $form->mtime_ischanged('delivery_orders');
 
 964   $main::auth->assert($form->{type} eq 'purchase_delivery_order' ? 'vendor_invoice_edit' : 'invoice_edit');
 
 966   $form->{convert_from_do_ids} = $form->{id};
 
 967   $form->{deliverydate}        = $form->{transdate};
 
 968   $form->{transdate}           = $form->{invdate} = $form->current_date(\%myconfig);
 
 969   $form->{duedate}             = $form->current_date(\%myconfig, $form->{invdate}, $form->{terms} * 1);
 
 970   $form->{defaultcurrency}     = $form->get_default_currency(\%myconfig);
 
 974   delete @{$form}{qw(id closed delivered)};
 
 976   my ($script, $buysell);
 
 977   if ($form->{type} eq 'purchase_delivery_order') {
 
 978     $form->{title}  = $locale->text('Add Vendor Invoice');
 
 979     $form->{script} = 'ir.pl';
 
 984     $form->{title}  = $locale->text('Add Sales Invoice');
 
 985     $form->{script} = 'is.pl';
 
 990   for my $i (1 .. $form->{rowcount}) {
 
 991     map { $form->{"${_}_${i}"} = $form->parse_amount(\%myconfig, $form->{"${_}_${i}"}) if $form->{"${_}_${i}"} } qw(ship qty sellprice lastcost basefactor discount);
 
 993     # adds a customer/vendor discount, unless we have a workflow case
 
 994     # CAVEAT: has to be done, after the above parse_amount
 
 995     unless ($form->{"ordnumber"}) {
 
 996       if ($form->{discount}) { # Falls wir einen Lieferanten-/Kundenrabatt haben
 
 997         # und rabattfähig sind, dann
 
 998         unless ($form->{"not_discountable_$i"}) {
 
 999           $form->{"discount_$i"} = $form->{discount}*100; # ... nehmen wir diesen Rabatt
 
1003     $form->{"donumber_$i"} = $form->{donumber};
 
1004     $form->{"converted_from_delivery_order_items_id_$i"} = delete $form->{"delivery_order_items_id_$i"};
 
1007   $form->{type} = "invoice";
 
1010   $main::locale = Locale->new("$myconfig{countrycode}", "$script");
 
1011   $locale = $main::locale;
 
1013   require "bin/mozilla/$form->{script}";
 
1015   my $currency = $form->{currency};
 
1018   if ($form->{ordnumber}) {
 
1019     require SL::DB::Order;
 
1020     if (my $order = SL::DB::Manager::Order->find_by(ordnumber => $form->{ordnumber})) {
 
1022       $form->{orddate} = $order->transdate_as_date;
 
1023       $form->{$_}      = $order->$_ for qw(payment_id salesman_id taxzone_id quonumber);
 
1027   $form->{currency}     = $currency;
 
1028   $form->{exchangerate} = "";
 
1029   $form->{forex}        = $form->check_exchangerate(\%myconfig, $form->{currency}, $form->{invdate}, $buysell);
 
1030   $form->{exchangerate} = $form->{forex} if ($form->{forex});
 
1035   for my $i (1 .. $form->{rowcount}) {
 
1036     $form->{"discount_$i"} = $form->format_amount(\%myconfig, $form->{"discount_$i"});
 
1038     my ($dec) = ($form->{"sellprice_$i"} =~ /\.(\d+)/);
 
1040     my $decimalplaces = ($dec > 2) ? $dec : 2;
 
1042     # copy delivery date from reqdate for order -> invoice conversion
 
1043     $form->{"deliverydate_$i"} = $form->{"reqdate_$i"}
 
1044       unless $form->{"deliverydate_$i"};
 
1047     $form->{"sellprice_$i"} =
 
1048       $form->format_amount(\%myconfig, $form->{"sellprice_$i"},
 
1051     $form->{"lastcost_$i"} =
 
1052       $form->format_amount(\%myconfig, $form->{"lastcost_$i"},
 
1055     (my $dec_qty) = ($form->{"qty_$i"} =~ /\.(\d+)/);
 
1056     $dec_qty = length $dec_qty;
 
1058       $form->format_amount(\%myconfig, $form->{"qty_$i"}, $dec_qty);
 
1064   $main::lxdebug->leave_sub();
 
1068   $main::lxdebug->enter_sub();
 
1070   my $form     = $main::form;
 
1071   my %myconfig = %main::myconfig;
 
1072   my $locale   = $main::locale;
 
1075   $main::auth->assert($form->{type} eq 'sales_delivery_order' ? 'invoice_edit' : 'vendor_invoice_edit');
 
1077   my @do_ids = map { $form->{"trans_id_$_"} } grep { $form->{"multi_id_$_"} } (1..$form->{rowcount});
 
1079   if (!scalar @do_ids) {
 
1080     $form->show_generic_error($locale->text('You have not selected any delivery order.'));
 
1083   map { delete $form->{$_} } grep { m/^(?:trans|multi)_id_\d+/ } keys %{ $form };
 
1085   if (!DO->retrieve('vc' => $form->{vc}, 'ids' => \@do_ids)) {
 
1086     $form->show_generic_error($form->{vc} eq 'customer' ?
 
1087                               $locale->text('You cannot create an invoice for delivery orders for different customers.') :
 
1088                               $locale->text('You cannot create an invoice for delivery orders from different vendors.'),
 
1089                               'back_button' => 1);
 
1092   my $source_type              = $form->{type};
 
1093   $form->{convert_from_do_ids} = join ' ', @do_ids;
 
1094   # bei der auswahl von mehreren Lieferscheinen fuer eine Rechnung, die einfach in donumber_array
 
1095   # zwischenspeichern (DO.pm) und als ' '-separierte Liste wieder zurueckschreiben
 
1096   # Hinweis: delete gibt den wert zurueck und loescht danach das element (nett und einfach)
 
1097   # $shell: perldoc perlunc; /delete EXPR
 
1098   $form->{donumber}            = delete $form->{donumber_array};
 
1099   $form->{ordnumber}           = delete $form->{ordnumber_array};
 
1100   $form->{cusordnumber}        = delete $form->{cusordnumber_array};
 
1101   $form->{deliverydate}        = $form->{transdate};
 
1102   $form->{transdate}           = $form->current_date(\%myconfig);
 
1103   $form->{duedate}             = $form->current_date(\%myconfig, $form->{invdate}, $form->{terms} * 1);
 
1104   $form->{type}                = "invoice";
 
1105   $form->{closed}              = 0;
 
1106   $form->{defaultcurrency}     = $form->get_default_currency(\%myconfig);
 
1108   my ($script, $buysell);
 
1109   if ($source_type eq 'purchase_delivery_order') {
 
1110     $form->{title}  = $locale->text('Add Vendor Invoice');
 
1111     $form->{script} = 'ir.pl';
 
1116     $form->{title}  = $locale->text('Add Sales Invoice');
 
1117     $form->{script} = 'is.pl';
 
1122   map { delete $form->{$_} } qw(id subject message cc bcc printed emailed queued);
 
1124   # get vendor or customer discount
 
1126   my $saved_form = save_form();
 
1127   if ($form->{vc} eq 'vendor') {
 
1128     IR->get_vendor(\%myconfig, \%$form);
 
1129     $vc_discount = $form->{vendor_discount};
 
1131     IS->get_customer(\%myconfig, \%$form);
 
1132     $vc_discount = $form->{customer_discount};
 
1134   # use payment terms from customer or vendor
 
1135   restore_form($saved_form,0,qw(payment_id));
 
1137   $form->{rowcount} = 0;
 
1138   foreach my $ref (@{ $form->{form_details} }) {
 
1139     $form->{rowcount}++;
 
1140     $ref->{reqdate} ||= $ref->{dord_transdate}; # copy transdates into each invoice row
 
1141     map { $form->{"${_}_$form->{rowcount}"} = $ref->{$_} } keys %{ $ref };
 
1142     map { $form->{"${_}_$form->{rowcount}"} = $form->format_amount(\%myconfig, $ref->{$_}) } qw(qty sellprice lastcost);
 
1143     $form->{"converted_from_delivery_order_items_id_$form->{rowcount}"} = delete $form->{"delivery_order_items_id_$form->{rowcount}"};
 
1145     if ($vc_discount){ # falls wir einen Lieferanten/Kundenrabatt haben
 
1146       # und keinen anderen discount wert an $i ...
 
1147       $form->{"discount_$form->{rowcount}"} ||= $vc_discount; # ... nehmen wir diesen Rabatt
 
1150     $form->{"discount_$form->{rowcount}"}   = $form->{"discount_$form->{rowcount}"}  * 100; #s.a. Bug 1151
 
1151     # Anm.: Eine Änderung des discounts in der SL/DO.pm->retrieve (select (doi.discount * 100) as discount) ergibt in psql einen
 
1152     # Wert von 10.0000001490116. Ferner ist der Rabatt in der Rechnung dann bei 1.0 (?). Deswegen lasse ich das hier. jb 10.10.09
 
1154     $form->{"discount_$form->{rowcount}"} = $form->format_amount(\%myconfig, $form->{"discount_$form->{rowcount}"});
 
1156   delete $form->{form_details};
 
1158   $locale = Locale->new("$myconfig{countrycode}", "$script");
 
1160   require "bin/mozilla/$form->{script}";
 
1167   $main::lxdebug->leave_sub();
 
1171   $main::lxdebug->enter_sub();
 
1175   my $form     = $main::form;
 
1177   $form->{saveasnew} = 1;
 
1178   $form->{closed}    = 0;
 
1179   $form->{delivered} = 0;
 
1180   map { delete $form->{$_} } qw(printed emailed queued);
 
1181   delete @{ $form }{ grep { m/^stock_(?:in|out)_\d+/ } keys %{ $form } };
 
1182   $form->{"converted_from_delivery_order_items_id_$_"} = delete $form->{"delivery_order_items_id_$_"} for 1 .. $form->{"rowcount"};
 
1183   # Let kivitendo assign a new order number if the user hasn't changed the
 
1184   # previous one. If it has been changed manually then use it as-is.
 
1185   $form->{donumber} =~ s/^\s*//g;
 
1186   $form->{donumber} =~ s/\s*$//g;
 
1187   if ($form->{saved_donumber} && ($form->{saved_donumber} eq $form->{donumber})) {
 
1188     delete($form->{donumber});
 
1193   $main::lxdebug->leave_sub();
 
1196 sub calculate_stock_in_out {
 
1197   $main::lxdebug->enter_sub();
 
1199   my $form     = $main::form;
 
1203   if (!$form->{"id_${i}"}) {
 
1204     $main::lxdebug->leave_sub();
 
1208   my $all_units = AM->retrieve_all_units();
 
1210   my $in_out   = $form->{type} =~ /^sales/ ? 'out' : 'in';
 
1211   my $sinfo    = DO->unpack_stock_information('packed' => $form->{"stock_${in_out}_${i}"});
 
1213   my $do_qty   = AM->sum_with_unit($::form->{"qty_$i"}, $::form->{"unit_$i"});
 
1214   my $sum      = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $sinfo });
 
1215   my $matches  = $do_qty == $sum;
 
1217   my $content  = $form->format_amount_units('amount'      => $sum * 1,
 
1218                                             'part_unit'   => $form->{"partunit_$i"},
 
1219                                             'amount_unit' => $all_units->{$form->{"partunit_$i"}}->{base_unit},
 
1220                                             'conv_units'  => 'convertible_not_smaller',
 
1222   $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="?">|;
 
1224   $main::lxdebug->leave_sub();
 
1229 sub get_basic_bin_wh_info {
 
1230   $main::lxdebug->enter_sub();
 
1232   my $stock_info = shift;
 
1234   my $form     = $main::form;
 
1236   foreach my $sinfo (@{ $stock_info }) {
 
1237     next unless ($sinfo->{bin_id});
 
1239     my $bin_info = WH->get_basic_bin_info('id' => $sinfo->{bin_id});
 
1240     map { $sinfo->{"${_}_description"} = $sinfo->{"${_}description"} = $bin_info->{"${_}_description"} } qw(bin warehouse);
 
1243   $main::lxdebug->leave_sub();
 
1246 sub stock_in_out_form {
 
1247   $main::lxdebug->enter_sub();
 
1249   my $form     = $main::form;
 
1251   if ($form->{in_out} eq 'out') {
 
1257   $main::lxdebug->leave_sub();
 
1260 sub redo_stock_info {
 
1261   $main::lxdebug->enter_sub();
 
1265   my $form     = $main::form;
 
1267   my @non_empty = grep { $_->{qty} } @{ $params{stock_info} };
 
1269   if ($params{add_empty_row}) {
 
1271       'warehouse_id' => scalar(@non_empty) ? $non_empty[-1]->{warehouse_id} : undef,
 
1272       'bin_id'       => scalar(@non_empty) ? $non_empty[-1]->{bin_id}       : undef,
 
1276   @{ $params{stock_info} } = @non_empty;
 
1278   $main::lxdebug->leave_sub();
 
1281 sub update_stock_in {
 
1282   $main::lxdebug->enter_sub();
 
1284   my $form     = $main::form;
 
1285   my %myconfig = %main::myconfig;
 
1287   my $stock_info = [];
 
1289   foreach my $i (1..$form->{rowcount}) {
 
1290     $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
 
1291     push @{ $stock_info }, { map { $_ => $form->{"${_}_${i}"} } qw(warehouse_id bin_id chargenumber
 
1292                                                                    bestbefore qty unit delivery_order_items_stock_id) };
 
1295   display_stock_in_form($stock_info);
 
1297   $main::lxdebug->leave_sub();
 
1301   $main::lxdebug->enter_sub();
 
1303   my $form     = $main::form;
 
1305   my $stock_info = DO->unpack_stock_information('packed' => $form->{stock});
 
1307   display_stock_in_form($stock_info);
 
1309   $main::lxdebug->leave_sub();
 
1312 sub display_stock_in_form {
 
1313   $main::lxdebug->enter_sub();
 
1315   my $stock_info = shift;
 
1317   my $form     = $main::form;
 
1318   my %myconfig = %main::myconfig;
 
1319   my $locale   = $main::locale;
 
1321   $form->{title} = $locale->text('Stock');
 
1323   my $part_info  = IC->get_basic_part_info('id' => $form->{parts_id});
 
1325   # Standardlagerplatz für Standard-Auslagern verwenden, falls keiner für die Ware explizit definiert wurde
 
1326   if ($::instance_conf->get_transfer_default_use_master_default_bin) {
 
1327     $part_info->{warehouse_id} ||= $::instance_conf->get_warehouse_id;
 
1328     $part_info->{bin_id}       ||= $::instance_conf->get_bin_id;
 
1331   my $units      = AM->retrieve_units(\%myconfig, $form);
 
1332   # der zweite Parameter von unit_select_data gibt den default-Namen (selected) vor
 
1333   my $units_data = AM->unit_select_data($units, $form->{do_unit}, undef, $part_info->{unit});
 
1335   $form->get_lists('warehouses' => { 'key'    => 'WAREHOUSES',
 
1336                                      'bins'   => 'BINS' });
 
1338   redo_stock_info('stock_info' => $stock_info, 'add_empty_row' => !$form->{delivered});
 
1340   get_basic_bin_wh_info($stock_info);
 
1342   $form->header(no_layout => 1);
 
1343   print $form->parse_html_template('do/stock_in_form', { 'UNITS'      => $units_data,
 
1344                                                          'STOCK_INFO' => $stock_info,
 
1345                                                          'PART_INFO'  => $part_info, });
 
1347   $main::lxdebug->leave_sub();
 
1350 sub _stock_in_out_set_qty_display {
 
1351   my $stock_info       = shift;
 
1353   my $all_units        = AM->retrieve_all_units();
 
1354   my $sum              = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $stock_info });
 
1355   $form->{qty_display} = $form->format_amount_units(amount      => $sum * 1,
 
1356                                                     part_unit   => $form->{partunit},
 
1357                                                     amount_unit => $all_units->{ $form->{partunit} }->{base_unit},
 
1358                                                     conv_units  => 'convertible_not_smaller',
 
1363   $main::lxdebug->enter_sub();
 
1365   my $form     = $main::form;
 
1366   my %myconfig = %main::myconfig;
 
1368   my $stock_info = [];
 
1370   foreach my $i (1..$form->{rowcount}) {
 
1371     $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
 
1373     next if ($form->{"qty_$i"} <= 0);
 
1375     push @{ $stock_info }, { map { $_ => $form->{"${_}_${i}"} } qw(delivery_order_items_stock_id warehouse_id bin_id chargenumber bestbefore qty unit) };
 
1378   $form->{stock} = YAML::Dump($stock_info);
 
1380   _stock_in_out_set_qty_display($stock_info);
 
1382   my $do_qty       = AM->sum_with_unit($::form->parse_amount(\%::myconfig, $::form->{do_qty}), $::form->{do_unit});
 
1383   my $transfer_qty = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $stock_info });
 
1386   print $form->parse_html_template('do/set_stock_in_out', {
 
1387     qty_matches => $do_qty == $transfer_qty,
 
1390   $main::lxdebug->leave_sub();
 
1393 sub stock_out_form {
 
1394   $main::lxdebug->enter_sub();
 
1396   my $form     = $main::form;
 
1397   my %myconfig = %main::myconfig;
 
1398   my $locale   = $main::locale;
 
1400   $form->{title} = $locale->text('Release From Stock');
 
1402   my $part_info  = IC->get_basic_part_info('id' => $form->{parts_id});
 
1404   my $units      = AM->retrieve_units(\%myconfig, $form);
 
1405   my $units_data = AM->unit_select_data($units, undef, undef, $part_info->{unit});
 
1407   my @contents   = DO->get_item_availability('parts_id' => $form->{parts_id});
 
1409   my $stock_info = DO->unpack_stock_information('packed' => $form->{stock});
 
1411   if (!$form->{delivered}) {
 
1412     foreach my $row (@contents) {
 
1413       $row->{available_qty} = $form->format_amount_units('amount'      => $row->{qty} * 1,
 
1414                                                          'part_unit'   => $part_info->{unit},
 
1415                                                          'conv_units'  => 'convertible_not_smaller',
 
1418       foreach my $sinfo (@{ $stock_info }) {
 
1419         next if (($row->{bin_id}       != $sinfo->{bin_id}) ||
 
1420                  ($row->{warehouse_id} != $sinfo->{warehouse_id}) ||
 
1421                  ($row->{chargenumber} ne $sinfo->{chargenumber}) ||
 
1422                  ($row->{bestbefore}   ne $sinfo->{bestbefore}));
 
1424         map { $row->{"stock_$_"} = $sinfo->{$_} } qw(qty unit error delivery_order_items_stock_id);
 
1429     get_basic_bin_wh_info($stock_info);
 
1431     foreach my $sinfo (@{ $stock_info }) {
 
1432       map { $sinfo->{"stock_$_"} = $sinfo->{$_} } qw(qty unit);
 
1436   $form->header(no_layout => 1);
 
1437   print $form->parse_html_template('do/stock_out_form', { 'UNITS'      => $units_data,
 
1438                                                           'WHCONTENTS' => $form->{delivered} ? $stock_info : \@contents,
 
1439                                                           'PART_INFO'  => $part_info, });
 
1441   $main::lxdebug->leave_sub();
 
1445   $main::lxdebug->enter_sub();
 
1447   my $form     = $main::form;
 
1448   my %myconfig = %main::myconfig;
 
1449   my $locale   = $main::locale;
 
1451   my $stock_info = [];
 
1453   foreach my $i (1 .. $form->{rowcount}) {
 
1454     $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
 
1456     next if ($form->{"qty_$i"} <= 0);
 
1458     push @{ $stock_info }, {
 
1459       'warehouse_id' => $form->{"warehouse_id_$i"},
 
1460       'bin_id'       => $form->{"bin_id_$i"},
 
1461       'chargenumber' => $form->{"chargenumber_$i"},
 
1462       'bestbefore'   => $form->{"bestbefore_$i"},
 
1463       'qty'          => $form->{"qty_$i"},
 
1464       'unit'         => $form->{"unit_$i"},
 
1466       'delivery_order_items_stock_id'  => $form->{"delivery_order_items_stock_id_$i"},
 
1470   my @errors     = DO->check_stock_availability('requests' => $stock_info,
 
1471                                                 'parts_id' => $form->{parts_id});
 
1473   $form->{stock} = YAML::Dump($stock_info);
 
1476     $form->{ERRORS} = [];
 
1477     map { push @{ $form->{ERRORS} }, $locale->text('Error in row #1: The quantity you entered is bigger than the stocked quantity.', $_->{row}); } @errors;
 
1478     stock_in_out_form();
 
1481     _stock_in_out_set_qty_display($stock_info);
 
1483     my $do_qty       = AM->sum_with_unit($::form->parse_amount(\%::myconfig, $::form->{do_qty}), $::form->{do_unit});
 
1484     my $transfer_qty = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $stock_info });
 
1487     print $form->parse_html_template('do/set_stock_in_out', {
 
1488       qty_matches => $do_qty == $transfer_qty,
 
1492   $main::lxdebug->leave_sub();
 
1496   $main::lxdebug->enter_sub();
 
1498   my $form     = $main::form;
 
1499   my %myconfig = %main::myconfig;
 
1500   my $locale   = $main::locale;
 
1502   if ($form->{id} && DO->is_marked_as_delivered(id => $form->{id})) {
 
1503     $form->show_generic_error($locale->text('The parts for this delivery order have already been transferred in.'));
 
1506   save(no_redirect => 1);
 
1508   my @part_ids = map { $form->{"id_${_}"} } grep { $form->{"id_${_}"} && $form->{"stock_in_${_}"} } (1 .. $form->{rowcount});
 
1512     my $units         = AM->retrieve_units(\%myconfig, $form);
 
1513     my %part_info_map = IC->get_basic_part_info('id' => \@part_ids);
 
1516     $form->{ERRORS}   = [];
 
1518     foreach my $i (1 .. $form->{rowcount}) {
 
1519       next unless ($form->{"id_$i"} && $form->{"stock_in_$i"});
 
1521       my $row_sum_base_qty = 0;
 
1522       my $base_unit_factor = $units->{ $part_info_map{$form->{"id_$i"}}->{unit} }->{factor} || 1;
 
1524       foreach my $request (@{ DO->unpack_stock_information('packed' => $form->{"stock_in_$i"}) }) {
 
1525         $request->{parts_id}  = $form->{"id_$i"};
 
1526         $row_sum_base_qty    += $request->{qty} * $units->{$request->{unit}}->{factor} / $base_unit_factor;
 
1528         $request->{project_id} = $form->{"project_id_$i"} || $form->{globalproject_id};
 
1530         push @all_requests, $request;
 
1533       next if (0 == $row_sum_base_qty);
 
1535       my $do_base_qty = $form->parse_amount(\%myconfig, $form->{"qty_$i"}) * $units->{$form->{"unit_$i"}}->{factor} / $base_unit_factor;
 
1537 #      if ($do_base_qty != $row_sum_base_qty) {
 
1538 #        push @{ $form->{ERRORS} }, $locale->text('Error in position #1: You must either assign no stock at all or the full quantity of #2 #3.',
 
1539 #                                                 $i, $form->{"qty_$i"}, $form->{"unit_$i"});
 
1543     if (@{ $form->{ERRORS} }) {
 
1544       push @{ $form->{ERRORS} }, $locale->text('The delivery order has not been marked as delivered. The warehouse contents have not changed.');
 
1546       set_headings('edit');
 
1548       $main::lxdebug->leave_sub();
 
1550       $::dispatcher->end_request;
 
1554   DO->transfer_in_out('direction' => 'in',
 
1555                       'requests'  => \@all_requests);
 
1557   SL::DB::DeliveryOrder->new(id => $form->{id})->load->update_attributes(delivered => 1);
 
1559   $form->{callback} = 'do.pl?action=edit&type=purchase_delivery_order&id=' . $form->escape($form->{id});
 
1562   $main::lxdebug->leave_sub();
 
1566   $main::lxdebug->enter_sub();
 
1568   my $form     = $main::form;
 
1569   my %myconfig = %main::myconfig;
 
1570   my $locale   = $main::locale;
 
1572   if ($form->{id} && DO->is_marked_as_delivered(id => $form->{id})) {
 
1573     $form->show_generic_error($locale->text('The parts for this delivery order have already been transferred out.'));
 
1576   save(no_redirect => 1);
 
1578   my @part_ids = map { $form->{"id_${_}"} } grep { $form->{"id_${_}"} && $form->{"stock_out_${_}"} } (1 .. $form->{rowcount});
 
1582     my $units         = AM->retrieve_units(\%myconfig, $form);
 
1583     my %part_info_map = IC->get_basic_part_info('id' => \@part_ids);
 
1586     $form->{ERRORS}   = [];
 
1588     foreach my $i (1 .. $form->{rowcount}) {
 
1589       next unless ($form->{"id_$i"} && $form->{"stock_out_$i"});
 
1591       my $row_sum_base_qty = 0;
 
1592       my $base_unit_factor = $units->{ $part_info_map{$form->{"id_$i"}}->{unit} }->{factor} || 1;
 
1594       foreach my $request (@{ DO->unpack_stock_information('packed' => $form->{"stock_out_$i"}) }) {
 
1595         $request->{parts_id} = $form->{"id_$i"};
 
1596         $request->{base_qty} = $request->{qty} * $units->{$request->{unit}}->{factor} / $base_unit_factor;
 
1597         $request->{project_id} = $form->{"project_id_$i"} ? $form->{"project_id_$i"} : $form->{globalproject_id};
 
1599         my $map_key          = join '--', ($form->{"id_$i"}, @{$request}{qw(warehouse_id bin_id chargenumber bestbefore)});
 
1601         $request_map{$map_key}                 ||= $request;
 
1602         $request_map{$map_key}->{sum_base_qty} ||= 0;
 
1603         $request_map{$map_key}->{sum_base_qty}  += $request->{base_qty};
 
1604         $row_sum_base_qty                       += $request->{base_qty};
 
1606         push @all_requests, $request;
 
1609       next if (0 == $row_sum_base_qty);
 
1611       my $do_base_qty = $form->{"qty_$i"} * $units->{$form->{"unit_$i"}}->{factor} / $base_unit_factor;
 
1613 #      if ($do_base_qty != $row_sum_base_qty) {
 
1614 #        push @{ $form->{ERRORS} }, $locale->text('Error in position #1: You must either assign no transfer at all or the full quantity of #2 #3.',
 
1615 #                                                 $i, $form->{"qty_$i"}, $form->{"unit_$i"});
 
1620       my @bin_ids      = map { $_->{bin_id} } values %request_map;
 
1621       my %bin_info_map = WH->get_basic_bin_info('id' => \@bin_ids);
 
1622       my @contents     = DO->get_item_availability('parts_id' => \@part_ids);
 
1624       foreach my $inv (@contents) {
 
1625         my $map_key = join '--', @{$inv}{qw(parts_id warehouse_id bin_id chargenumber bestbefore)};
 
1627         next unless ($request_map{$map_key});
 
1629         my $request    = $request_map{$map_key};
 
1630         $request->{ok} = $request->{sum_base_qty} <= $inv->{qty};
 
1633       foreach my $request (values %request_map) {
 
1634         next if ($request->{ok});
 
1636         my $pinfo = $part_info_map{$request->{parts_id}};
 
1637         my $binfo = $bin_info_map{$request->{bin_id}};
 
1639         if ($::instance_conf->get_show_bestbefore) {
 
1640             push @{ $form->{ERRORS} }, $locale->text("There is not enough available of '#1' at warehouse '#2', bin '#3', #4, #5, for the transfer of #6.",
 
1641                                                      $pinfo->{description},
 
1642                                                      $binfo->{warehouse_description},
 
1643                                                      $binfo->{bin_description},
 
1644                                                      $request->{chargenumber} ? $locale->text('chargenumber #1', $request->{chargenumber}) : $locale->text('no chargenumber'),
 
1645                                                      $request->{bestbefore} ? $locale->text('bestbefore #1', $request->{bestbefore}) : $locale->text('no bestbefore'),
 
1646                                                      $form->format_amount_units('amount'      => $request->{sum_base_qty},
 
1647                                                                                 'part_unit'   => $pinfo->{unit},
 
1648                                                                                 'conv_units'  => 'convertible_not_smaller'));
 
1650             push @{ $form->{ERRORS} }, $locale->text("There is not enough available of '#1' at warehouse '#2', bin '#3', #4, for the transfer of #5.",
 
1651                                                      $pinfo->{description},
 
1652                                                      $binfo->{warehouse_description},
 
1653                                                      $binfo->{bin_description},
 
1654                                                      $request->{chargenumber} ? $locale->text('chargenumber #1', $request->{chargenumber}) : $locale->text('no chargenumber'),
 
1655                                                      $form->format_amount_units('amount'      => $request->{sum_base_qty},
 
1656                                                                                 'part_unit'   => $pinfo->{unit},
 
1657                                                                                 'conv_units'  => 'convertible_not_smaller'));
 
1662     if (@{ $form->{ERRORS} }) {
 
1663       push @{ $form->{ERRORS} }, $locale->text('The delivery order has not been marked as delivered. The warehouse contents have not changed.');
 
1665       set_headings('edit');
 
1667       $main::lxdebug->leave_sub();
 
1669       $::dispatcher->end_request;
 
1672   DO->transfer_in_out('direction' => 'out',
 
1673                       'requests'  => \@all_requests);
 
1675   SL::DB::DeliveryOrder->new(id => $form->{id})->load->update_attributes(delivered => 1);
 
1677   $form->{callback} = 'do.pl?action=edit&type=sales_delivery_order&id=' . $form->escape($form->{id});
 
1680   $main::lxdebug->leave_sub();
 
1684   $main::lxdebug->enter_sub();
 
1686   my $form     = $main::form;
 
1688   DO->close_orders('ids' => [ $form->{id} ]);
 
1690   $form->{closed} = 1;
 
1694   $main::lxdebug->leave_sub();
 
1698   $::lxdebug->enter_sub;
 
1700   $::auth->assert('purchase_delivery_order_edit | sales_delivery_order_edit');
 
1703   retrieve_partunits();
 
1705   my $new_rowcount = $::form->{"rowcount"} * 1 + 1;
 
1706   $::form->{"project_id_${new_rowcount}"} = $::form->{"globalproject_id"};
 
1708   $::form->language_payment(\%::myconfig);
 
1710   Common::webdav_folder($::form);
 
1713   display_row(++$::form->{rowcount});
 
1716   $::lxdebug->leave_sub;
 
1720   call_sub($main::form->{yes_nextsub});
 
1724   call_sub($main::form->{no_nextsub});
 
1728   call_sub($main::form->{update_nextsub} || $main::form->{nextsub} || 'update_delivery_order');
 
1732   my $form     = $main::form;
 
1733   my $locale   = $main::locale;
 
1735   foreach my $action (qw(update print save transfer_out transfer_out_default sort
 
1736                          transfer_in transfer_in_default mark_closed save_as_new invoice delete)) {
 
1737     if ($form->{"action_${action}"}) {
 
1743   $form->error($locale->text('No action defined.'));
 
1746 sub transfer_out_default {
 
1747   $main::lxdebug->enter_sub();
 
1749   my $form     = $main::form;
 
1751   transfer_in_out_default('direction' => 'out');
 
1753   $main::lxdebug->leave_sub();
 
1756 sub transfer_in_default {
 
1757   $main::lxdebug->enter_sub();
 
1759   my $form     = $main::form;
 
1761   transfer_in_out_default('direction' => 'in');
 
1763   $main::lxdebug->leave_sub();
 
1766 # Falls das Standardlagerverfahren aktiv ist, wird
 
1767 # geprüft, ob alle Standardlagerplätze für die Auslager-
 
1768 # artikel vorhanden sind UND ob die Warenmenge ausreicht zum
 
1769 # Auslagern. Falls nicht wird entsprechend eine Fehlermeldung
 
1770 # generiert. Offen Chargennummer / bestbefore wird nicht berücksichtigt
 
1771 sub transfer_in_out_default {
 
1772   $main::lxdebug->enter_sub();
 
1774   my $form     = $main::form;
 
1775   my %myconfig = %main::myconfig;
 
1776   my $locale   = $main::locale;
 
1779   my (%missing_default_bins, %qty_parts, @all_requests, %part_info_map, $default_warehouse_id, $default_bin_id);
 
1781   Common::check_params(\%params, qw(direction));
 
1783   # entsprechende defaults holen, falls standardlagerplatz verwendet werden soll
 
1784   if ($::instance_conf->get_transfer_default_use_master_default_bin) {
 
1785     $default_warehouse_id = $::instance_conf->get_warehouse_id;
 
1786     $default_bin_id       = $::instance_conf->get_bin_id;
 
1790   my @part_ids = map { $form->{"id_${_}"} } (1 .. $form->{rowcount});
 
1792     my $units         = AM->retrieve_units(\%myconfig, $form);
 
1793     %part_info_map = IC->get_basic_part_info('id' => \@part_ids);
 
1794     foreach my $i (1 .. $form->{rowcount}) {
 
1795       next unless ($form->{"id_$i"});
 
1796       my $base_unit_factor = $units->{ $part_info_map{$form->{"id_$i"}}->{unit} }->{factor} || 1;
 
1797       my $qty =   $form->parse_amount(\%myconfig, $form->{"qty_$i"}) * $units->{$form->{"unit_$i"}}->{factor} / $base_unit_factor;
 
1799       $form->show_generic_error($locale->text("Cannot transfer negative entries." )) if ($qty < 0);
 
1800       # if we do not want to transfer services and this part is a service, set qty to zero
 
1801       # ... and do not create a hash entry in %qty_parts below (will skip check for bins for the transfer == out case)
 
1802       # ... 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)
 
1804       $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});
 
1805       $qty_parts{$form->{"id_$i"}} += $qty;
 
1807         delete $qty_parts{$form->{"id_$i"}} unless $qty_parts{$form->{"id_$i"}};
 
1808         undef $form->{"stock_in_$i"};
 
1811       $part_info_map{$form->{"id_$i"}}{bin_id}       ||= $default_bin_id;
 
1812       $part_info_map{$form->{"id_$i"}}{warehouse_id} ||= $default_warehouse_id;
 
1814       push @all_requests, ($qty == 0) ? { } : {
 
1815                         'chargenumber' => '',  #?? die müsste entsprechend geholt werden
 
1816                         #'bestbefore' => undef, # TODO wird nicht berücksichtigt
 
1817                         'bin_id' => $part_info_map{$form->{"id_$i"}}{bin_id},
 
1819                         'parts_id' => $form->{"id_$i"},
 
1820                         'comment' => $locale->text("Default transfer delivery order"),
 
1821                         'unit' => $part_info_map{$form->{"id_$i"}}{unit},
 
1822                         'warehouse_id' => $part_info_map{$form->{"id_$i"}}{warehouse_id},
 
1823                         'oe_id' => $form->{id},
 
1824                         'project_id' => $form->{"project_id_$i"} ? $form->{"project_id_$i"} : $form->{globalproject_id}
 
1828     # jetzt wird erst überprüft, ob die Stückzahl entsprechend stimmt.
 
1829     # check if bin (transfer in and transfer out and qty (transfer out) is correct
 
1830     foreach my $key (keys %qty_parts) {
 
1832       $missing_default_bins{$key}{missing_bin} = 1 unless ($part_info_map{$key}{bin_id});
 
1833       next unless ($part_info_map{$key}{bin_id}); # abbruch
 
1835       if ($params{direction} eq 'out') {  # wird nur für ausgehende Mengen benötigt
 
1836         my ($max_qty, $error) = WH->get_max_qty_parts_bin(parts_id => $key, bin_id => $part_info_map{$key}{bin_id});
 
1838           # wir können nicht entscheiden, welche charge oder mhd (bestbefore) ausgewählt sein soll
 
1839           # deshalb rückmeldung nach oben geben, manuell auszulagern
 
1840           # TODO Bei nur einem Treffer mit Charge oder bestbefore wäre das noch möglich
 
1841           $missing_default_bins{$key}{chargenumber} = 1;
 
1843         if ($max_qty < $qty_parts{$key}){
 
1844           $missing_default_bins{$key}{missing_qty} = $max_qty - $qty_parts{$key};
 
1850   # Abfrage für Fehlerbehandlung (nur bei direction == out)
 
1851   if (scalar (keys %missing_default_bins)) {
 
1853     foreach my $fehler (keys %missing_default_bins) {
 
1855       my $ware = WH->get_part_description(parts_id => $fehler);
 
1856       if ($missing_default_bins{$fehler}{missing_bin}){
 
1857         $fehlertext .= "Kein Standardlagerplatz definiert bei $ware <br>";
 
1859       if ($missing_default_bins{$fehler}{missing_qty}) {  # missing_qty
 
1860         $fehlertext .= "Es fehlen " . $missing_default_bins{$fehler}{missing_qty}*-1 .
 
1861                        " von $ware auf dem Standard-Lagerplatz " . $part_info_map{$fehler}{bin} .   " zum Auslagern<br>";
 
1863       if ($missing_default_bins{$fehler}{chargenumber}){
 
1864         $fehlertext .= "Die Ware hat eine Chargennummer oder eine Mindesthaltbarkeit definiert.
 
1865                         Hier kann man nicht automatisch entscheiden.
 
1866                         Bitte diesen Lieferschein manuell auslagern.
 
1869       # auslagern soll immer gehen, auch wenn nicht genügend auf lager ist.
 
1870       # der lagerplatz ist hier extra konfigurierbar, bspw. Lager-Korrektur mit
 
1871       # Lagerplatz Lagerplatz-Korrektur
 
1872       my $default_warehouse_id_ignore_onhand = $::instance_conf->get_warehouse_id_ignore_onhand;
 
1873       my $default_bin_id_ignore_onhand       = $::instance_conf->get_bin_id_ignore_onhand;
 
1874       if ($::instance_conf->get_transfer_default_ignore_onhand && $default_bin_id_ignore_onhand) {
 
1875         # entsprechende defaults holen
 
1876         # falls chargenumber, bestbefore oder anzahl nicht stimmt, auf automatischen
 
1877         # lagerplatz wegbuchen!
 
1878         foreach (@all_requests) {
 
1879           if ($_->{parts_id} eq $fehler){
 
1880           $_->{bin_id}        = $default_bin_id_ignore_onhand;
 
1881           $_->{warehouse_id}  = $default_warehouse_id_ignore_onhand;
 
1885         #$main::lxdebug->message(0, 'Fehlertext: ' . $fehlertext);
 
1886         $form->show_generic_error($locale->text("Cannot transfer. <br> Reason:<br>#1", $fehlertext ));
 
1892   # hier der eigentliche fallunterschied für in oder out
 
1893   my $prefix   = $params{direction} eq 'in' ? 'in' : 'out';
 
1895   # dieser array_ref ist für DO->save da:
 
1896   # einmal die all_requests in YAML verwandeln, damit delivery_order_items_stock
 
1897   # gefüllt werden kann.
 
1898   # could be dumped to the form in the first loop,
 
1899   # but maybe bin_id and warehouse_id has changed to the "korrekturlager" with
 
1900   # allowed negative qty ($::instance_conf->get_warehouse_id_ignore_onhand) ...
 
1902   foreach (@all_requests){
 
1904     next unless scalar(%{ $_ });
 
1905     $form->{"stock_${prefix}_$i"} = YAML::Dump([$_]);
 
1908   save(no_redirect => 1); # Wir können auslagern, deshalb beleg speichern
 
1909                           # und in delivery_order_items_stock speichern
 
1911   # ... and fill back the persistent dois_id for inventory fk
 
1912   undef (@all_requests);
 
1913   foreach my $i (1 .. $form->{rowcount}) {
 
1914     next unless ($form->{"id_$i"} && $form->{"stock_${prefix}_$i"});
 
1915     push @all_requests, @{ DO->unpack_stock_information('packed' => $form->{"stock_${prefix}_$i"}) };
 
1917   DO->transfer_in_out('direction' => $prefix,
 
1918                       'requests'  => \@all_requests);
 
1920   SL::DB::DeliveryOrder->new(id => $form->{id})->load->update_attributes(delivered => 1);
 
1922   $form->{callback} = 'do.pl?action=edit&type=sales_delivery_order&id=' . $form->escape($form->{id}) if $params{direction} eq 'out';
 
1923   $form->{callback} = 'do.pl?action=edit&type=purchase_delivery_order&id=' . $form->escape($form->{id}) if $params{direction} eq 'in';
 
1929   $main::lxdebug->enter_sub();
 
1933   my $form     = $main::form;
 
1936   save(no_redirect => 1); # has to be done, at least for newly added positions
 
1938   # hashify partnumbers, positions. key is delivery_order_items_id
 
1939   for my $i (1 .. ($form->{rowcount}) ) {
 
1940     $temp_hash{$form->{"delivery_order_items_id_$i"}} = { runningnumber => $form->{"runningnumber_$i"}, partnumber => $form->{"partnumber_$i"} };
 
1941     if ($form->{id} && $form->{"discount_$i"}) {
 
1942       # prepare_order assumes a db value if there is a form->id and multiplies *100
 
1943       # We hope for new controller code (no more format_amount/parse_amount distinction)
 
1944       $form->{"discount_$i"} /=100;
 
1947   # naturally sort partnumbers and get a sorted array of doi_ids
 
1948   my @sorted_doi_ids =  sort { Sort::Naturally::ncmp($temp_hash{$a}->{"partnumber"}, $temp_hash{$b}->{"partnumber"}) }  keys %temp_hash;
 
1953   for (@sorted_doi_ids) {
 
1954     $form->{"runningnumber_$temp_hash{$_}->{runningnumber}"} = $new_number;
 
1957   # all parse_amounts changes are in form (i.e. , to .) therefore we need
 
1958   # another format_amount to change it back, for the next save ;-(
 
1959   # works great except for row discounts (see above comment)
 
1963     $main::lxdebug->leave_sub();
 
1975 do.pl - Script for all calls to delivery order
 
1983 Sorts all position with Natural Sort. Can be activated in form_footer.html like this
 
1984 C<E<lt>input class="submit" type="submit" name="action_sort" id="sort_button" value="[% 'Sort and Save' | $T8 %]"E<gt>>
 
1990 Sort and Save can be implemented as an optional button if configuration ca be set by client config.
 
1991 Example coding for database scripts and templates in (git show af2f24b8), check also
 
1992 autogeneration for rose (scripts/rose_auto_create_model.pl --h)