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., 675 Mass Ave, Cambridge, MA 02139, USA.
 
  28 #======================================================================
 
  31 #======================================================================
 
  34 use List::MoreUtils qw(uniq);
 
  35 use List::Util qw(max sum);
 
  36 use POSIX qw(strftime);
 
  39 use SL::DB::DeliveryOrder;
 
  43 use SL::MoreCommon qw(ary_diff);
 
  44 use SL::ReportGenerator;
 
  46 use Sort::Naturally ();
 
  47 require "bin/mozilla/arap.pl";
 
  48 require "bin/mozilla/common.pl";
 
  49 require "bin/mozilla/invoice_io.pl";
 
  50 require "bin/mozilla/io.pl";
 
  51 require "bin/mozilla/reportgenerator.pl";
 
  60   $main::auth->assert($main::form->{type} . '_edit');
 
  64   $main::lxdebug->enter_sub();
 
  70   my $form     = $main::form;
 
  71   my $locale   = $main::locale;
 
  73   if ($form->{type} eq 'purchase_delivery_order') {
 
  74     $form->{vc}    = 'vendor';
 
  75     $form->{title} = $action eq "edit" ? $locale->text('Edit Purchase Delivery Order') : $locale->text('Add Purchase Delivery Order');
 
  77     $form->{vc}    = 'customer';
 
  78     $form->{title} = $action eq "edit" ? $locale->text('Edit Sales Delivery Order') : $locale->text('Add Sales Delivery Order');
 
  81   $form->{heading} = $locale->text('Delivery Order');
 
  83   $main::lxdebug->leave_sub();
 
  87   $main::lxdebug->enter_sub();
 
  91   if (($::form->{type} =~ /purchase/) && !$::instance_conf->get_allow_new_purchase_invoice) {
 
  92     $::form->show_generic_error($::locale->text("You do not have the permissions to access this function."));
 
  95   my $form     = $main::form;
 
  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   # show history button
 
 116   $form->{javascript} = qq|<script type="text/javascript" src="js/show_history.js"></script>|;
 
 117   #/show hhistory button
 
 119   $form->{simple_save} = 0;
 
 121   set_headings("edit");
 
 123   # editing without stuff to edit? try adding it first
 
 124   if ($form->{rowcount} && !$form->{print_and_save}) {
 
 125 #     map { $id++ if $form->{"multi_id_$_"} } (1 .. $form->{rowcount});
 
 129       undef $form->{rowcount};
 
 131       $main::lxdebug->leave_sub();
 
 134   } elsif (!$form->{id}) {
 
 136     $main::lxdebug->leave_sub();
 
 140   my ($language_id, $printer_id);
 
 141   if ($form->{print_and_save}) {
 
 142     $form->{action}   = "dispatcher";
 
 143     $form->{action_print} = "1";
 
 144     $form->{resubmit} = 1;
 
 145     $language_id      = $form->{language_id};
 
 146     $printer_id       = $form->{printer_id};
 
 149   set_headings("edit");
 
 154   if ($form->{print_and_save}) {
 
 155     $form->{language_id} = $language_id;
 
 156     $form->{printer_id}  = $printer_id;
 
 161   $main::lxdebug->leave_sub();
 
 165   $main::lxdebug->enter_sub();
 
 169   my $form     = $main::form;
 
 170   my %myconfig = %main::myconfig;
 
 172   # get customer/vendor
 
 173   $form->all_vc(\%myconfig, $form->{vc}, ($form->{vc} eq 'customer') ? "AR" : "AP");
 
 175   # retrieve order/quotation
 
 176   my $editing = $form->{id};
 
 178   DO->retrieve('vc'  => $form->{vc},
 
 179                'ids' => $form->{id});
 
 181   $form->backup_vars(qw(payment_id language_id taxzone_id salesman_id taxincluded cp_id intnotes delivery_term_id currency));
 
 183   # get customer / vendor
 
 184   if ($form->{vc} eq 'vendor') {
 
 185     IR->get_vendor(\%myconfig, \%$form);
 
 186     $form->{discount} = $form->{vendor_discount};
 
 188     IS->get_customer(\%myconfig, \%$form);
 
 189     $form->{discount} = $form->{customer_discount};
 
 192   $form->restore_vars(qw(payment_id language_id taxzone_id intnotes cp_id delivery_term_id));
 
 193   $form->restore_vars(qw(currency)) if ($form->{id} || $form->{convert_from_oe_ids});
 
 194   $form->restore_vars(qw(taxincluded)) if $form->{id};
 
 195   $form->restore_vars(qw(salesman_id)) if $editing;
 
 197   if ($form->{"all_$form->{vc}"}) {
 
 198     unless ($form->{"$form->{vc}_id"}) {
 
 199       $form->{"$form->{vc}_id"} = $form->{"all_$form->{vc}"}->[0]->{id};
 
 203   ($form->{ $form->{vc} })  = split /--/, $form->{ $form->{vc} };
 
 204   $form->{"old$form->{vc}"} = qq|$form->{$form->{vc}}--$form->{"$form->{vc}_id"}|;
 
 206   $form->{employee} = "$form->{employee}--$form->{employee_id}";
 
 208   $main::lxdebug->leave_sub();
 
 212   $main::lxdebug->enter_sub();
 
 216   my $form     = $main::form;
 
 217   my %myconfig = %main::myconfig;
 
 219   $form->{formname} = $form->{type} unless $form->{formname};
 
 222   foreach my $ref (@{ $form->{form_details} }) {
 
 223     $form->{rowcount} = ++$i;
 
 225     map { $form->{"${_}_$i"} = $ref->{$_} } keys %{$ref};
 
 227   for my $i (1 .. $form->{rowcount}) {
 
 229       $form->{"discount_$i"} = $form->format_amount(\%myconfig, $form->{"discount_$i"} * 100);
 
 231       $form->{"discount_$i"} = $form->format_amount(\%myconfig, $form->{"discount_$i"});
 
 233     my ($dec) = ($form->{"sellprice_$i"} =~ /\.(\d+)/);
 
 235     my $decimalplaces = ($dec > 2) ? $dec : 2;
 
 237     # copy reqdate from deliverydate for invoice -> order conversion
 
 238     $form->{"reqdate_$i"} = $form->{"deliverydate_$i"} unless $form->{"reqdate_$i"};
 
 240     $form->{"sellprice_$i"} = $form->format_amount(\%myconfig, $form->{"sellprice_$i"}, $decimalplaces);
 
 241     $form->{"lastcost_$i"} = $form->format_amount(\%myconfig, $form->{"lastcost_$i"}, $decimalplaces);
 
 243     (my $dec_qty) = ($form->{"qty_$i"} =~ /\.(\d+)/);
 
 244     $dec_qty = length $dec_qty;
 
 245     $form->{"qty_$i"} = $form->format_amount(\%myconfig, $form->{"qty_$i"}, $dec_qty);
 
 248   $main::lxdebug->leave_sub();
 
 252   $main::lxdebug->enter_sub();
 
 256   my $form     = $main::form;
 
 257   my %myconfig = %main::myconfig;
 
 259   $form->{employee_id} = $form->{old_employee_id} if $form->{old_employee_id};
 
 260   $form->{salesman_id} = $form->{old_salesman_id} if $form->{old_salesman_id};
 
 262   my $vc = $form->{vc} eq "customer" ? "customers" : "vendors";
 
 263   $form->get_lists($vc              => "ALL_VC",
 
 264                    "price_factors"  => "ALL_PRICE_FACTORS",
 
 265                    "departments"    => "ALL_DEPARTMENTS",
 
 266                    "business_types" => "ALL_BUSINESS_TYPES",
 
 270   my @old_project_ids = uniq grep { $_ } map { $_ * 1 } ($form->{"globalproject_id"}, map { $form->{"project_id_$_"} } 1..$form->{"rowcount"});
 
 271   my @old_ids_cond    = @old_project_ids ? (id => \@old_project_ids) : ();
 
 273   if (($vc eq 'customers') && $::instance_conf->get_customer_projects_only_in_sales) {
 
 276         customer_id          => $::form->{customer_id},
 
 277         billable_customer_id => $::form->{customer_id},
 
 282       and => [ active => 1, @customer_cond ],
 
 286   $::form->{ALL_PROJECTS}          = SL::DB::Manager::Project->get_all_sorted(query => \@conditions);
 
 287   $::form->{ALL_EMPLOYEES}         = SL::DB::Manager::Employee->get_all_sorted(query => [ or => [ id => $::form->{employee_id},  deleted => 0 ] ]);
 
 288   $::form->{ALL_SALESMEN}          = SL::DB::Manager::Employee->get_all_sorted(query => [ or => [ id => $::form->{salesman_id},  deleted => 0 ] ]);
 
 289   $::form->{ALL_SHIPTO}            = SL::DB::Manager::Shipto->get_all_sorted(query => [
 
 290     or => [ trans_id  => $::form->{"$::form->{vc}_id"} * 1, and => [ shipto_id => $::form->{shipto_id} * 1, trans_id => undef ] ]
 
 292   $::form->{ALL_CONTACTS}          = SL::DB::Manager::Contact->get_all_sorted(query => [
 
 294       cp_cv_id => $::form->{"$::form->{vc}_id"} * 1,
 
 297         cp_id    => $::form->{cp_id} * 1
 
 302   map { $_->{value} = "$_->{description}--$_->{id}" } @{ $form->{ALL_DEPARTMENTS} };
 
 303   map { $_->{value} = "$_->{name}--$_->{id}"        } @{ $form->{ALL_VC} };
 
 305   $form->{SHOW_VC_DROP_DOWN} =  $myconfig{vclimit} > scalar @{ $form->{ALL_VC} };
 
 307   $form->{oldvcname}         =  $form->{"old$form->{vc}"};
 
 308   $form->{oldvcname}         =~ s/--.*//;
 
 310   my $dispatch_to_popup = '';
 
 311   if ($form->{resubmit} && ($form->{format} eq "html")) {
 
 312     $dispatch_to_popup  = "window.open('about:blank','Beleg'); document.do.target = 'Beleg';";
 
 313     $dispatch_to_popup .= "document.do.submit();";
 
 314   } elsif ($form->{resubmit}) {
 
 315     # emulate click for resubmitting actions
 
 316     $dispatch_to_popup  = "document.do.${_}.click(); " for grep { /^action_/ } keys %$form;
 
 318   $::request->{layout}->add_javascripts_inline("\$(function(){$dispatch_to_popup});");
 
 321   my $follow_up_vc                =  $form->{ $form->{vc} eq 'customer' ? 'customer' : 'vendor' };
 
 322   $follow_up_vc                   =~ s/--\d*\s*$//;
 
 324   $form->{follow_up_trans_info} = $form->{donumber} .'('. $follow_up_vc .')';
 
 326   $::request->{layout}->use_javascript(map { "${_}.js" } qw(kivi.SalesPurchase ckeditor/ckeditor ckeditor/adapters/jquery kivi.io autocomplete_customer autocomplete_part));
 
 329   # Fix für Bug 1082 Erwartet wird: 'abteilungsNAME--abteilungsID'
 
 330   # und Erweiterung für Bug 1760:
 
 331   # Das war leider nur ein Teil-Fix, da das Verhalten den 'Erneuern'-Knopf
 
 332   # nicht überlebt. Konsequent jetzt auf L umgestellt
 
 333   #   $ perldoc SL::Template::Plugin::L
 
 334   # Daher entsprechend nur die Anpassung in form_header
 
 335   # und in DO.pm gemacht. 4 Testfälle:
 
 336   # department_id speichern                 | i.O.
 
 337   # department_id lesen                     | i.O.
 
 338   # department leer überlebt erneuern       | i.O.
 
 339   # department nicht leer überlebt erneuern | i.O.
 
 340   # $main::lxdebug->message(0, 'ABTEILUNGS ID in form?' . $form->{department_id});
 
 341   print $form->parse_html_template('do/form_header');
 
 343   $main::lxdebug->leave_sub();
 
 347   $main::lxdebug->enter_sub();
 
 351   my $form     = $main::form;
 
 353   $form->{PRINT_OPTIONS} = print_options('inline' => 1);
 
 354   $form->{ALL_DELIVERY_TERMS} = SL::DB::Manager::DeliveryTerm->get_all_sorted();
 
 356   print $form->parse_html_template('do/form_footer',
 
 357     {transfer_default         => ($::instance_conf->get_transfer_default)});
 
 359   $main::lxdebug->leave_sub();
 
 362 sub update_delivery_order {
 
 363   $main::lxdebug->enter_sub();
 
 367   my $form     = $main::form;
 
 368   my %myconfig = %main::myconfig;
 
 370   set_headings($form->{"id"} ? "edit" : "add");
 
 372   $form->{insertdate} = SL::DB::DeliveryOrder->new(id => $form->{id})->load->itime_as_date if $form->{id};
 
 377   $payment_id = $form->{payment_id} if $form->{payment_id};
 
 379   check_name($form->{vc});
 
 380   $form->{discount} =  $form->{"$form->{vc}_discount"} if defined $form->{"$form->{vc}_discount"};
 
 381   # Problem: Wenn man ohne Erneuern einen Kunden/Lieferanten
 
 382   # wechselt, wird der entsprechende Kunden/ Lieferantenrabatt
 
 383   # nicht übernommen. Grundproblem: In Commit 82574e78
 
 384   # hab ich aus discount customer_discount und vendor_discount
 
 385   # gemacht und entsprechend an den Oberflächen richtig hin-
 
 386   # geschoben. Die damals bessere Lösung wäre gewesen:
 
 387   # In den Templates nur die hidden für form-discount wieder ein-
 
 388   # setzen dann wäre die Verrenkung jetzt nicht notwendig.
 
 389   # TODO: Ggf. Bugfix 1284, 1575 und 817 wieder zusammenführen
 
 390   # Testfälle: Kunden mit Rabatt 0 -> Rabatt 20 i.O.
 
 391   #            Kunde mit Rabatt 20 -> Rabatt 0  i.O.
 
 392   #            Kunde mit Rabatt 20 -> Rabatt 5,5 i.O.
 
 393   $form->{payment_id} = $payment_id if $form->{payment_id} eq "";
 
 395   my $i = $form->{rowcount};
 
 397   if (   ($form->{"partnumber_$i"} eq "")
 
 398       && ($form->{"description_$i"} eq "")
 
 399       && ($form->{"partsgroup_$i"}  eq "")) {
 
 406     if ($form->{type} eq 'purchase_delivery_order') {
 
 407       IR->retrieve_item(\%myconfig, $form);
 
 410       IS->retrieve_item(\%myconfig, $form);
 
 414     my $rows = scalar @{ $form->{item_list} };
 
 417       $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
 
 418       if( !$form->{"qty_$i"} ) {
 
 419         $form->{"qty_$i"} = 1;
 
 424         select_item(mode => $mode, pre_entered_qty => $form->{"qty_$i"});
 
 429         my $sellprice = $form->parse_amount(\%myconfig, $form->{"sellprice_$i"});
 
 431         map { $form->{"${_}_$i"} = $form->{item_list}[0]{$_} } keys %{ $form->{item_list}[0] };
 
 433         $form->{"marge_price_factor_$i"} = $form->{item_list}->[0]->{price_factor};
 
 436           $form->{"sellprice_$i"} = $sellprice;
 
 438           my $record        = _make_record();
 
 439           my $price_source  = SL::PriceSource->new(record_item => $record->items->[$i-1], record => $record);
 
 440           my $best_price    = $price_source->best_price;
 
 441           my $best_discount = $price_source->best_discount;
 
 444             $::form->{"sellprice_$i"}           = $best_price->price;
 
 445             $::form->{"active_price_source_$i"} = $best_price->source;
 
 447           if ($best_discount) {
 
 448             $::form->{"discount_$i"}               = $best_discount->discount;
 
 449             $::form->{"active_discount_source_$i"} = $best_discount->source;
 
 453         $form->{"sellprice_$i"}          = $form->format_amount(\%myconfig, $form->{"sellprice_$i"});
 
 454         $form->{"lastcost_$i"}           = $form->format_amount(\%myconfig, $form->{"lastcost_$i"});
 
 455         $form->{"qty_$i"}                = $form->format_amount(\%myconfig, $form->{"qty_$i"});
 
 456         $form->{"discount_$i"}           = $form->format_amount(\%myconfig, $form->{"discount_$i"} * 100.0);
 
 463       # ok, so this is a new part
 
 464       # ask if it is a part or service item
 
 466       if (   $form->{"partsgroup_$i"}
 
 467           && ($form->{"partsnumber_$i"} eq "")
 
 468           && ($form->{"description_$i"} eq "")) {
 
 470         $form->{"discount_$i"} = "";
 
 471         $form->{"not_discountable_$i"} = "";
 
 475         $form->{"id_$i"}   = 0;
 
 481   $main::lxdebug->leave_sub();
 
 485   $main::lxdebug->enter_sub();
 
 489   my $form     = $main::form;
 
 490   my %myconfig = %main::myconfig;
 
 491   my $locale   = $main::locale;
 
 493   $form->{vc} = $form->{type} eq 'purchase_delivery_order' ? 'vendor' : 'customer';
 
 495   $form->get_lists("projects"       => { "key" => "ALL_PROJECTS",
 
 497                    "departments"    => "ALL_DEPARTMENTS",
 
 498                    "$form->{vc}s"   => "ALL_VC",
 
 499                    "business_types" => "ALL_BUSINESS_TYPES");
 
 500   $form->{ALL_EMPLOYEES} = SL::DB::Manager::Employee->get_all_sorted(query => [ deleted => 0 ]);
 
 502   $form->{SHOW_VC_DROP_DOWN} =  $myconfig{vclimit} > scalar @{ $form->{ALL_VC} };
 
 503   $form->{title}             = $locale->text('Delivery Orders');
 
 507   print $form->parse_html_template('do/search');
 
 509   $main::lxdebug->leave_sub();
 
 513   $main::lxdebug->enter_sub();
 
 517   my $form     = $main::form;
 
 518   my %myconfig = %main::myconfig;
 
 519   my $locale   = $main::locale;
 
 520   my $cgi      = $::request->{cgi};
 
 522   $form->{department_id} = (split /--/, $form->{department})[-1];
 
 523   ($form->{ $form->{vc} }, $form->{"$form->{vc}_id"}) = split(/--/, $form->{ $form->{vc} });
 
 525   report_generator_set_default_sort('transdate', 1);
 
 529   $form->{rowcount} = scalar @{ $form->{DO} };
 
 532     ids                     transdate               reqdate
 
 534     ordnumber               customernumber          cusordnumber
 
 535     name                    employee  salesman
 
 536     shipvia                 globalprojectnumber
 
 537     transaction_description department
 
 542   $form->{l_open}      = $form->{l_closed} = "Y" if ($form->{open}      && $form->{closed});
 
 543   $form->{l_delivered} = "Y"                     if ($form->{delivered} && $form->{notdelivered});
 
 545   $form->{title}       = $locale->text('Delivery Orders');
 
 547   my $attachment_basename = $form->{vc} eq 'vendor' ? $locale->text('purchase_delivery_order_list') : $locale->text('sales_delivery_order_list');
 
 549   my $report = SL::ReportGenerator->new(\%myconfig, $form);
 
 551   my @hidden_variables = map { "l_${_}" } @columns;
 
 552   push @hidden_variables, $form->{vc}, qw(l_closed l_notdelivered open closed delivered notdelivered donumber ordnumber serialnumber cusordnumber
 
 553                                           transaction_description transdatefrom transdateto reqdatefrom reqdateto
 
 554                                           type vc employee_id salesman_id project_id
 
 555                                           insertdatefrom insertdateto business_id);
 
 557   my $href = build_std_url('action=orders', grep { $form->{$_} } @hidden_variables);
 
 560     'ids'                     => { 'text' => '', },
 
 561     'transdate'               => { 'text' => $locale->text('Delivery Order Date'), },
 
 562     'reqdate'                 => { 'text' => $locale->text('Reqdate'), },
 
 563     'id'                      => { 'text' => $locale->text('ID'), },
 
 564     'donumber'                => { 'text' => $locale->text('Delivery Order'), },
 
 565     'ordnumber'               => { 'text' => $locale->text('Order'), },
 
 566     'customernumber'          => { 'text' => $locale->text('Customer Number'), },
 
 567     'cusordnumber'            => { 'text' => $locale->text('Customer Order Number'), },
 
 568     'name'                    => { 'text' => $form->{vc} eq 'customer' ? $locale->text('Customer') : $locale->text('Vendor'), },
 
 569     'employee'                => { 'text' => $locale->text('Employee'), },
 
 570     'salesman'                => { 'text' => $locale->text('Salesman'), },
 
 571     'shipvia'                 => { 'text' => $locale->text('Ship via'), },
 
 572     'globalprojectnumber'     => { 'text' => $locale->text('Project Number'), },
 
 573     'transaction_description' => { 'text' => $locale->text('Transaction description'), },
 
 574     'open'                    => { 'text' => $locale->text('Open'), },
 
 575     'delivered'               => { 'text' => $locale->text('Delivered'), },
 
 576     'department'              => { 'text' => $locale->text('Department'), },
 
 577     'insertdate'              => { 'text' => $locale->text('Insert Date'), },
 
 580   foreach my $name (qw(id transdate reqdate donumber ordnumber name employee salesman shipvia transaction_description department insertdate)) {
 
 581     my $sortdir                 = $form->{sort} eq $name ? 1 - $form->{sortdir} : $form->{sortdir};
 
 582     $column_defs{$name}->{link} = $href . "&sort=$name&sortdir=$sortdir";
 
 585   $form->{"l_type"} = "Y";
 
 586   map { $column_defs{$_}->{visible} = $form->{"l_${_}"} ? 1 : 0 } @columns;
 
 588   $column_defs{ids}->{visible} = 'HTML';
 
 590   $report->set_columns(%column_defs);
 
 591   $report->set_column_order(@columns);
 
 593   $report->set_export_options('orders', @hidden_variables, qw(sort sortdir));
 
 595   $report->set_sort_indicator($form->{sort}, $form->{sortdir});
 
 598   if ($form->{customer}) {
 
 599     push @options, $locale->text('Customer') . " : $form->{customer}";
 
 601   if ($form->{vendor}) {
 
 602     push @options, $locale->text('Vendor') . " : $form->{vendor}";
 
 604   if ($form->{cp_name}) {
 
 605     push @options, $locale->text('Contact Person') . " : $form->{cp_name}";
 
 607   if ($form->{department}) {
 
 608     my ($department) = split /--/, $form->{department};
 
 609     push @options, $locale->text('Department') . " : $department";
 
 611   if ($form->{donumber}) {
 
 612     push @options, $locale->text('Delivery Order Number') . " : $form->{donumber}";
 
 614   if ($form->{ordnumber}) {
 
 615     push @options, $locale->text('Order Number') . " : $form->{ordnumber}";
 
 617   push @options, $locale->text('Serial Number') . " : $form->{serialnumber}" if $form->{serialnumber};
 
 618   if ($form->{business_id}) {
 
 619     my $vc_type_label = $form->{vc} eq 'customer' ? $locale->text('Customer type') : $locale->text('Vendor type');
 
 620     push @options, $vc_type_label . " : " . SL::DB::Business->new(id => $form->{business_id})->load->description;
 
 622   if ($form->{transaction_description}) {
 
 623     push @options, $locale->text('Transaction description') . " : $form->{transaction_description}";
 
 625   if ( $form->{transdatefrom} or $form->{transdateto} ) {
 
 626     push @options, $locale->text('Delivery Order Date');
 
 627     push @options, $locale->text('From') . " " . $locale->date(\%myconfig, $form->{transdatefrom}, 1)     if $form->{transdatefrom};
 
 628     push @options, $locale->text('Bis')  . " " . $locale->date(\%myconfig, $form->{transdateto},   1)     if $form->{transdateto};
 
 630   if ( $form->{reqdatefrom} or $form->{reqdateto} ) {
 
 631     push @options, $locale->text('Reqdate');
 
 632     push @options, $locale->text('From') . " " . $locale->date(\%myconfig, $form->{reqdatefrom}, 1)       if $form->{reqdatefrom};
 
 633     push @options, $locale->text('Bis')  . " " . $locale->date(\%myconfig, $form->{reqdateto},   1)       if $form->{reqdateto};
 
 635   if ( $form->{insertdatefrom} or $form->{insertdateto} ) {
 
 636     push @options, $locale->text('Insert Date');
 
 637     push @options, $locale->text('From') . " " . $locale->date(\%myconfig, $form->{insertdatefrom}, 1)    if $form->{insertdatefrom};
 
 638     push @options, $locale->text('Bis')  . " " . $locale->date(\%myconfig, $form->{insertdateto},   1)    if $form->{insertdateto};
 
 641     push @options, $locale->text('Open');
 
 643   if ($form->{closed}) {
 
 644     push @options, $locale->text('Closed');
 
 646   if ($form->{delivered}) {
 
 647     push @options, $locale->text('Delivered');
 
 649   if ($form->{notdelivered}) {
 
 650     push @options, $locale->text('Not delivered');
 
 653   $report->set_options('top_info_text'        => join("\n", @options),
 
 654                        'raw_top_info_text'    => $form->parse_html_template('do/orders_top'),
 
 655                        'raw_bottom_info_text' => $form->parse_html_template('do/orders_bottom'),
 
 656                        'output_format'        => 'HTML',
 
 657                        'title'                => $form->{title},
 
 658                        'attachment_basename'  => $attachment_basename . strftime('_%Y%m%d', localtime time),
 
 660   $report->set_options_from_form();
 
 661   $locale->set_numberformat_wo_thousands_separator(\%myconfig) if lc($report->{options}->{output_format}) eq 'csv';
 
 663   # add sort and escape callback, this one we use for the add sub
 
 664   $form->{callback} = $href .= "&sort=$form->{sort}";
 
 666   # escape callback for href
 
 667   my $callback = $form->escape($href);
 
 669   my $edit_url       = build_std_url('action=edit', 'type', 'vc');
 
 670   my $edit_order_url = build_std_url('script=oe.pl', 'type=' . ($form->{type} eq 'sales_delivery_order' ? 'sales_order' : 'purchase_order'), 'action=edit');
 
 674   foreach my $dord (@{ $form->{DO} }) {
 
 675     $dord->{open}      = $dord->{closed}    ? $locale->text('No')  : $locale->text('Yes');
 
 676     $dord->{delivered} = $dord->{delivered} ? $locale->text('Yes') : $locale->text('No');
 
 678     my $row = { map { $_ => { 'data' => $dord->{$_} } } @columns };
 
 681       'raw_data' =>   $cgi->hidden('-name' => "trans_id_${idx}", '-value' => $dord->{id})
 
 682                     . $cgi->checkbox('-name' => "multi_id_${idx}", '-value' => 1, '-label' => ''),
 
 683       'valign'   => 'center',
 
 687     $row->{donumber}->{link}  = $edit_url       . "&id=" . E($dord->{id})      . "&callback=${callback}";
 
 688     $row->{ordnumber}->{link} = $edit_order_url . "&id=" . E($dord->{oe_id})   . "&callback=${callback}" if $dord->{oe_id};
 
 689     $report->add_data($row);
 
 694   $report->generate_with_headers();
 
 696   $main::lxdebug->leave_sub();
 
 700   $main::lxdebug->enter_sub();
 
 706   my $form     = $main::form;
 
 707   my %myconfig = %main::myconfig;
 
 708   my $locale   = $main::locale;
 
 710   $form->mtime_ischanged('delivery_orders');
 
 712   $form->{defaultcurrency} = $form->get_default_currency(\%myconfig);
 
 714   $form->isblank("transdate", $locale->text('Delivery Order Date missing!'));
 
 716   $form->{donumber} =~ s/^\s*//g;
 
 717   $form->{donumber} =~ s/\s*$//g;
 
 719   my $msg = ucfirst $form->{vc};
 
 720   $form->isblank($form->{vc}, $locale->text($msg . " missing!"));
 
 722   # $locale->text('Customer missing!');
 
 723   # $locale->text('Vendor missing!');
 
 725   remove_emptied_rows();
 
 728   # if the name changed get new values
 
 729   if (check_name($form->{vc})) {
 
 734   $form->{id} = 0 if $form->{saveasnew};
 
 738   if(!exists $form->{addition}) {
 
 739     $form->{snumbers} = qq|donumber_| . $form->{donumber};
 
 740     $form->{addition} = "SAVED";
 
 743   # /saving the history
 
 745   $form->{simple_save} = 1;
 
 746   if (!$params{no_redirect} && !$form->{print_and_save}) {
 
 747     delete @{$form}{ary_diff([keys %{ $form }], [qw(login id script type cursor_fokus)])};
 
 751   $main::lxdebug->leave_sub();
 
 755   $main::lxdebug->enter_sub();
 
 759   my $form     = $main::form;
 
 760   my %myconfig = %main::myconfig;
 
 761   my $locale   = $main::locale;
 
 765     if(!exists $form->{addition}) {
 
 766       $form->{snumbers} = qq|donumber_| . $form->{donumber};
 
 767       $form->{addition} = "DELETED";
 
 770     # /saving the history
 
 772     $form->info($locale->text('Delivery Order deleted!'));
 
 776   $form->error($locale->text('Cannot delete delivery order!'));
 
 778   $main::lxdebug->leave_sub();
 
 782   $main::lxdebug->enter_sub();
 
 784   my $form     = $main::form;
 
 785   my %myconfig = %main::myconfig;
 
 786   my $locale   = $main::locale;
 
 789   $form->mtime_ischanged('delivery_orders');
 
 791   $main::auth->assert($form->{type} eq 'purchase_delivery_order' ? 'vendor_invoice_edit' : 'invoice_edit');
 
 793   $form->{convert_from_do_ids} = $form->{id};
 
 794   $form->{deliverydate}        = $form->{transdate};
 
 795   $form->{transdate}           = $form->{invdate} = $form->current_date(\%myconfig);
 
 796   $form->{duedate}             = $form->current_date(\%myconfig, $form->{invdate}, $form->{terms} * 1);
 
 797   $form->{defaultcurrency}     = $form->get_default_currency(\%myconfig);
 
 801   delete @{$form}{qw(id closed delivered)};
 
 803   my ($script, $buysell);
 
 804   if ($form->{type} eq 'purchase_delivery_order') {
 
 805     $form->{title}  = $locale->text('Add Vendor Invoice');
 
 806     $form->{script} = 'ir.pl';
 
 811     $form->{title}  = $locale->text('Add Sales Invoice');
 
 812     $form->{script} = 'is.pl';
 
 817   for my $i (1 .. $form->{rowcount}) {
 
 819     unless ($form->{"ordnumber"}) {
 
 820       if ($form->{discount}) { # Falls wir einen Lieferanten-/Kundenrabatt haben
 
 821         # und rabattfähig sind, dann
 
 822         unless ($form->{"not_discountable_$i"}) {
 
 823           $form->{"discount_$i"} = $form->{discount}*100; # ... nehmen wir diesen Rabatt
 
 827     map { $form->{"${_}_${i}"} = $form->parse_amount(\%myconfig, $form->{"${_}_${i}"}) if $form->{"${_}_${i}"} } qw(ship qty sellprice lastcost basefactor);
 
 828     $form->{"donumber_$i"} = $form->{donumber};
 
 829     $form->{"converted_from_delivery_order_items_id_$i"} = delete $form->{"delivery_order_items_id_$i"};
 
 832   $form->{type} = "invoice";
 
 835   $main::locale = Locale->new("$myconfig{countrycode}", "$script");
 
 836   $locale = $main::locale;
 
 838   require "bin/mozilla/$form->{script}";
 
 840   my $currency = $form->{currency};
 
 843   if ($form->{ordnumber}) {
 
 844     require SL::DB::Order;
 
 845     if (my $order = SL::DB::Manager::Order->find_by(ordnumber => $form->{ordnumber})) {
 
 847       $form->{orddate} = $order->transdate_as_date;
 
 848       $form->{$_}      = $order->$_ for qw(payment_id salesman_id taxzone_id quonumber);
 
 852   $form->{currency}     = $currency;
 
 853   $form->{exchangerate} = "";
 
 854   $form->{forex}        = $form->check_exchangerate(\%myconfig, $form->{currency}, $form->{invdate}, $buysell);
 
 855   $form->{exchangerate} = $form->{forex} if ($form->{forex});
 
 860   for my $i (1 .. $form->{rowcount}) {
 
 861     $form->{"discount_$i"} = $form->format_amount(\%myconfig, $form->{"discount_$i"});
 
 863     my ($dec) = ($form->{"sellprice_$i"} =~ /\.(\d+)/);
 
 865     my $decimalplaces = ($dec > 2) ? $dec : 2;
 
 867     # copy delivery date from reqdate for order -> invoice conversion
 
 868     $form->{"deliverydate_$i"} = $form->{"reqdate_$i"}
 
 869       unless $form->{"deliverydate_$i"};
 
 872     $form->{"sellprice_$i"} =
 
 873       $form->format_amount(\%myconfig, $form->{"sellprice_$i"},
 
 876     $form->{"lastcost_$i"} =
 
 877       $form->format_amount(\%myconfig, $form->{"lastcost_$i"},
 
 880     (my $dec_qty) = ($form->{"qty_$i"} =~ /\.(\d+)/);
 
 881     $dec_qty = length $dec_qty;
 
 883       $form->format_amount(\%myconfig, $form->{"qty_$i"}, $dec_qty);
 
 889   $main::lxdebug->leave_sub();
 
 893   $main::lxdebug->enter_sub();
 
 895   my $form     = $main::form;
 
 896   my %myconfig = %main::myconfig;
 
 897   my $locale   = $main::locale;
 
 900   $main::auth->assert($form->{type} eq 'sales_delivery_order' ? 'invoice_edit' : 'vendor_invoice_edit');
 
 902   my @do_ids = map { $form->{"trans_id_$_"} } grep { $form->{"multi_id_$_"} } (1..$form->{rowcount});
 
 904   if (!scalar @do_ids) {
 
 905     $form->show_generic_error($locale->text('You have not selected any delivery order.'), 'back_button' => 1);
 
 908   map { delete $form->{$_} } grep { m/^(?:trans|multi)_id_\d+/ } keys %{ $form };
 
 910   if (!DO->retrieve('vc' => $form->{vc}, 'ids' => \@do_ids)) {
 
 911     $form->show_generic_error($form->{vc} eq 'customer' ?
 
 912                               $locale->text('You cannot create an invoice for delivery orders for different customers.') :
 
 913                               $locale->text('You cannot create an invoice for delivery orders from different vendors.'),
 
 917   my $source_type              = $form->{type};
 
 918   $form->{convert_from_do_ids} = join ' ', @do_ids;
 
 919   # bei der auswahl von mehreren Lieferscheinen fuer eine Rechnung, die einfach in donumber_array
 
 920   # zwischenspeichern (DO.pm) und als ' '-separierte Liste wieder zurueckschreiben
 
 921   # Hinweis: delete gibt den wert zurueck und loescht danach das element (nett und einfach)
 
 922   # $shell: perldoc perlunc; /delete EXPR
 
 923   $form->{donumber}            = delete $form->{donumber_array};
 
 924   $form->{ordnumber}           = delete $form->{ordnumber_array};
 
 925   $form->{cusordnumber}        = delete $form->{cusordnumber_array};
 
 926   $form->{deliverydate}        = $form->{transdate};
 
 927   $form->{transdate}           = $form->current_date(\%myconfig);
 
 928   $form->{duedate}             = $form->current_date(\%myconfig, $form->{invdate}, $form->{terms} * 1);
 
 929   $form->{type}                = "invoice";
 
 931   $form->{defaultcurrency}     = $form->get_default_currency(\%myconfig);
 
 933   my ($script, $buysell);
 
 934   if ($source_type eq 'purchase_delivery_order') {
 
 935     $form->{title}  = $locale->text('Add Vendor Invoice');
 
 936     $form->{script} = 'ir.pl';
 
 941     $form->{title}  = $locale->text('Add Sales Invoice');
 
 942     $form->{script} = 'is.pl';
 
 947   map { delete $form->{$_} } qw(id subject message cc bcc printed emailed queued);
 
 949   # get vendor or customer discount
 
 951   my $saved_form = save_form();
 
 952   if ($form->{vc} eq 'vendor') {
 
 953     IR->get_vendor(\%myconfig, \%$form);
 
 954     $vc_discount = $form->{vendor_discount};
 
 956     IS->get_customer(\%myconfig, \%$form);
 
 957     $vc_discount = $form->{customer_discount};
 
 959   restore_form($saved_form);
 
 961   $form->{rowcount} = 0;
 
 962   foreach my $ref (@{ $form->{form_details} }) {
 
 964     $ref->{reqdate} ||= $ref->{dord_transdate}; # copy transdates into each invoice row
 
 965     map { $form->{"${_}_$form->{rowcount}"} = $ref->{$_} } keys %{ $ref };
 
 966     map { $form->{"${_}_$form->{rowcount}"} = $form->format_amount(\%myconfig, $ref->{$_}) } qw(qty sellprice lastcost);
 
 967     $form->{"converted_from_delivery_order_items_id_$form->{rowcount}"} = delete $form->{"delivery_order_items_id_$form->{rowcount}"};
 
 969     if ($vc_discount){ # falls wir einen Lieferanten/Kundenrabatt haben
 
 970       # und keinen anderen discount wert an $i ...
 
 971       $form->{"discount_$form->{rowcount}"} ||= $vc_discount; # ... nehmen wir diesen Rabatt
 
 974     $form->{"discount_$form->{rowcount}"}   = $form->{"discount_$form->{rowcount}"}  * 100; #s.a. Bug 1151
 
 975     # Anm.: Eine Änderung des discounts in der SL/DO.pm->retrieve (select (doi.discount * 100) as discount) ergibt in psql einen
 
 976     # Wert von 10.0000001490116. Ferner ist der Rabatt in der Rechnung dann bei 1.0 (?). Deswegen lasse ich das hier. jb 10.10.09
 
 978     $form->{"discount_$form->{rowcount}"} = $form->format_amount(\%myconfig, $form->{"discount_$form->{rowcount}"});
 
 980   delete $form->{form_details};
 
 982   $locale = Locale->new("$myconfig{countrycode}", "$script");
 
 984   require "bin/mozilla/$form->{script}";
 
 991   $main::lxdebug->leave_sub();
 
 995   $main::lxdebug->enter_sub();
 
 999   my $form     = $main::form;
 
1001   $form->{saveasnew} = 1;
 
1002   $form->{closed}    = 0;
 
1003   $form->{delivered} = 0;
 
1004   map { delete $form->{$_} } qw(printed emailed queued);
 
1005   delete @{ $form }{ grep { m/^stock_(?:in|out)_\d+/ } keys %{ $form } };
 
1006   $form->{"converted_from_delivery_order_items_id_$_"} = delete $form->{"delivery_order_items_id_$_"} for 1 .. $form->{"rowcount"};
 
1007   # Let kivitendo assign a new order number if the user hasn't changed the
 
1008   # previous one. If it has been changed manually then use it as-is.
 
1009   $form->{donumber} =~ s/^\s*//g;
 
1010   $form->{donumber} =~ s/\s*$//g;
 
1011   if ($form->{saved_donumber} && ($form->{saved_donumber} eq $form->{donumber})) {
 
1012     delete($form->{donumber});
 
1017   $main::lxdebug->leave_sub();
 
1021   $main::lxdebug->enter_sub();
 
1025   $::form->mtime_ischanged('delivery_orders','mail');
 
1027   $::form->{print_and_save} = 1;
 
1029   my $saved_form = save_form();
 
1033   restore_form($saved_form, 0, qw(id ordnumber quonumber));
 
1037   $main::lxdebug->leave_sub();
 
1040 sub calculate_stock_in_out {
 
1041   $main::lxdebug->enter_sub();
 
1043   my $form     = $main::form;
 
1047   if (!$form->{"id_${i}"}) {
 
1048     $main::lxdebug->leave_sub();
 
1052   my $all_units = AM->retrieve_all_units();
 
1054   my $in_out   = $form->{type} =~ /^sales/ ? 'out' : 'in';
 
1055   my $sinfo    = DO->unpack_stock_information('packed' => $form->{"stock_${in_out}_${i}"});
 
1057   my $do_qty   = AM->sum_with_unit($::form->{"qty_$i"}, $::form->{"unit_$i"});
 
1058   my $sum      = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $sinfo });
 
1059   my $matches  = $do_qty == $sum;
 
1061   my $content  = $form->format_amount_units('amount'      => $sum * 1,
 
1062                                             'part_unit'   => $form->{"partunit_$i"},
 
1063                                             'amount_unit' => $all_units->{$form->{"partunit_$i"}}->{base_unit},
 
1064                                             'conv_units'  => 'convertible_not_smaller',
 
1066   $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="?">|;
 
1068   $main::lxdebug->leave_sub();
 
1073 sub get_basic_bin_wh_info {
 
1074   $main::lxdebug->enter_sub();
 
1076   my $stock_info = shift;
 
1078   my $form     = $main::form;
 
1080   foreach my $sinfo (@{ $stock_info }) {
 
1081     next unless ($sinfo->{bin_id});
 
1083     my $bin_info = WH->get_basic_bin_info('id' => $sinfo->{bin_id});
 
1084     map { $sinfo->{"${_}_description"} = $sinfo->{"${_}description"} = $bin_info->{"${_}_description"} } qw(bin warehouse);
 
1087   $main::lxdebug->leave_sub();
 
1090 sub stock_in_out_form {
 
1091   $main::lxdebug->enter_sub();
 
1093   my $form     = $main::form;
 
1095   if ($form->{in_out} eq 'out') {
 
1101   $main::lxdebug->leave_sub();
 
1104 sub redo_stock_info {
 
1105   $main::lxdebug->enter_sub();
 
1109   my $form     = $main::form;
 
1111   my @non_empty = grep { $_->{qty} } @{ $params{stock_info} };
 
1113   if ($params{add_empty_row}) {
 
1115       'warehouse_id' => scalar(@non_empty) ? $non_empty[-1]->{warehouse_id} : undef,
 
1116       'bin_id'       => scalar(@non_empty) ? $non_empty[-1]->{bin_id}       : undef,
 
1120   @{ $params{stock_info} } = @non_empty;
 
1122   $main::lxdebug->leave_sub();
 
1125 sub update_stock_in {
 
1126   $main::lxdebug->enter_sub();
 
1128   my $form     = $main::form;
 
1129   my %myconfig = %main::myconfig;
 
1131   my $stock_info = [];
 
1133   foreach my $i (1..$form->{rowcount}) {
 
1134     $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
 
1135     push @{ $stock_info }, { map { $_ => $form->{"${_}_${i}"} } qw(warehouse_id bin_id chargenumber
 
1136                                                                    bestbefore qty unit delivery_order_items_stock_id) };
 
1139   display_stock_in_form($stock_info);
 
1141   $main::lxdebug->leave_sub();
 
1145   $main::lxdebug->enter_sub();
 
1147   my $form     = $main::form;
 
1149   my $stock_info = DO->unpack_stock_information('packed' => $form->{stock});
 
1151   display_stock_in_form($stock_info);
 
1153   $main::lxdebug->leave_sub();
 
1156 sub display_stock_in_form {
 
1157   $main::lxdebug->enter_sub();
 
1159   my $stock_info = shift;
 
1161   my $form     = $main::form;
 
1162   my %myconfig = %main::myconfig;
 
1163   my $locale   = $main::locale;
 
1165   $form->{title} = $locale->text('Stock');
 
1167   my $part_info  = IC->get_basic_part_info('id' => $form->{parts_id});
 
1169   # Standardlagerplatz für Standard-Auslagern verwenden, falls keiner für die Ware explizit definiert wurde
 
1170   if ($::instance_conf->get_transfer_default_use_master_default_bin) {
 
1171     $part_info->{warehouse_id} ||= $::instance_conf->get_warehouse_id;
 
1172     $part_info->{bin_id}       ||= $::instance_conf->get_bin_id;
 
1175   my $units      = AM->retrieve_units(\%myconfig, $form);
 
1176   # der zweite Parameter von unit_select_data gibt den default-Namen (selected) vor
 
1177   my $units_data = AM->unit_select_data($units, $form->{do_unit}, undef, $part_info->{unit});
 
1179   $form->get_lists('warehouses' => { 'key'    => 'WAREHOUSES',
 
1180                                      'bins'   => 'BINS' });
 
1182   redo_stock_info('stock_info' => $stock_info, 'add_empty_row' => !$form->{delivered});
 
1184   get_basic_bin_wh_info($stock_info);
 
1186   $form->header(no_layout => 1);
 
1187   print $form->parse_html_template('do/stock_in_form', { 'UNITS'      => $units_data,
 
1188                                                          'STOCK_INFO' => $stock_info,
 
1189                                                          'PART_INFO'  => $part_info, });
 
1191   $main::lxdebug->leave_sub();
 
1194 sub _stock_in_out_set_qty_display {
 
1195   my $stock_info       = shift;
 
1197   my $all_units        = AM->retrieve_all_units();
 
1198   my $sum              = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $stock_info });
 
1199   $form->{qty_display} = $form->format_amount_units(amount      => $sum * 1,
 
1200                                                     part_unit   => $form->{partunit},
 
1201                                                     amount_unit => $all_units->{ $form->{partunit} }->{base_unit},
 
1202                                                     conv_units  => 'convertible_not_smaller',
 
1207   $main::lxdebug->enter_sub();
 
1209   my $form     = $main::form;
 
1210   my %myconfig = %main::myconfig;
 
1212   my $stock_info = [];
 
1214   foreach my $i (1..$form->{rowcount}) {
 
1215     $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
 
1217     next if ($form->{"qty_$i"} <= 0);
 
1219     push @{ $stock_info }, { map { $_ => $form->{"${_}_${i}"} } qw(delivery_order_items_stock_id warehouse_id bin_id chargenumber bestbefore qty unit) };
 
1222   $form->{stock} = YAML::Dump($stock_info);
 
1224   _stock_in_out_set_qty_display($stock_info);
 
1226   my $do_qty       = AM->sum_with_unit($::form->parse_amount(\%::myconfig, $::form->{do_qty}), $::form->{do_unit});
 
1227   my $transfer_qty = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $stock_info });
 
1230   print $form->parse_html_template('do/set_stock_in_out', {
 
1231     qty_matches => $do_qty == $transfer_qty,
 
1234   $main::lxdebug->leave_sub();
 
1237 sub stock_out_form {
 
1238   $main::lxdebug->enter_sub();
 
1240   my $form     = $main::form;
 
1241   my %myconfig = %main::myconfig;
 
1242   my $locale   = $main::locale;
 
1244   $form->{title} = $locale->text('Release From Stock');
 
1246   my $part_info  = IC->get_basic_part_info('id' => $form->{parts_id});
 
1248   my $units      = AM->retrieve_units(\%myconfig, $form);
 
1249   my $units_data = AM->unit_select_data($units, undef, undef, $part_info->{unit});
 
1251   my @contents   = DO->get_item_availability('parts_id' => $form->{parts_id});
 
1253   my $stock_info = DO->unpack_stock_information('packed' => $form->{stock});
 
1255   if (!$form->{delivered}) {
 
1256     foreach my $row (@contents) {
 
1257       $row->{available_qty} = $form->format_amount_units('amount'      => $row->{qty} * 1,
 
1258                                                          'part_unit'   => $part_info->{unit},
 
1259                                                          'conv_units'  => 'convertible_not_smaller',
 
1262       foreach my $sinfo (@{ $stock_info }) {
 
1263         next if (($row->{bin_id}       != $sinfo->{bin_id}) ||
 
1264                  ($row->{warehouse_id} != $sinfo->{warehouse_id}) ||
 
1265                  ($row->{chargenumber} ne $sinfo->{chargenumber}) ||
 
1266                  ($row->{bestbefore}   ne $sinfo->{bestbefore}));
 
1268         map { $row->{"stock_$_"} = $sinfo->{$_} } qw(qty unit error delivery_order_items_stock_id);
 
1273     get_basic_bin_wh_info($stock_info);
 
1275     foreach my $sinfo (@{ $stock_info }) {
 
1276       map { $sinfo->{"stock_$_"} = $sinfo->{$_} } qw(qty unit);
 
1280   $form->header(no_layout => 1);
 
1281   print $form->parse_html_template('do/stock_out_form', { 'UNITS'      => $units_data,
 
1282                                                           'WHCONTENTS' => $form->{delivered} ? $stock_info : \@contents,
 
1283                                                           'PART_INFO'  => $part_info, });
 
1285   $main::lxdebug->leave_sub();
 
1289   $main::lxdebug->enter_sub();
 
1291   my $form     = $main::form;
 
1292   my %myconfig = %main::myconfig;
 
1293   my $locale   = $main::locale;
 
1295   my $stock_info = [];
 
1297   foreach my $i (1 .. $form->{rowcount}) {
 
1298     $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
 
1300     next if ($form->{"qty_$i"} <= 0);
 
1302     push @{ $stock_info }, {
 
1303       'warehouse_id' => $form->{"warehouse_id_$i"},
 
1304       'bin_id'       => $form->{"bin_id_$i"},
 
1305       'chargenumber' => $form->{"chargenumber_$i"},
 
1306       'bestbefore'   => $form->{"bestbefore_$i"},
 
1307       'qty'          => $form->{"qty_$i"},
 
1308       'unit'         => $form->{"unit_$i"},
 
1310       'delivery_order_items_stock_id'  => $form->{"delivery_order_items_stock_id_$i"},
 
1314   my @errors     = DO->check_stock_availability('requests' => $stock_info,
 
1315                                                 'parts_id' => $form->{parts_id});
 
1317   $form->{stock} = YAML::Dump($stock_info);
 
1320     $form->{ERRORS} = [];
 
1321     map { push @{ $form->{ERRORS} }, $locale->text('Error in row #1: The quantity you entered is bigger than the stocked quantity.', $_->{row}); } @errors;
 
1322     stock_in_out_form();
 
1325     _stock_in_out_set_qty_display($stock_info);
 
1327     my $do_qty       = AM->sum_with_unit($::form->parse_amount(\%::myconfig, $::form->{do_qty}), $::form->{do_unit});
 
1328     my $transfer_qty = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $stock_info });
 
1331     print $form->parse_html_template('do/set_stock_in_out', {
 
1332       qty_matches => $do_qty == $transfer_qty,
 
1336   $main::lxdebug->leave_sub();
 
1340   $main::lxdebug->enter_sub();
 
1342   my $form     = $main::form;
 
1343   my %myconfig = %main::myconfig;
 
1344   my $locale   = $main::locale;
 
1346   if ($form->{id} && DO->is_marked_as_delivered(id => $form->{id})) {
 
1347     $form->show_generic_error($locale->text('The parts for this delivery order have already been transferred in.'), 'back_button' => 1);
 
1350   save(no_redirect => 1);
 
1352   my @part_ids = map { $form->{"id_${_}"} } grep { $form->{"id_${_}"} && $form->{"stock_in_${_}"} } (1 .. $form->{rowcount});
 
1356     my $units         = AM->retrieve_units(\%myconfig, $form);
 
1357     my %part_info_map = IC->get_basic_part_info('id' => \@part_ids);
 
1360     $form->{ERRORS}   = [];
 
1362     foreach my $i (1 .. $form->{rowcount}) {
 
1363       next unless ($form->{"id_$i"} && $form->{"stock_in_$i"});
 
1365       my $row_sum_base_qty = 0;
 
1366       my $base_unit_factor = $units->{ $part_info_map{$form->{"id_$i"}}->{unit} }->{factor} || 1;
 
1368       foreach my $request (@{ DO->unpack_stock_information('packed' => $form->{"stock_in_$i"}) }) {
 
1369         $request->{parts_id}  = $form->{"id_$i"};
 
1370         $row_sum_base_qty    += $request->{qty} * $units->{$request->{unit}}->{factor} / $base_unit_factor;
 
1372         $request->{project_id} = $form->{"project_id_$i"} || $form->{globalproject_id};
 
1374         push @all_requests, $request;
 
1377       next if (0 == $row_sum_base_qty);
 
1379       my $do_base_qty = $form->parse_amount(\%myconfig, $form->{"qty_$i"}) * $units->{$form->{"unit_$i"}}->{factor} / $base_unit_factor;
 
1381 #      if ($do_base_qty != $row_sum_base_qty) {
 
1382 #        push @{ $form->{ERRORS} }, $locale->text('Error in position #1: You must either assign no stock at all or the full quantity of #2 #3.',
 
1383 #                                                 $i, $form->{"qty_$i"}, $form->{"unit_$i"});
 
1387     if (@{ $form->{ERRORS} }) {
 
1388       push @{ $form->{ERRORS} }, $locale->text('The delivery order has not been marked as delivered. The warehouse contents have not changed.');
 
1390       set_headings('edit');
 
1392       $main::lxdebug->leave_sub();
 
1398   DO->transfer_in_out('direction' => 'in',
 
1399                       'requests'  => \@all_requests);
 
1401   SL::DB::DeliveryOrder->new(id => $form->{id})->load->update_attributes(delivered => 1);
 
1403   $form->{callback} = 'do.pl?action=edit&type=purchase_delivery_order&id=' . $form->escape($form->{id});
 
1406   $main::lxdebug->leave_sub();
 
1410   $main::lxdebug->enter_sub();
 
1412   my $form     = $main::form;
 
1413   my %myconfig = %main::myconfig;
 
1414   my $locale   = $main::locale;
 
1416   if ($form->{id} && DO->is_marked_as_delivered(id => $form->{id})) {
 
1417     $form->show_generic_error($locale->text('The parts for this delivery order have already been transferred out.'), 'back_button' => 1);
 
1420   save(no_redirect => 1);
 
1422   my @part_ids = map { $form->{"id_${_}"} } grep { $form->{"id_${_}"} && $form->{"stock_out_${_}"} } (1 .. $form->{rowcount});
 
1426     my $units         = AM->retrieve_units(\%myconfig, $form);
 
1427     my %part_info_map = IC->get_basic_part_info('id' => \@part_ids);
 
1430     $form->{ERRORS}   = [];
 
1432     foreach my $i (1 .. $form->{rowcount}) {
 
1433       next unless ($form->{"id_$i"} && $form->{"stock_out_$i"});
 
1435       my $row_sum_base_qty = 0;
 
1436       my $base_unit_factor = $units->{ $part_info_map{$form->{"id_$i"}}->{unit} }->{factor} || 1;
 
1438       foreach my $request (@{ DO->unpack_stock_information('packed' => $form->{"stock_out_$i"}) }) {
 
1439         $request->{parts_id} = $form->{"id_$i"};
 
1440         $request->{base_qty} = $request->{qty} * $units->{$request->{unit}}->{factor} / $base_unit_factor;
 
1441         $request->{project_id} = $form->{"project_id_$i"} ? $form->{"project_id_$i"} : $form->{globalproject_id};
 
1443         my $map_key          = join '--', ($form->{"id_$i"}, @{$request}{qw(warehouse_id bin_id chargenumber bestbefore)});
 
1445         $request_map{$map_key}                 ||= $request;
 
1446         $request_map{$map_key}->{sum_base_qty} ||= 0;
 
1447         $request_map{$map_key}->{sum_base_qty}  += $request->{base_qty};
 
1448         $row_sum_base_qty                       += $request->{base_qty};
 
1450         push @all_requests, $request;
 
1453       next if (0 == $row_sum_base_qty);
 
1455       my $do_base_qty = $form->{"qty_$i"} * $units->{$form->{"unit_$i"}}->{factor} / $base_unit_factor;
 
1457 #      if ($do_base_qty != $row_sum_base_qty) {
 
1458 #        push @{ $form->{ERRORS} }, $locale->text('Error in position #1: You must either assign no transfer at all or the full quantity of #2 #3.',
 
1459 #                                                 $i, $form->{"qty_$i"}, $form->{"unit_$i"});
 
1464       my @bin_ids      = map { $_->{bin_id} } values %request_map;
 
1465       my %bin_info_map = WH->get_basic_bin_info('id' => \@bin_ids);
 
1466       my @contents     = DO->get_item_availability('parts_id' => \@part_ids);
 
1468       foreach my $inv (@contents) {
 
1469         my $map_key = join '--', @{$inv}{qw(parts_id warehouse_id bin_id chargenumber bestbefore)};
 
1471         next unless ($request_map{$map_key});
 
1473         my $request    = $request_map{$map_key};
 
1474         $request->{ok} = $request->{sum_base_qty} <= $inv->{qty};
 
1477       foreach my $request (values %request_map) {
 
1478         next if ($request->{ok});
 
1480         my $pinfo = $part_info_map{$request->{parts_id}};
 
1481         my $binfo = $bin_info_map{$request->{bin_id}};
 
1483         if ($::instance_conf->get_show_bestbefore) {
 
1484             push @{ $form->{ERRORS} }, $locale->text("There is not enough available of '#1' at warehouse '#2', bin '#3', #4, #5, for the transfer of #6.",
 
1485                                                      $pinfo->{description},
 
1486                                                      $binfo->{warehouse_description},
 
1487                                                      $binfo->{bin_description},
 
1488                                                      $request->{chargenumber} ? $locale->text('chargenumber #1', $request->{chargenumber}) : $locale->text('no chargenumber'),
 
1489                                                      $request->{bestbefore} ? $locale->text('bestbefore #1', $request->{bestbefore}) : $locale->text('no bestbefore'),
 
1490                                                      $form->format_amount_units('amount'      => $request->{sum_base_qty},
 
1491                                                                                 'part_unit'   => $pinfo->{unit},
 
1492                                                                                 'conv_units'  => 'convertible_not_smaller'));
 
1494             push @{ $form->{ERRORS} }, $locale->text("There is not enough available of '#1' at warehouse '#2', bin '#3', #4, for the transfer of #5.",
 
1495                                                      $pinfo->{description},
 
1496                                                      $binfo->{warehouse_description},
 
1497                                                      $binfo->{bin_description},
 
1498                                                      $request->{chargenumber} ? $locale->text('chargenumber #1', $request->{chargenumber}) : $locale->text('no chargenumber'),
 
1499                                                      $form->format_amount_units('amount'      => $request->{sum_base_qty},
 
1500                                                                                 'part_unit'   => $pinfo->{unit},
 
1501                                                                                 'conv_units'  => 'convertible_not_smaller'));
 
1506     if (@{ $form->{ERRORS} }) {
 
1507       push @{ $form->{ERRORS} }, $locale->text('The delivery order has not been marked as delivered. The warehouse contents have not changed.');
 
1509       set_headings('edit');
 
1511       $main::lxdebug->leave_sub();
 
1516   DO->transfer_in_out('direction' => 'out',
 
1517                       'requests'  => \@all_requests);
 
1519   SL::DB::DeliveryOrder->new(id => $form->{id})->load->update_attributes(delivered => 1);
 
1521   $form->{callback} = 'do.pl?action=edit&type=sales_delivery_order&id=' . $form->escape($form->{id});
 
1524   $main::lxdebug->leave_sub();
 
1528   $main::lxdebug->enter_sub();
 
1530   my $form     = $main::form;
 
1532   DO->close_orders('ids' => [ $form->{id} ]);
 
1534   $form->{closed} = 1;
 
1538   $main::lxdebug->leave_sub();
 
1543   call_sub($main::form->{yes_nextsub});
 
1547   call_sub($main::form->{no_nextsub});
 
1551   call_sub($main::form->{update_nextsub} || $main::form->{nextsub} || 'update_delivery_order');
 
1555   my $form     = $main::form;
 
1556   my $locale   = $main::locale;
 
1558   foreach my $action (qw(update ship_to print e_mail save transfer_out transfer_out_default sort
 
1559                          transfer_in transfer_in_default mark_closed save_as_new invoice delete)) {
 
1560     if ($form->{"action_${action}"}) {
 
1566   $form->error($locale->text('No action defined.'));
 
1569 sub transfer_out_default {
 
1570   $main::lxdebug->enter_sub();
 
1572   my $form     = $main::form;
 
1574   transfer_in_out_default('direction' => 'out');
 
1576   $main::lxdebug->leave_sub();
 
1579 sub transfer_in_default {
 
1580   $main::lxdebug->enter_sub();
 
1582   my $form     = $main::form;
 
1584   transfer_in_out_default('direction' => 'in');
 
1586   $main::lxdebug->leave_sub();
 
1589 # Falls das Standardlagerverfahren aktiv ist, wird
 
1590 # geprüft, ob alle Standardlagerplätze für die Auslager-
 
1591 # artikel vorhanden sind UND ob die Warenmenge ausreicht zum
 
1592 # Auslagern. Falls nicht wird entsprechend eine Fehlermeldung
 
1593 # generiert. Offen Chargennummer / bestbefore wird nicht berücksichtigt
 
1594 sub transfer_in_out_default {
 
1595   $main::lxdebug->enter_sub();
 
1597   my $form     = $main::form;
 
1598   my %myconfig = %main::myconfig;
 
1599   my $locale   = $main::locale;
 
1602   my (%missing_default_bins, %qty_parts, @all_requests, %part_info_map, $default_warehouse_id, $default_bin_id);
 
1604   Common::check_params(\%params, qw(direction));
 
1606   # entsprechende defaults holen, falls standardlagerplatz verwendet werden soll
 
1607   if ($::instance_conf->get_transfer_default_use_master_default_bin) {
 
1608     $default_warehouse_id = $::instance_conf->get_warehouse_id;
 
1609     $default_bin_id       = $::instance_conf->get_bin_id;
 
1613   my @part_ids = map { $form->{"id_${_}"} } (1 .. $form->{rowcount});
 
1615     my $units         = AM->retrieve_units(\%myconfig, $form);
 
1616     %part_info_map = IC->get_basic_part_info('id' => \@part_ids);
 
1617     foreach my $i (1 .. $form->{rowcount}) {
 
1618       next unless ($form->{"id_$i"});
 
1619       my $base_unit_factor = $units->{ $part_info_map{$form->{"id_$i"}}->{unit} }->{factor} || 1;
 
1620       my $qty =   $form->parse_amount(\%myconfig, $form->{"qty_$i"}) * $units->{$form->{"unit_$i"}}->{factor} / $base_unit_factor;
 
1622       $form->show_generic_error($locale->text("Cannot transfer negative entries." ), 'back_button' => 1) if ($qty < 0);
 
1623       # if we do not want to transfer services and this part is a service, set qty to zero
 
1624       # ... and do not create a hash entry in %qty_parts below (will skip check for bins for the transfer == out case)
 
1625       # ... 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)
 
1627       $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});
 
1628       $qty_parts{$form->{"id_$i"}} += $qty;
 
1630         delete $qty_parts{$form->{"id_$i"}} unless $qty_parts{$form->{"id_$i"}};
 
1631         undef $form->{"stock_in_$i"};
 
1634       $part_info_map{$form->{"id_$i"}}{bin_id}       ||= $default_bin_id;
 
1635       $part_info_map{$form->{"id_$i"}}{warehouse_id} ||= $default_warehouse_id;
 
1637       push @all_requests, ($qty == 0) ? { } : {
 
1638                         'chargenumber' => '',  #?? die müsste entsprechend geholt werden
 
1639                         #'bestbefore' => undef, # TODO wird nicht berücksichtigt
 
1640                         'bin_id' => $part_info_map{$form->{"id_$i"}}{bin_id},
 
1642                         'parts_id' => $form->{"id_$i"},
 
1643                         'comment' => $locale->text("Default transfer delivery order"),
 
1644                         'unit' => $part_info_map{$form->{"id_$i"}}{unit},
 
1645                         'warehouse_id' => $part_info_map{$form->{"id_$i"}}{warehouse_id},
 
1646                         'oe_id' => $form->{id},
 
1647                         'project_id' => $form->{"project_id_$i"} ? $form->{"project_id_$i"} : $form->{globalproject_id}
 
1651     # jetzt wird erst überprüft, ob die Stückzahl entsprechend stimmt.
 
1652     # check if bin (transfer in and transfer out and qty (transfer out) is correct
 
1653     foreach my $key (keys %qty_parts) {
 
1655       $missing_default_bins{$key}{missing_bin} = 1 unless ($part_info_map{$key}{bin_id});
 
1656       next unless ($part_info_map{$key}{bin_id}); # abbruch
 
1658       if ($params{direction} eq 'out') {  # wird nur für ausgehende Mengen benötigt
 
1659         my ($max_qty, $error) = WH->get_max_qty_parts_bin(parts_id => $key, bin_id => $part_info_map{$key}{bin_id});
 
1661           # wir können nicht entscheiden, welche charge oder mhd (bestbefore) ausgewählt sein soll
 
1662           # deshalb rückmeldung nach oben geben, manuell auszulagern
 
1663           # TODO Bei nur einem Treffer mit Charge oder bestbefore wäre das noch möglich
 
1664           $missing_default_bins{$key}{chargenumber} = 1;
 
1666         if ($max_qty < $qty_parts{$key}){
 
1667           $missing_default_bins{$key}{missing_qty} = $max_qty - $qty_parts{$key};
 
1673   # Abfrage für Fehlerbehandlung (nur bei direction == out)
 
1674   if (scalar (keys %missing_default_bins)) {
 
1676     foreach my $fehler (keys %missing_default_bins) {
 
1678       my $ware = WH->get_part_description(parts_id => $fehler);
 
1679       if ($missing_default_bins{$fehler}{missing_bin}){
 
1680         $fehlertext .= "Kein Standardlagerplatz definiert bei $ware <br>";
 
1682       if ($missing_default_bins{$fehler}{missing_qty}) {  # missing_qty
 
1683         $fehlertext .= "Es fehlen " . $missing_default_bins{$fehler}{missing_qty}*-1 .
 
1684                        " von $ware auf dem Standard-Lagerplatz " . $part_info_map{$fehler}{bin} .   " zum Auslagern<br>";
 
1686       if ($missing_default_bins{$fehler}{chargenumber}){
 
1687         $fehlertext .= "Die Ware hat eine Chargennummer oder eine Mindesthaltbarkeit definiert.
 
1688                         Hier kann man nicht automatisch entscheiden.
 
1689                         Bitte diesen Lieferschein manuell auslagern.
 
1692       # auslagern soll immer gehen, auch wenn nicht genügend auf lager ist.
 
1693       # der lagerplatz ist hier extra konfigurierbar, bspw. Lager-Korrektur mit
 
1694       # Lagerplatz Lagerplatz-Korrektur
 
1695       my $default_warehouse_id_ignore_onhand = $::instance_conf->get_warehouse_id_ignore_onhand;
 
1696       my $default_bin_id_ignore_onhand       = $::instance_conf->get_bin_id_ignore_onhand;
 
1697       if ($::instance_conf->get_transfer_default_ignore_onhand && $default_bin_id_ignore_onhand) {
 
1698         # entsprechende defaults holen
 
1699         # falls chargenumber, bestbefore oder anzahl nicht stimmt, auf automatischen
 
1700         # lagerplatz wegbuchen!
 
1701         foreach (@all_requests) {
 
1702           if ($_->{parts_id} eq $fehler){
 
1703           $_->{bin_id}        = $default_bin_id_ignore_onhand;
 
1704           $_->{warehouse_id}  = $default_warehouse_id_ignore_onhand;
 
1708         #$main::lxdebug->message(0, 'Fehlertext: ' . $fehlertext);
 
1709         $form->show_generic_error($locale->text("Cannot transfer. <br> Reason:<br>#1", $fehlertext ), 'back_button' => 1);
 
1715   # hier der eigentliche fallunterschied für in oder out
 
1716   my $prefix   = $params{direction} eq 'in' ? 'in' : 'out';
 
1718   # dieser array_ref ist für DO->save da:
 
1719   # einmal die all_requests in YAML verwandeln, damit delivery_order_items_stock
 
1720   # gefüllt werden kann.
 
1721   # could be dumped to the form in the first loop,
 
1722   # but maybe bin_id and warehouse_id has changed to the "korrekturlager" with
 
1723   # allowed negative qty ($::instance_conf->get_warehouse_id_ignore_onhand) ...
 
1725   foreach (@all_requests){
 
1727     next unless scalar(%{ $_ });
 
1728     $form->{"stock_${prefix}_$i"} = YAML::Dump([$_]);
 
1731   save(no_redirect => 1); # Wir können auslagern, deshalb beleg speichern
 
1732                           # und in delivery_order_items_stock speichern
 
1734   # ... and fill back the persistent dois_id for inventory fk
 
1735   undef (@all_requests);
 
1736   foreach my $i (1 .. $form->{rowcount}) {
 
1737     next unless ($form->{"id_$i"} && $form->{"stock_${prefix}_$i"});
 
1738     push @all_requests, @{ DO->unpack_stock_information('packed' => $form->{"stock_${prefix}_$i"}) };
 
1740   DO->transfer_in_out('direction' => $prefix,
 
1741                       'requests'  => \@all_requests);
 
1743   SL::DB::DeliveryOrder->new(id => $form->{id})->load->update_attributes(delivered => 1);
 
1745   $form->{callback} = 'do.pl?action=edit&type=sales_delivery_order&id=' . $form->escape($form->{id}) if $params{direction} eq 'out';
 
1746   $form->{callback} = 'do.pl?action=edit&type=purchase_delivery_order&id=' . $form->escape($form->{id}) if $params{direction} eq 'in';
 
1752   $main::lxdebug->enter_sub();
 
1756   my $form     = $main::form;
 
1759   croak ("Delivery Order needs to be saved") unless $form->{id};
 
1761   # hashify partnumbers, positions. key is delivery_order_items_id
 
1762   for my $i (1 .. ($form->{rowcount}) ) {
 
1763     $temp_hash{$form->{"delivery_order_items_id_$i"}} = { runningnumber => $form->{"runningnumber_$i"}, partnumber => $form->{"partnumber_$i"} };
 
1765   # naturally sort partnumbers and get a sorted array of doi_ids
 
1766   my @sorted_doi_ids =  sort { Sort::Naturally::ncmp($temp_hash{$a}->{"partnumber"}, $temp_hash{$b}->{"partnumber"}) }  keys %temp_hash;
 
1771   for (@sorted_doi_ids) {
 
1772     $form->{"runningnumber_$temp_hash{$_}->{runningnumber}"} = $new_number;
 
1775     $main::lxdebug->leave_sub();
 
1787 do.pl - Script for all calls to delivery order
 
1796 Sorts all position with Natural Sort. Can be activated in form_footer.html like this
 
1797 C<E<lt>input class="submit" type="submit" name="action_sort" id="sort_button" value="[% 'Sort and Save' | $T8 %]"E<gt>>
 
1803 Sort and Save can be implemented as an optional button if configuration ca be set by client config.
 
1804 Example coding for database scripts and templates in (git show af2f24b8), check also
 
1805 autogeneration for rose (scripts/rose_auto_create_model.pl --h)