fix FSF address
[kivitendo-erp.git] / bin / mozilla / do.pl
1 #=====================================================================
2 # LX-Office ERP
3 # Copyright (C) 2004
4 # Based on SQL-Ledger Version 2.1.9
5 # Web http://www.lx-office.org
6 #
7 #=====================================================================
8 # SQL-Ledger, Accounting
9 # Copyright (c) 1998-2003
10 #
11 #  Author: Dieter Simader
12 #   Email: dsimader@sql-ledger.org
13 #     Web: http://www.sql-ledger.org
14 #
15 #
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.
20 #
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,
28 # MA 02110-1335, USA.
29 #======================================================================
30 #
31 # Delivery orders
32 #======================================================================
33
34 use Carp;
35 use List::MoreUtils qw(uniq);
36 use List::Util qw(max sum);
37 use POSIX qw(strftime);
38 use YAML;
39
40 use SL::DB::DeliveryOrder;
41 use SL::DO;
42 use SL::IR;
43 use SL::IS;
44 use SL::MoreCommon qw(ary_diff restore_form save_form);
45 use SL::ReportGenerator;
46 use SL::WH;
47 use Sort::Naturally ();
48 require "bin/mozilla/arap.pl";
49 require "bin/mozilla/common.pl";
50 require "bin/mozilla/io.pl";
51 require "bin/mozilla/reportgenerator.pl";
52
53 use strict;
54
55 1;
56
57 # end of main
58
59 sub check_do_access {
60   $main::auth->assert($main::form->{type} . '_edit');
61 }
62
63 sub set_headings {
64   $main::lxdebug->enter_sub();
65
66   check_do_access();
67
68   my ($action) = @_;
69
70   my $form     = $main::form;
71   my $locale   = $main::locale;
72
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');
76   } else {
77     $form->{vc}    = 'customer';
78     $form->{title} = $action eq "edit" ? $locale->text('Edit Sales Delivery Order') : $locale->text('Add Sales Delivery Order');
79   }
80
81   $form->{heading} = $locale->text('Delivery Order');
82
83   $main::lxdebug->leave_sub();
84 }
85
86 sub add {
87   $main::lxdebug->enter_sub();
88
89   check_do_access();
90
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."));
93   }
94
95   my $form     = $main::form;
96
97   set_headings("add");
98
99   $form->{show_details} = $::myconfig{show_form_details};
100   $form->{callback} = build_std_url('action=add', 'type', 'vc') unless ($form->{callback});
101
102   order_links();
103   prepare_order();
104   display_form();
105
106   $main::lxdebug->leave_sub();
107 }
108
109 sub edit {
110   $main::lxdebug->enter_sub();
111
112   check_do_access();
113
114   my $form     = $main::form;
115
116   $form->{show_details} = $::myconfig{show_form_details};
117
118   # show history button
119   $form->{javascript} = qq|<script type="text/javascript" src="js/show_history.js"></script>|;
120   #/show hhistory button
121
122   $form->{simple_save} = 0;
123
124   set_headings("edit");
125
126   # editing without stuff to edit? try adding it first
127   if ($form->{rowcount} && !$form->{print_and_save}) {
128 #     map { $id++ if $form->{"multi_id_$_"} } (1 .. $form->{rowcount});
129 #     if (!$id) {
130
131       # reset rowcount
132       undef $form->{rowcount};
133       add();
134       $main::lxdebug->leave_sub();
135       return;
136 #     }
137   } elsif (!$form->{id}) {
138     add();
139     $main::lxdebug->leave_sub();
140     return;
141   }
142
143   my ($language_id, $printer_id);
144   if ($form->{print_and_save}) {
145     $form->{action}   = "dispatcher";
146     $form->{action_print} = "1";
147     $form->{resubmit} = 1;
148     $language_id      = $form->{language_id};
149     $printer_id       = $form->{printer_id};
150   }
151
152   set_headings("edit");
153
154   order_links();
155   prepare_order();
156
157   if ($form->{print_and_save}) {
158     $form->{language_id} = $language_id;
159     $form->{printer_id}  = $printer_id;
160   }
161
162   display_form();
163
164   $main::lxdebug->leave_sub();
165 }
166
167 sub order_links {
168   $main::lxdebug->enter_sub();
169
170   check_do_access();
171
172   my $form     = $main::form;
173   my %myconfig = %main::myconfig;
174
175   # get customer/vendor
176   $form->all_vc(\%myconfig, $form->{vc}, ($form->{vc} eq 'customer') ? "AR" : "AP");
177
178   # retrieve order/quotation
179   my $editing = $form->{id};
180
181   DO->retrieve('vc'  => $form->{vc},
182                'ids' => $form->{id});
183
184   $form->backup_vars(qw(payment_id language_id taxzone_id salesman_id taxincluded cp_id intnotes delivery_term_id currency));
185
186   # get customer / vendor
187   if ($form->{vc} eq 'vendor') {
188     IR->get_vendor(\%myconfig, \%$form);
189     $form->{discount} = $form->{vendor_discount};
190   } else {
191     IS->get_customer(\%myconfig, \%$form);
192     $form->{discount} = $form->{customer_discount};
193   }
194
195   $form->restore_vars(qw(payment_id language_id taxzone_id intnotes cp_id delivery_term_id));
196   $form->restore_vars(qw(currency)) if ($form->{id} || $form->{convert_from_oe_ids});
197   $form->restore_vars(qw(taxincluded)) if $form->{id};
198   $form->restore_vars(qw(salesman_id)) if $editing;
199
200   if ($form->{"all_$form->{vc}"}) {
201     unless ($form->{"$form->{vc}_id"}) {
202       $form->{"$form->{vc}_id"} = $form->{"all_$form->{vc}"}->[0]->{id};
203     }
204   }
205
206   ($form->{ $form->{vc} })  = split /--/, $form->{ $form->{vc} };
207   $form->{"old$form->{vc}"} = qq|$form->{$form->{vc}}--$form->{"$form->{vc}_id"}|;
208
209   $form->{employee} = "$form->{employee}--$form->{employee_id}";
210
211   $main::lxdebug->leave_sub();
212 }
213
214 sub prepare_order {
215   $main::lxdebug->enter_sub();
216
217   check_do_access();
218
219   my $form     = $main::form;
220   my %myconfig = %main::myconfig;
221
222   $form->{formname} = $form->{type} unless $form->{formname};
223
224   my $i = 0;
225   foreach my $ref (@{ $form->{form_details} }) {
226     $form->{rowcount} = ++$i;
227
228     map { $form->{"${_}_$i"} = $ref->{$_} } keys %{$ref};
229   }
230   for my $i (1 .. $form->{rowcount}) {
231     if ($form->{id}) {
232       $form->{"discount_$i"} = $form->format_amount(\%myconfig, $form->{"discount_$i"} * 100);
233     } else {
234       $form->{"discount_$i"} = $form->format_amount(\%myconfig, $form->{"discount_$i"});
235     }
236     my ($dec) = ($form->{"sellprice_$i"} =~ /\.(\d+)/);
237     $dec           = length $dec;
238     my $decimalplaces = ($dec > 2) ? $dec : 2;
239
240     # copy reqdate from deliverydate for invoice -> order conversion
241     $form->{"reqdate_$i"} = $form->{"deliverydate_$i"} unless $form->{"reqdate_$i"};
242
243     $form->{"sellprice_$i"} = $form->format_amount(\%myconfig, $form->{"sellprice_$i"}, $decimalplaces);
244     $form->{"lastcost_$i"} = $form->format_amount(\%myconfig, $form->{"lastcost_$i"}, $decimalplaces);
245
246     (my $dec_qty) = ($form->{"qty_$i"} =~ /\.(\d+)/);
247     $dec_qty = length $dec_qty;
248     $form->{"qty_$i"} = $form->format_amount(\%myconfig, $form->{"qty_$i"}, $dec_qty);
249   }
250
251   $main::lxdebug->leave_sub();
252 }
253
254 sub form_header {
255   $main::lxdebug->enter_sub();
256
257   check_do_access();
258
259   my $form     = $main::form;
260   my %myconfig = %main::myconfig;
261
262   $form->{employee_id} = $form->{old_employee_id} if $form->{old_employee_id};
263   $form->{salesman_id} = $form->{old_salesman_id} if $form->{old_salesman_id};
264
265   my $vc = $form->{vc} eq "customer" ? "customers" : "vendors";
266   $form->get_lists($vc              => "ALL_VC",
267                    "price_factors"  => "ALL_PRICE_FACTORS",
268                    "departments"    => "ALL_DEPARTMENTS",
269                    "business_types" => "ALL_BUSINESS_TYPES",
270     );
271
272   # Projects
273   my @old_project_ids = uniq grep { $_ } map { $_ * 1 } ($form->{"globalproject_id"}, map { $form->{"project_id_$_"} } 1..$form->{"rowcount"});
274   my @old_ids_cond    = @old_project_ids ? (id => \@old_project_ids) : ();
275   my @customer_cond;
276   if (($vc eq 'customers') && $::instance_conf->get_customer_projects_only_in_sales) {
277     @customer_cond = (
278       or => [
279         customer_id          => $::form->{customer_id},
280         billable_customer_id => $::form->{customer_id},
281       ]);
282   }
283   my @conditions = (
284     or => [
285       and => [ active => 1, @customer_cond ],
286       @old_ids_cond,
287     ]);
288
289   $::form->{ALL_PROJECTS}          = SL::DB::Manager::Project->get_all_sorted(query => \@conditions);
290   $::form->{ALL_EMPLOYEES}         = SL::DB::Manager::Employee->get_all_sorted(query => [ or => [ id => $::form->{employee_id},  deleted => 0 ] ]);
291   $::form->{ALL_SALESMEN}          = SL::DB::Manager::Employee->get_all_sorted(query => [ or => [ id => $::form->{salesman_id},  deleted => 0 ] ]);
292   $::form->{ALL_SHIPTO}            = SL::DB::Manager::Shipto->get_all_sorted(query => [
293     or => [ trans_id  => $::form->{"$::form->{vc}_id"} * 1, and => [ shipto_id => $::form->{shipto_id} * 1, trans_id => undef ] ]
294   ]);
295   $::form->{ALL_CONTACTS}          = SL::DB::Manager::Contact->get_all_sorted(query => [
296     or => [
297       cp_cv_id => $::form->{"$::form->{vc}_id"} * 1,
298       and      => [
299         cp_cv_id => undef,
300         cp_id    => $::form->{cp_id} * 1
301       ]
302     ]
303   ]);
304
305   map { $_->{value} = "$_->{description}--$_->{id}" } @{ $form->{ALL_DEPARTMENTS} };
306   map { $_->{value} = "$_->{name}--$_->{id}"        } @{ $form->{ALL_VC} };
307
308   $form->{SHOW_VC_DROP_DOWN} =  $myconfig{vclimit} > scalar @{ $form->{ALL_VC} };
309
310   $form->{oldvcname}         =  $form->{"old$form->{vc}"};
311   $form->{oldvcname}         =~ s/--.*//;
312
313   my $dispatch_to_popup = '';
314   if ($form->{resubmit} && ($form->{format} eq "html")) {
315     $dispatch_to_popup  = "window.open('about:blank','Beleg'); document.do.target = 'Beleg';";
316     $dispatch_to_popup .= "document.do.submit();";
317   } elsif ($form->{resubmit}) {
318     # emulate click for resubmitting actions
319     $dispatch_to_popup  = "document.do.${_}.click(); " for grep { /^action_/ } keys %$form;
320   }
321   $::request->{layout}->add_javascripts_inline("\$(function(){$dispatch_to_popup});");
322
323
324   my $follow_up_vc                =  $form->{ $form->{vc} eq 'customer' ? 'customer' : 'vendor' };
325   $follow_up_vc                   =~ s/--\d*\s*$//;
326
327   $form->{follow_up_trans_info} = $form->{donumber} .'('. $follow_up_vc .')';
328
329   $::request->{layout}->use_javascript(map { "${_}.js" } qw(kivi.SalesPurchase ckeditor/ckeditor ckeditor/adapters/jquery kivi.io autocomplete_customer autocomplete_part));
330
331   my @custom_hidden;
332   push @custom_hidden, map { "shiptocvar_" . $_->name } @{ SL::DB::Manager::CustomVariableConfig->get_all(where => [ module => 'ShipTo' ]) };
333
334   $::form->{HIDDENS} = [ map { +{ name => $_, value => $::form->{$_} } } (@custom_hidden) ];
335
336   $form->header();
337   # Fix für Bug 1082 Erwartet wird: 'abteilungsNAME--abteilungsID'
338   # und Erweiterung für Bug 1760:
339   # Das war leider nur ein Teil-Fix, da das Verhalten den 'Erneuern'-Knopf
340   # nicht überlebt. Konsequent jetzt auf L umgestellt
341   #   $ perldoc SL::Template::Plugin::L
342   # Daher entsprechend nur die Anpassung in form_header
343   # und in DO.pm gemacht. 4 Testfälle:
344   # department_id speichern                 | i.O.
345   # department_id lesen                     | i.O.
346   # department leer überlebt erneuern       | i.O.
347   # department nicht leer überlebt erneuern | i.O.
348   # $main::lxdebug->message(0, 'ABTEILUNGS ID in form?' . $form->{department_id});
349   print $form->parse_html_template('do/form_header');
350
351   $main::lxdebug->leave_sub();
352 }
353
354 sub form_footer {
355   $main::lxdebug->enter_sub();
356
357   check_do_access();
358
359   my $form     = $main::form;
360
361   $form->{PRINT_OPTIONS} = print_options('inline' => 1);
362   $form->{ALL_DELIVERY_TERMS} = SL::DB::Manager::DeliveryTerm->get_all_sorted();
363
364   print $form->parse_html_template('do/form_footer',
365     {transfer_default         => ($::instance_conf->get_transfer_default)});
366
367   $main::lxdebug->leave_sub();
368 }
369
370 sub update_delivery_order {
371   $main::lxdebug->enter_sub();
372
373   check_do_access();
374
375   my $form     = $main::form;
376   my %myconfig = %main::myconfig;
377
378   set_headings($form->{"id"} ? "edit" : "add");
379
380   $form->{insertdate} = SL::DB::DeliveryOrder->new(id => $form->{id})->load->itime_as_date if $form->{id};
381
382   $form->{update} = 1;
383
384   my $payment_id;
385   $payment_id = $form->{payment_id} if $form->{payment_id};
386
387   check_name($form->{vc});
388   $form->{discount} =  $form->{"$form->{vc}_discount"} if defined $form->{"$form->{vc}_discount"};
389   # Problem: Wenn man ohne Erneuern einen Kunden/Lieferanten
390   # wechselt, wird der entsprechende Kunden/ Lieferantenrabatt
391   # nicht übernommen. Grundproblem: In Commit 82574e78
392   # hab ich aus discount customer_discount und vendor_discount
393   # gemacht und entsprechend an den Oberflächen richtig hin-
394   # geschoben. Die damals bessere Lösung wäre gewesen:
395   # In den Templates nur die hidden für form-discount wieder ein-
396   # setzen dann wäre die Verrenkung jetzt nicht notwendig.
397   # TODO: Ggf. Bugfix 1284, 1575 und 817 wieder zusammenführen
398   # Testfälle: Kunden mit Rabatt 0 -> Rabatt 20 i.O.
399   #            Kunde mit Rabatt 20 -> Rabatt 0  i.O.
400   #            Kunde mit Rabatt 20 -> Rabatt 5,5 i.O.
401   $form->{payment_id} = $payment_id if $form->{payment_id} eq "";
402
403   my $i = $form->{rowcount};
404
405   if (   ($form->{"partnumber_$i"} eq "")
406       && ($form->{"description_$i"} eq "")
407       && ($form->{"partsgroup_$i"}  eq "")) {
408
409     check_form();
410
411   } else {
412
413     my $mode;
414     if ($form->{type} eq 'purchase_delivery_order') {
415       IR->retrieve_item(\%myconfig, $form);
416       $mode = 'IR';
417     } else {
418       IS->retrieve_item(\%myconfig, $form);
419       $mode = 'IS';
420     }
421
422     my $rows = scalar @{ $form->{item_list} };
423
424     if ($rows) {
425       $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
426       if( !$form->{"qty_$i"} ) {
427         $form->{"qty_$i"} = 1;
428       }
429
430       if ($rows > 1) {
431
432         select_item(mode => $mode, pre_entered_qty => $form->{"qty_$i"});
433         $::dispatcher->end_request;
434
435       } else {
436
437         my $sellprice = $form->parse_amount(\%myconfig, $form->{"sellprice_$i"});
438
439         map { $form->{"${_}_$i"} = $form->{item_list}[0]{$_} } keys %{ $form->{item_list}[0] };
440
441         $form->{"marge_price_factor_$i"} = $form->{item_list}->[0]->{price_factor};
442
443         if ($sellprice) {
444           $form->{"sellprice_$i"} = $sellprice;
445         } else {
446           my $record        = _make_record();
447           my $price_source  = SL::PriceSource->new(record_item => $record->items->[$i-1], record => $record);
448           my $best_price    = $price_source->best_price;
449           my $best_discount = $price_source->best_discount;
450
451           if ($best_price) {
452             $::form->{"sellprice_$i"}           = $best_price->price;
453             $::form->{"active_price_source_$i"} = $best_price->source;
454           }
455           if ($best_discount) {
456             $::form->{"discount_$i"}               = $best_discount->discount;
457             $::form->{"active_discount_source_$i"} = $best_discount->source;
458           }
459         }
460
461         $form->{"sellprice_$i"}          = $form->format_amount(\%myconfig, $form->{"sellprice_$i"});
462         $form->{"lastcost_$i"}           = $form->format_amount(\%myconfig, $form->{"lastcost_$i"});
463         $form->{"qty_$i"}                = $form->format_amount(\%myconfig, $form->{"qty_$i"});
464         $form->{"discount_$i"}           = $form->format_amount(\%myconfig, $form->{"discount_$i"} * 100.0);
465       }
466
467       display_form();
468
469     } else {
470
471       # ok, so this is a new part
472       # ask if it is a part or service item
473
474       if (   $form->{"partsgroup_$i"}
475           && ($form->{"partsnumber_$i"} eq "")
476           && ($form->{"description_$i"} eq "")) {
477         $form->{rowcount}--;
478         $form->{"discount_$i"} = "";
479         $form->{"not_discountable_$i"} = "";
480         display_form();
481
482       } else {
483         $form->{"id_$i"}   = 0;
484         new_item();
485       }
486     }
487   }
488
489   $main::lxdebug->leave_sub();
490 }
491
492 sub search {
493   $main::lxdebug->enter_sub();
494
495   check_do_access();
496
497   my $form     = $main::form;
498   my %myconfig = %main::myconfig;
499   my $locale   = $main::locale;
500
501   $form->{vc} = $form->{type} eq 'purchase_delivery_order' ? 'vendor' : 'customer';
502
503   $form->get_lists("projects"       => { "key" => "ALL_PROJECTS",
504                                          "all" => 1 },
505                    "departments"    => "ALL_DEPARTMENTS",
506                    "$form->{vc}s"   => "ALL_VC",
507                    "business_types" => "ALL_BUSINESS_TYPES");
508   $form->{ALL_EMPLOYEES} = SL::DB::Manager::Employee->get_all_sorted(query => [ deleted => 0 ]);
509
510   $form->{SHOW_VC_DROP_DOWN} =  $myconfig{vclimit} > scalar @{ $form->{ALL_VC} };
511   $form->{title}             = $locale->text('Delivery Orders');
512
513   $form->header();
514
515   print $form->parse_html_template('do/search');
516
517   $main::lxdebug->leave_sub();
518 }
519
520 sub orders {
521   $main::lxdebug->enter_sub();
522
523   check_do_access();
524
525   my $form     = $main::form;
526   my %myconfig = %main::myconfig;
527   my $locale   = $main::locale;
528   my $cgi      = $::request->{cgi};
529
530   $form->{department_id} = (split /--/, $form->{department})[-1];
531   ($form->{ $form->{vc} }, $form->{"$form->{vc}_id"}) = split(/--/, $form->{ $form->{vc} });
532
533   report_generator_set_default_sort('transdate', 1);
534
535   DO->transactions();
536
537   $form->{rowcount} = scalar @{ $form->{DO} };
538
539   my @columns = qw(
540     ids                     transdate               reqdate
541     id                      donumber
542     ordnumber               customernumber          cusordnumber
543     name                    employee  salesman
544     shipvia                 globalprojectnumber
545     transaction_description department
546     open                    delivered
547     insertdate
548   );
549
550   $form->{l_open}      = $form->{l_closed} = "Y" if ($form->{open}      && $form->{closed});
551   $form->{l_delivered} = "Y"                     if ($form->{delivered} && $form->{notdelivered});
552
553   $form->{title}       = $locale->text('Delivery Orders');
554
555   my $attachment_basename = $form->{vc} eq 'vendor' ? $locale->text('purchase_delivery_order_list') : $locale->text('sales_delivery_order_list');
556
557   my $report = SL::ReportGenerator->new(\%myconfig, $form);
558
559   my @hidden_variables = map { "l_${_}" } @columns;
560   push @hidden_variables, $form->{vc}, qw(l_closed l_notdelivered open closed delivered notdelivered donumber ordnumber serialnumber cusordnumber
561                                           transaction_description transdatefrom transdateto reqdatefrom reqdateto
562                                           type vc employee_id salesman_id project_id parts_partnumber parts_description
563                                           insertdatefrom insertdateto business_id);
564
565   my $href = build_std_url('action=orders', grep { $form->{$_} } @hidden_variables);
566
567   my %column_defs = (
568     'ids'                     => { 'text' => '', },
569     'transdate'               => { 'text' => $locale->text('Delivery Order Date'), },
570     'reqdate'                 => { 'text' => $locale->text('Reqdate'), },
571     'id'                      => { 'text' => $locale->text('ID'), },
572     'donumber'                => { 'text' => $locale->text('Delivery Order'), },
573     'ordnumber'               => { 'text' => $locale->text('Order'), },
574     'customernumber'          => { 'text' => $locale->text('Customer Number'), },
575     'cusordnumber'            => { 'text' => $locale->text('Customer Order Number'), },
576     'name'                    => { 'text' => $form->{vc} eq 'customer' ? $locale->text('Customer') : $locale->text('Vendor'), },
577     'employee'                => { 'text' => $locale->text('Employee'), },
578     'salesman'                => { 'text' => $locale->text('Salesman'), },
579     'shipvia'                 => { 'text' => $locale->text('Ship via'), },
580     'globalprojectnumber'     => { 'text' => $locale->text('Project Number'), },
581     'transaction_description' => { 'text' => $locale->text('Transaction description'), },
582     'open'                    => { 'text' => $locale->text('Open'), },
583     'delivered'               => { 'text' => $locale->text('Delivered'), },
584     'department'              => { 'text' => $locale->text('Department'), },
585     'insertdate'              => { 'text' => $locale->text('Insert Date'), },
586   );
587
588   foreach my $name (qw(id transdate reqdate donumber ordnumber name employee salesman shipvia transaction_description department insertdate)) {
589     my $sortdir                 = $form->{sort} eq $name ? 1 - $form->{sortdir} : $form->{sortdir};
590     $column_defs{$name}->{link} = $href . "&sort=$name&sortdir=$sortdir";
591   }
592
593   $form->{"l_type"} = "Y";
594   map { $column_defs{$_}->{visible} = $form->{"l_${_}"} ? 1 : 0 } @columns;
595
596   $column_defs{ids}->{visible} = 'HTML';
597
598   $report->set_columns(%column_defs);
599   $report->set_column_order(@columns);
600
601   $report->set_export_options('orders', @hidden_variables, qw(sort sortdir));
602
603   $report->set_sort_indicator($form->{sort}, $form->{sortdir});
604
605   my @options;
606   if ($form->{customer}) {
607     push @options, $locale->text('Customer') . " : $form->{customer}";
608   }
609   if ($form->{vendor}) {
610     push @options, $locale->text('Vendor') . " : $form->{vendor}";
611   }
612   if ($form->{cp_name}) {
613     push @options, $locale->text('Contact Person') . " : $form->{cp_name}";
614   }
615   if ($form->{department}) {
616     my ($department) = split /--/, $form->{department};
617     push @options, $locale->text('Department') . " : $department";
618   }
619   if ($form->{donumber}) {
620     push @options, $locale->text('Delivery Order Number') . " : $form->{donumber}";
621   }
622   if ($form->{ordnumber}) {
623     push @options, $locale->text('Order Number') . " : $form->{ordnumber}";
624   }
625   push @options, $locale->text('Serial Number') . " : $form->{serialnumber}" if $form->{serialnumber};
626   if ($form->{business_id}) {
627     my $vc_type_label = $form->{vc} eq 'customer' ? $locale->text('Customer type') : $locale->text('Vendor type');
628     push @options, $vc_type_label . " : " . SL::DB::Business->new(id => $form->{business_id})->load->description;
629   }
630   if ($form->{transaction_description}) {
631     push @options, $locale->text('Transaction description') . " : $form->{transaction_description}";
632   }
633   if ($form->{parts_description}) {
634     push @options, $locale->text('Part Description') . " : $form->{parts_description}";
635   }
636   if ($form->{parts_partnumber}) {
637     push @options, $locale->text('Part Number') . " : $form->{parts_partnumber}";
638   }
639   if ( $form->{transdatefrom} or $form->{transdateto} ) {
640     push @options, $locale->text('Delivery Order Date');
641     push @options, $locale->text('From') . " " . $locale->date(\%myconfig, $form->{transdatefrom}, 1)     if $form->{transdatefrom};
642     push @options, $locale->text('Bis')  . " " . $locale->date(\%myconfig, $form->{transdateto},   1)     if $form->{transdateto};
643   };
644   if ( $form->{reqdatefrom} or $form->{reqdateto} ) {
645     push @options, $locale->text('Reqdate');
646     push @options, $locale->text('From') . " " . $locale->date(\%myconfig, $form->{reqdatefrom}, 1)       if $form->{reqdatefrom};
647     push @options, $locale->text('Bis')  . " " . $locale->date(\%myconfig, $form->{reqdateto},   1)       if $form->{reqdateto};
648   };
649   if ( $form->{insertdatefrom} or $form->{insertdateto} ) {
650     push @options, $locale->text('Insert Date');
651     push @options, $locale->text('From') . " " . $locale->date(\%myconfig, $form->{insertdatefrom}, 1)    if $form->{insertdatefrom};
652     push @options, $locale->text('Bis')  . " " . $locale->date(\%myconfig, $form->{insertdateto},   1)    if $form->{insertdateto};
653   };
654   if ($form->{open}) {
655     push @options, $locale->text('Open');
656   }
657   if ($form->{closed}) {
658     push @options, $locale->text('Closed');
659   }
660   if ($form->{delivered}) {
661     push @options, $locale->text('Delivered');
662   }
663   if ($form->{notdelivered}) {
664     push @options, $locale->text('Not delivered');
665   }
666
667   $report->set_options('top_info_text'        => join("\n", @options),
668                        'raw_top_info_text'    => $form->parse_html_template('do/orders_top'),
669                        'raw_bottom_info_text' => $form->parse_html_template('do/orders_bottom'),
670                        'output_format'        => 'HTML',
671                        'title'                => $form->{title},
672                        'attachment_basename'  => $attachment_basename . strftime('_%Y%m%d', localtime time),
673     );
674   $report->set_options_from_form();
675   $locale->set_numberformat_wo_thousands_separator(\%myconfig) if lc($report->{options}->{output_format}) eq 'csv';
676
677   # add sort and escape callback, this one we use for the add sub
678   $form->{callback} = $href .= "&sort=$form->{sort}";
679
680   # escape callback for href
681   my $callback = $form->escape($href);
682
683   my $edit_url       = build_std_url('action=edit', 'type', 'vc');
684   my $edit_order_url = build_std_url('script=oe.pl', 'type=' . ($form->{type} eq 'sales_delivery_order' ? 'sales_order' : 'purchase_order'), 'action=edit');
685
686   my $idx            = 1;
687
688   foreach my $dord (@{ $form->{DO} }) {
689     $dord->{open}      = $dord->{closed}    ? $locale->text('No')  : $locale->text('Yes');
690     $dord->{delivered} = $dord->{delivered} ? $locale->text('Yes') : $locale->text('No');
691
692     my $row = { map { $_ => { 'data' => $dord->{$_} } } @columns };
693
694     $row->{ids}  = {
695       'raw_data' =>   $cgi->hidden('-name' => "trans_id_${idx}", '-value' => $dord->{id})
696                     . $cgi->checkbox('-name' => "multi_id_${idx}", '-value' => 1, '-label' => ''),
697       'valign'   => 'center',
698       'align'    => 'center',
699     };
700
701     $row->{donumber}->{link}  = $edit_url       . "&id=" . E($dord->{id})      . "&callback=${callback}";
702     $row->{ordnumber}->{link} = $edit_order_url . "&id=" . E($dord->{oe_id})   . "&callback=${callback}" if $dord->{oe_id};
703     $report->add_data($row);
704
705     $idx++;
706   }
707
708   $report->generate_with_headers();
709
710   $main::lxdebug->leave_sub();
711 }
712
713 sub save {
714   $main::lxdebug->enter_sub();
715
716   my (%params) = @_;
717
718   check_do_access();
719
720   my $form     = $main::form;
721   my %myconfig = %main::myconfig;
722   my $locale   = $main::locale;
723
724   $form->mtime_ischanged('delivery_orders');
725
726   $form->{defaultcurrency} = $form->get_default_currency(\%myconfig);
727
728   $form->isblank("transdate", $locale->text('Delivery Order Date missing!'));
729
730   $form->{donumber} =~ s/^\s*//g;
731   $form->{donumber} =~ s/\s*$//g;
732
733   my $msg = ucfirst $form->{vc};
734   $form->isblank($form->{vc}, $locale->text($msg . " missing!"));
735
736   # $locale->text('Customer missing!');
737   # $locale->text('Vendor missing!');
738
739   remove_emptied_rows();
740   validate_items();
741
742   # if the name changed get new values
743   if (check_name($form->{vc})) {
744     update();
745     $::dispatcher->end_request;
746   }
747
748   $form->{id} = 0 if $form->{saveasnew};
749
750   DO->save();
751   # saving the history
752   if(!exists $form->{addition}) {
753     $form->{snumbers} = qq|donumber_| . $form->{donumber};
754     $form->{addition} = "SAVED";
755     $form->save_history;
756   }
757   # /saving the history
758
759   $form->{simple_save} = 1;
760   if (!$params{no_redirect} && !$form->{print_and_save}) {
761     delete @{$form}{ary_diff([keys %{ $form }], [qw(login id script type cursor_fokus)])};
762     edit();
763     $::dispatcher->end_request;
764   }
765   $main::lxdebug->leave_sub();
766 }
767
768 sub delete {
769   $main::lxdebug->enter_sub();
770
771   check_do_access();
772
773   my $form     = $main::form;
774   my %myconfig = %main::myconfig;
775   my $locale   = $main::locale;
776
777   if (DO->delete()) {
778     # saving the history
779     if(!exists $form->{addition}) {
780       $form->{snumbers} = qq|donumber_| . $form->{donumber};
781       $form->{addition} = "DELETED";
782       $form->save_history;
783     }
784     # /saving the history
785
786     $form->info($locale->text('Delivery Order deleted!'));
787     $::dispatcher->end_request;
788   }
789
790   $form->error($locale->text('Cannot delete delivery order!'));
791
792   $main::lxdebug->leave_sub();
793 }
794
795 sub invoice {
796   $main::lxdebug->enter_sub();
797
798   my $form     = $main::form;
799   my %myconfig = %main::myconfig;
800   my $locale   = $main::locale;
801
802   check_do_access();
803   $form->mtime_ischanged('delivery_orders');
804
805   $main::auth->assert($form->{type} eq 'purchase_delivery_order' ? 'vendor_invoice_edit' : 'invoice_edit');
806
807   $form->{convert_from_do_ids} = $form->{id};
808   $form->{deliverydate}        = $form->{transdate};
809   $form->{transdate}           = $form->{invdate} = $form->current_date(\%myconfig);
810   $form->{duedate}             = $form->current_date(\%myconfig, $form->{invdate}, $form->{terms} * 1);
811   $form->{defaultcurrency}     = $form->get_default_currency(\%myconfig);
812
813   $form->{rowcount}--;
814
815   delete @{$form}{qw(id closed delivered)};
816
817   my ($script, $buysell);
818   if ($form->{type} eq 'purchase_delivery_order') {
819     $form->{title}  = $locale->text('Add Vendor Invoice');
820     $form->{script} = 'ir.pl';
821     $script         = "ir";
822     $buysell        = 'sell';
823
824   } else {
825     $form->{title}  = $locale->text('Add Sales Invoice');
826     $form->{script} = 'is.pl';
827     $script         = "is";
828     $buysell        = 'buy';
829   }
830
831   for my $i (1 .. $form->{rowcount}) {
832     map { $form->{"${_}_${i}"} = $form->parse_amount(\%myconfig, $form->{"${_}_${i}"}) if $form->{"${_}_${i}"} } qw(ship qty sellprice lastcost basefactor discount);
833     # für bug 1284
834     # adds a customer/vendor discount, unless we have a workflow case
835     # CAVEAT: has to be done, after the above parse_amount
836     unless ($form->{"ordnumber"}) {
837       if ($form->{discount}) { # Falls wir einen Lieferanten-/Kundenrabatt haben
838         # und rabattfähig sind, dann
839         unless ($form->{"not_discountable_$i"}) {
840           $form->{"discount_$i"} = $form->{discount}*100; # ... nehmen wir diesen Rabatt
841         }
842       }
843     }
844     $form->{"donumber_$i"} = $form->{donumber};
845     $form->{"converted_from_delivery_order_items_id_$i"} = delete $form->{"delivery_order_items_id_$i"};
846   }
847
848   $form->{type} = "invoice";
849
850   # locale messages
851   $main::locale = Locale->new("$myconfig{countrycode}", "$script");
852   $locale = $main::locale;
853
854   require "bin/mozilla/$form->{script}";
855
856   my $currency = $form->{currency};
857   invoice_links();
858
859   if ($form->{ordnumber}) {
860     require SL::DB::Order;
861     if (my $order = SL::DB::Manager::Order->find_by(ordnumber => $form->{ordnumber})) {
862       $order->load;
863       $form->{orddate} = $order->transdate_as_date;
864       $form->{$_}      = $order->$_ for qw(payment_id salesman_id taxzone_id quonumber);
865     }
866   }
867
868   $form->{currency}     = $currency;
869   $form->{exchangerate} = "";
870   $form->{forex}        = $form->check_exchangerate(\%myconfig, $form->{currency}, $form->{invdate}, $buysell);
871   $form->{exchangerate} = $form->{forex} if ($form->{forex});
872
873   prepare_invoice();
874
875   # format amounts
876   for my $i (1 .. $form->{rowcount}) {
877     $form->{"discount_$i"} = $form->format_amount(\%myconfig, $form->{"discount_$i"});
878
879     my ($dec) = ($form->{"sellprice_$i"} =~ /\.(\d+)/);
880     $dec           = length $dec;
881     my $decimalplaces = ($dec > 2) ? $dec : 2;
882
883     # copy delivery date from reqdate for order -> invoice conversion
884     $form->{"deliverydate_$i"} = $form->{"reqdate_$i"}
885       unless $form->{"deliverydate_$i"};
886
887
888     $form->{"sellprice_$i"} =
889       $form->format_amount(\%myconfig, $form->{"sellprice_$i"},
890                            $decimalplaces);
891
892     $form->{"lastcost_$i"} =
893       $form->format_amount(\%myconfig, $form->{"lastcost_$i"},
894                            $decimalplaces);
895
896     (my $dec_qty) = ($form->{"qty_$i"} =~ /\.(\d+)/);
897     $dec_qty = length $dec_qty;
898     $form->{"qty_$i"} =
899       $form->format_amount(\%myconfig, $form->{"qty_$i"}, $dec_qty);
900
901   }
902
903   display_form();
904
905   $main::lxdebug->leave_sub();
906 }
907
908 sub invoice_multi {
909   $main::lxdebug->enter_sub();
910
911   my $form     = $main::form;
912   my %myconfig = %main::myconfig;
913   my $locale   = $main::locale;
914
915   check_do_access();
916   $main::auth->assert($form->{type} eq 'sales_delivery_order' ? 'invoice_edit' : 'vendor_invoice_edit');
917
918   my @do_ids = map { $form->{"trans_id_$_"} } grep { $form->{"multi_id_$_"} } (1..$form->{rowcount});
919
920   if (!scalar @do_ids) {
921     $form->show_generic_error($locale->text('You have not selected any delivery order.'), 'back_button' => 1);
922   }
923
924   map { delete $form->{$_} } grep { m/^(?:trans|multi)_id_\d+/ } keys %{ $form };
925
926   if (!DO->retrieve('vc' => $form->{vc}, 'ids' => \@do_ids)) {
927     $form->show_generic_error($form->{vc} eq 'customer' ?
928                               $locale->text('You cannot create an invoice for delivery orders for different customers.') :
929                               $locale->text('You cannot create an invoice for delivery orders from different vendors.'),
930                               'back_button' => 1);
931   }
932
933   my $source_type              = $form->{type};
934   $form->{convert_from_do_ids} = join ' ', @do_ids;
935   # bei der auswahl von mehreren Lieferscheinen fuer eine Rechnung, die einfach in donumber_array
936   # zwischenspeichern (DO.pm) und als ' '-separierte Liste wieder zurueckschreiben
937   # Hinweis: delete gibt den wert zurueck und loescht danach das element (nett und einfach)
938   # $shell: perldoc perlunc; /delete EXPR
939   $form->{donumber}            = delete $form->{donumber_array};
940   $form->{ordnumber}           = delete $form->{ordnumber_array};
941   $form->{cusordnumber}        = delete $form->{cusordnumber_array};
942   $form->{deliverydate}        = $form->{transdate};
943   $form->{transdate}           = $form->current_date(\%myconfig);
944   $form->{duedate}             = $form->current_date(\%myconfig, $form->{invdate}, $form->{terms} * 1);
945   $form->{type}                = "invoice";
946   $form->{closed}              = 0;
947   $form->{defaultcurrency}     = $form->get_default_currency(\%myconfig);
948
949   my ($script, $buysell);
950   if ($source_type eq 'purchase_delivery_order') {
951     $form->{title}  = $locale->text('Add Vendor Invoice');
952     $form->{script} = 'ir.pl';
953     $script         = "ir";
954     $buysell        = 'sell';
955
956   } else {
957     $form->{title}  = $locale->text('Add Sales Invoice');
958     $form->{script} = 'is.pl';
959     $script         = "is";
960     $buysell        = 'buy';
961   }
962
963   map { delete $form->{$_} } qw(id subject message cc bcc printed emailed queued);
964
965   # get vendor or customer discount
966   my $vc_discount;
967   my $saved_form = save_form();
968   if ($form->{vc} eq 'vendor') {
969     IR->get_vendor(\%myconfig, \%$form);
970     $vc_discount = $form->{vendor_discount};
971   } else {
972     IS->get_customer(\%myconfig, \%$form);
973     $vc_discount = $form->{customer_discount};
974   }
975   # use payment terms from customer or vendor
976   restore_form($saved_form,0,qw(payment_id));
977
978   $form->{rowcount} = 0;
979   foreach my $ref (@{ $form->{form_details} }) {
980     $form->{rowcount}++;
981     $ref->{reqdate} ||= $ref->{dord_transdate}; # copy transdates into each invoice row
982     map { $form->{"${_}_$form->{rowcount}"} = $ref->{$_} } keys %{ $ref };
983     map { $form->{"${_}_$form->{rowcount}"} = $form->format_amount(\%myconfig, $ref->{$_}) } qw(qty sellprice lastcost);
984     $form->{"converted_from_delivery_order_items_id_$form->{rowcount}"} = delete $form->{"delivery_order_items_id_$form->{rowcount}"};
985
986     if ($vc_discount){ # falls wir einen Lieferanten/Kundenrabatt haben
987       # und keinen anderen discount wert an $i ...
988       $form->{"discount_$form->{rowcount}"} ||= $vc_discount; # ... nehmen wir diesen Rabatt
989     }
990
991     $form->{"discount_$form->{rowcount}"}   = $form->{"discount_$form->{rowcount}"}  * 100; #s.a. Bug 1151
992     # Anm.: Eine Änderung des discounts in der SL/DO.pm->retrieve (select (doi.discount * 100) as discount) ergibt in psql einen
993     # Wert von 10.0000001490116. Ferner ist der Rabatt in der Rechnung dann bei 1.0 (?). Deswegen lasse ich das hier. jb 10.10.09
994
995     $form->{"discount_$form->{rowcount}"} = $form->format_amount(\%myconfig, $form->{"discount_$form->{rowcount}"});
996   }
997   delete $form->{form_details};
998
999   $locale = Locale->new("$myconfig{countrycode}", "$script");
1000
1001   require "bin/mozilla/$form->{script}";
1002
1003   invoice_links();
1004   prepare_invoice();
1005
1006   display_form();
1007
1008   $main::lxdebug->leave_sub();
1009 }
1010
1011 sub save_as_new {
1012   $main::lxdebug->enter_sub();
1013
1014   check_do_access();
1015
1016   my $form     = $main::form;
1017
1018   $form->{saveasnew} = 1;
1019   $form->{closed}    = 0;
1020   $form->{delivered} = 0;
1021   map { delete $form->{$_} } qw(printed emailed queued);
1022   delete @{ $form }{ grep { m/^stock_(?:in|out)_\d+/ } keys %{ $form } };
1023   $form->{"converted_from_delivery_order_items_id_$_"} = delete $form->{"delivery_order_items_id_$_"} for 1 .. $form->{"rowcount"};
1024   # Let kivitendo assign a new order number if the user hasn't changed the
1025   # previous one. If it has been changed manually then use it as-is.
1026   $form->{donumber} =~ s/^\s*//g;
1027   $form->{donumber} =~ s/\s*$//g;
1028   if ($form->{saved_donumber} && ($form->{saved_donumber} eq $form->{donumber})) {
1029     delete($form->{donumber});
1030   }
1031
1032   save();
1033
1034   $main::lxdebug->leave_sub();
1035 }
1036
1037 sub e_mail {
1038   $main::lxdebug->enter_sub();
1039
1040   check_do_access();
1041
1042   $::form->mtime_ischanged('delivery_orders','mail');
1043
1044   $::form->{print_and_save} = 1;
1045
1046   my $saved_form = save_form();
1047
1048   save();
1049
1050   restore_form($saved_form, 0, qw(id ordnumber quonumber));
1051
1052   edit_e_mail();
1053
1054   $main::lxdebug->leave_sub();
1055 }
1056
1057 sub calculate_stock_in_out {
1058   $main::lxdebug->enter_sub();
1059
1060   my $form     = $main::form;
1061
1062   my $i = shift;
1063
1064   if (!$form->{"id_${i}"}) {
1065     $main::lxdebug->leave_sub();
1066     return '';
1067   }
1068
1069   my $all_units = AM->retrieve_all_units();
1070
1071   my $in_out   = $form->{type} =~ /^sales/ ? 'out' : 'in';
1072   my $sinfo    = DO->unpack_stock_information('packed' => $form->{"stock_${in_out}_${i}"});
1073
1074   my $do_qty   = AM->sum_with_unit($::form->{"qty_$i"}, $::form->{"unit_$i"});
1075   my $sum      = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $sinfo });
1076   my $matches  = $do_qty == $sum;
1077
1078   my $content  = $form->format_amount_units('amount'      => $sum * 1,
1079                                             'part_unit'   => $form->{"partunit_$i"},
1080                                             'amount_unit' => $all_units->{$form->{"partunit_$i"}}->{base_unit},
1081                                             'conv_units'  => 'convertible_not_smaller',
1082                                             'max_places'  => 2);
1083   $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="?">|;
1084
1085   $main::lxdebug->leave_sub();
1086
1087   return $content;
1088 }
1089
1090 sub get_basic_bin_wh_info {
1091   $main::lxdebug->enter_sub();
1092
1093   my $stock_info = shift;
1094
1095   my $form     = $main::form;
1096
1097   foreach my $sinfo (@{ $stock_info }) {
1098     next unless ($sinfo->{bin_id});
1099
1100     my $bin_info = WH->get_basic_bin_info('id' => $sinfo->{bin_id});
1101     map { $sinfo->{"${_}_description"} = $sinfo->{"${_}description"} = $bin_info->{"${_}_description"} } qw(bin warehouse);
1102   }
1103
1104   $main::lxdebug->leave_sub();
1105 }
1106
1107 sub stock_in_out_form {
1108   $main::lxdebug->enter_sub();
1109
1110   my $form     = $main::form;
1111
1112   if ($form->{in_out} eq 'out') {
1113     stock_out_form();
1114   } else {
1115     stock_in_form();
1116   }
1117
1118   $main::lxdebug->leave_sub();
1119 }
1120
1121 sub redo_stock_info {
1122   $main::lxdebug->enter_sub();
1123
1124   my %params    = @_;
1125
1126   my $form     = $main::form;
1127
1128   my @non_empty = grep { $_->{qty} } @{ $params{stock_info} };
1129
1130   if ($params{add_empty_row}) {
1131     push @non_empty, {
1132       'warehouse_id' => scalar(@non_empty) ? $non_empty[-1]->{warehouse_id} : undef,
1133       'bin_id'       => scalar(@non_empty) ? $non_empty[-1]->{bin_id}       : undef,
1134     };
1135   }
1136
1137   @{ $params{stock_info} } = @non_empty;
1138
1139   $main::lxdebug->leave_sub();
1140 }
1141
1142 sub update_stock_in {
1143   $main::lxdebug->enter_sub();
1144
1145   my $form     = $main::form;
1146   my %myconfig = %main::myconfig;
1147
1148   my $stock_info = [];
1149
1150   foreach my $i (1..$form->{rowcount}) {
1151     $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
1152     push @{ $stock_info }, { map { $_ => $form->{"${_}_${i}"} } qw(warehouse_id bin_id chargenumber
1153                                                                    bestbefore qty unit delivery_order_items_stock_id) };
1154   }
1155
1156   display_stock_in_form($stock_info);
1157
1158   $main::lxdebug->leave_sub();
1159 }
1160
1161 sub stock_in_form {
1162   $main::lxdebug->enter_sub();
1163
1164   my $form     = $main::form;
1165
1166   my $stock_info = DO->unpack_stock_information('packed' => $form->{stock});
1167
1168   display_stock_in_form($stock_info);
1169
1170   $main::lxdebug->leave_sub();
1171 }
1172
1173 sub display_stock_in_form {
1174   $main::lxdebug->enter_sub();
1175
1176   my $stock_info = shift;
1177
1178   my $form     = $main::form;
1179   my %myconfig = %main::myconfig;
1180   my $locale   = $main::locale;
1181
1182   $form->{title} = $locale->text('Stock');
1183
1184   my $part_info  = IC->get_basic_part_info('id' => $form->{parts_id});
1185
1186   # Standardlagerplatz für Standard-Auslagern verwenden, falls keiner für die Ware explizit definiert wurde
1187   if ($::instance_conf->get_transfer_default_use_master_default_bin) {
1188     $part_info->{warehouse_id} ||= $::instance_conf->get_warehouse_id;
1189     $part_info->{bin_id}       ||= $::instance_conf->get_bin_id;
1190   }
1191
1192   my $units      = AM->retrieve_units(\%myconfig, $form);
1193   # der zweite Parameter von unit_select_data gibt den default-Namen (selected) vor
1194   my $units_data = AM->unit_select_data($units, $form->{do_unit}, undef, $part_info->{unit});
1195
1196   $form->get_lists('warehouses' => { 'key'    => 'WAREHOUSES',
1197                                      'bins'   => 'BINS' });
1198
1199   redo_stock_info('stock_info' => $stock_info, 'add_empty_row' => !$form->{delivered});
1200
1201   get_basic_bin_wh_info($stock_info);
1202
1203   $form->header(no_layout => 1);
1204   print $form->parse_html_template('do/stock_in_form', { 'UNITS'      => $units_data,
1205                                                          'STOCK_INFO' => $stock_info,
1206                                                          'PART_INFO'  => $part_info, });
1207
1208   $main::lxdebug->leave_sub();
1209 }
1210
1211 sub _stock_in_out_set_qty_display {
1212   my $stock_info       = shift;
1213   my $form             = $::form;
1214   my $all_units        = AM->retrieve_all_units();
1215   my $sum              = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $stock_info });
1216   $form->{qty_display} = $form->format_amount_units(amount      => $sum * 1,
1217                                                     part_unit   => $form->{partunit},
1218                                                     amount_unit => $all_units->{ $form->{partunit} }->{base_unit},
1219                                                     conv_units  => 'convertible_not_smaller',
1220                                                     max_places  => 2);
1221 }
1222
1223 sub set_stock_in {
1224   $main::lxdebug->enter_sub();
1225
1226   my $form     = $main::form;
1227   my %myconfig = %main::myconfig;
1228
1229   my $stock_info = [];
1230
1231   foreach my $i (1..$form->{rowcount}) {
1232     $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
1233
1234     next if ($form->{"qty_$i"} <= 0);
1235
1236     push @{ $stock_info }, { map { $_ => $form->{"${_}_${i}"} } qw(delivery_order_items_stock_id warehouse_id bin_id chargenumber bestbefore qty unit) };
1237   }
1238
1239   $form->{stock} = YAML::Dump($stock_info);
1240
1241   _stock_in_out_set_qty_display($stock_info);
1242
1243   my $do_qty       = AM->sum_with_unit($::form->parse_amount(\%::myconfig, $::form->{do_qty}), $::form->{do_unit});
1244   my $transfer_qty = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $stock_info });
1245
1246   $form->header();
1247   print $form->parse_html_template('do/set_stock_in_out', {
1248     qty_matches => $do_qty == $transfer_qty,
1249   });
1250
1251   $main::lxdebug->leave_sub();
1252 }
1253
1254 sub stock_out_form {
1255   $main::lxdebug->enter_sub();
1256
1257   my $form     = $main::form;
1258   my %myconfig = %main::myconfig;
1259   my $locale   = $main::locale;
1260
1261   $form->{title} = $locale->text('Release From Stock');
1262
1263   my $part_info  = IC->get_basic_part_info('id' => $form->{parts_id});
1264
1265   my $units      = AM->retrieve_units(\%myconfig, $form);
1266   my $units_data = AM->unit_select_data($units, undef, undef, $part_info->{unit});
1267
1268   my @contents   = DO->get_item_availability('parts_id' => $form->{parts_id});
1269
1270   my $stock_info = DO->unpack_stock_information('packed' => $form->{stock});
1271
1272   if (!$form->{delivered}) {
1273     foreach my $row (@contents) {
1274       $row->{available_qty} = $form->format_amount_units('amount'      => $row->{qty} * 1,
1275                                                          'part_unit'   => $part_info->{unit},
1276                                                          'conv_units'  => 'convertible_not_smaller',
1277                                                          'max_places'  => 2);
1278
1279       foreach my $sinfo (@{ $stock_info }) {
1280         next if (($row->{bin_id}       != $sinfo->{bin_id}) ||
1281                  ($row->{warehouse_id} != $sinfo->{warehouse_id}) ||
1282                  ($row->{chargenumber} ne $sinfo->{chargenumber}) ||
1283                  ($row->{bestbefore}   ne $sinfo->{bestbefore}));
1284
1285         map { $row->{"stock_$_"} = $sinfo->{$_} } qw(qty unit error delivery_order_items_stock_id);
1286       }
1287     }
1288
1289   } else {
1290     get_basic_bin_wh_info($stock_info);
1291
1292     foreach my $sinfo (@{ $stock_info }) {
1293       map { $sinfo->{"stock_$_"} = $sinfo->{$_} } qw(qty unit);
1294     }
1295   }
1296
1297   $form->header(no_layout => 1);
1298   print $form->parse_html_template('do/stock_out_form', { 'UNITS'      => $units_data,
1299                                                           'WHCONTENTS' => $form->{delivered} ? $stock_info : \@contents,
1300                                                           'PART_INFO'  => $part_info, });
1301
1302   $main::lxdebug->leave_sub();
1303 }
1304
1305 sub set_stock_out {
1306   $main::lxdebug->enter_sub();
1307
1308   my $form     = $main::form;
1309   my %myconfig = %main::myconfig;
1310   my $locale   = $main::locale;
1311
1312   my $stock_info = [];
1313
1314   foreach my $i (1 .. $form->{rowcount}) {
1315     $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
1316
1317     next if ($form->{"qty_$i"} <= 0);
1318
1319     push @{ $stock_info }, {
1320       'warehouse_id' => $form->{"warehouse_id_$i"},
1321       'bin_id'       => $form->{"bin_id_$i"},
1322       'chargenumber' => $form->{"chargenumber_$i"},
1323       'bestbefore'   => $form->{"bestbefore_$i"},
1324       'qty'          => $form->{"qty_$i"},
1325       'unit'         => $form->{"unit_$i"},
1326       'row'          => $i,
1327       'delivery_order_items_stock_id'  => $form->{"delivery_order_items_stock_id_$i"},
1328     };
1329   }
1330
1331   my @errors     = DO->check_stock_availability('requests' => $stock_info,
1332                                                 'parts_id' => $form->{parts_id});
1333
1334   $form->{stock} = YAML::Dump($stock_info);
1335
1336   if (@errors) {
1337     $form->{ERRORS} = [];
1338     map { push @{ $form->{ERRORS} }, $locale->text('Error in row #1: The quantity you entered is bigger than the stocked quantity.', $_->{row}); } @errors;
1339     stock_in_out_form();
1340
1341   } else {
1342     _stock_in_out_set_qty_display($stock_info);
1343
1344     my $do_qty       = AM->sum_with_unit($::form->parse_amount(\%::myconfig, $::form->{do_qty}), $::form->{do_unit});
1345     my $transfer_qty = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $stock_info });
1346
1347     $form->header();
1348     print $form->parse_html_template('do/set_stock_in_out', {
1349       qty_matches => $do_qty == $transfer_qty,
1350     });
1351   }
1352
1353   $main::lxdebug->leave_sub();
1354 }
1355
1356 sub transfer_in {
1357   $main::lxdebug->enter_sub();
1358
1359   my $form     = $main::form;
1360   my %myconfig = %main::myconfig;
1361   my $locale   = $main::locale;
1362
1363   if ($form->{id} && DO->is_marked_as_delivered(id => $form->{id})) {
1364     $form->show_generic_error($locale->text('The parts for this delivery order have already been transferred in.'), 'back_button' => 1);
1365   }
1366
1367   save(no_redirect => 1);
1368
1369   my @part_ids = map { $form->{"id_${_}"} } grep { $form->{"id_${_}"} && $form->{"stock_in_${_}"} } (1 .. $form->{rowcount});
1370   my @all_requests;
1371
1372   if (@part_ids) {
1373     my $units         = AM->retrieve_units(\%myconfig, $form);
1374     my %part_info_map = IC->get_basic_part_info('id' => \@part_ids);
1375     my %request_map;
1376
1377     $form->{ERRORS}   = [];
1378
1379     foreach my $i (1 .. $form->{rowcount}) {
1380       next unless ($form->{"id_$i"} && $form->{"stock_in_$i"});
1381
1382       my $row_sum_base_qty = 0;
1383       my $base_unit_factor = $units->{ $part_info_map{$form->{"id_$i"}}->{unit} }->{factor} || 1;
1384
1385       foreach my $request (@{ DO->unpack_stock_information('packed' => $form->{"stock_in_$i"}) }) {
1386         $request->{parts_id}  = $form->{"id_$i"};
1387         $row_sum_base_qty    += $request->{qty} * $units->{$request->{unit}}->{factor} / $base_unit_factor;
1388
1389         $request->{project_id} = $form->{"project_id_$i"} || $form->{globalproject_id};
1390
1391         push @all_requests, $request;
1392       }
1393
1394       next if (0 == $row_sum_base_qty);
1395
1396       my $do_base_qty = $form->parse_amount(\%myconfig, $form->{"qty_$i"}) * $units->{$form->{"unit_$i"}}->{factor} / $base_unit_factor;
1397
1398 #      if ($do_base_qty != $row_sum_base_qty) {
1399 #        push @{ $form->{ERRORS} }, $locale->text('Error in position #1: You must either assign no stock at all or the full quantity of #2 #3.',
1400 #                                                 $i, $form->{"qty_$i"}, $form->{"unit_$i"});
1401 #      }
1402     }
1403
1404     if (@{ $form->{ERRORS} }) {
1405       push @{ $form->{ERRORS} }, $locale->text('The delivery order has not been marked as delivered. The warehouse contents have not changed.');
1406
1407       set_headings('edit');
1408       update();
1409       $main::lxdebug->leave_sub();
1410
1411       $::dispatcher->end_request;
1412     }
1413   }
1414
1415   DO->transfer_in_out('direction' => 'in',
1416                       'requests'  => \@all_requests);
1417
1418   SL::DB::DeliveryOrder->new(id => $form->{id})->load->update_attributes(delivered => 1);
1419
1420   $form->{callback} = 'do.pl?action=edit&type=purchase_delivery_order&id=' . $form->escape($form->{id});
1421   $form->redirect;
1422
1423   $main::lxdebug->leave_sub();
1424 }
1425
1426 sub transfer_out {
1427   $main::lxdebug->enter_sub();
1428
1429   my $form     = $main::form;
1430   my %myconfig = %main::myconfig;
1431   my $locale   = $main::locale;
1432
1433   if ($form->{id} && DO->is_marked_as_delivered(id => $form->{id})) {
1434     $form->show_generic_error($locale->text('The parts for this delivery order have already been transferred out.'), 'back_button' => 1);
1435   }
1436
1437   save(no_redirect => 1);
1438
1439   my @part_ids = map { $form->{"id_${_}"} } grep { $form->{"id_${_}"} && $form->{"stock_out_${_}"} } (1 .. $form->{rowcount});
1440   my @all_requests;
1441
1442   if (@part_ids) {
1443     my $units         = AM->retrieve_units(\%myconfig, $form);
1444     my %part_info_map = IC->get_basic_part_info('id' => \@part_ids);
1445     my %request_map;
1446
1447     $form->{ERRORS}   = [];
1448
1449     foreach my $i (1 .. $form->{rowcount}) {
1450       next unless ($form->{"id_$i"} && $form->{"stock_out_$i"});
1451
1452       my $row_sum_base_qty = 0;
1453       my $base_unit_factor = $units->{ $part_info_map{$form->{"id_$i"}}->{unit} }->{factor} || 1;
1454
1455       foreach my $request (@{ DO->unpack_stock_information('packed' => $form->{"stock_out_$i"}) }) {
1456         $request->{parts_id} = $form->{"id_$i"};
1457         $request->{base_qty} = $request->{qty} * $units->{$request->{unit}}->{factor} / $base_unit_factor;
1458         $request->{project_id} = $form->{"project_id_$i"} ? $form->{"project_id_$i"} : $form->{globalproject_id};
1459
1460         my $map_key          = join '--', ($form->{"id_$i"}, @{$request}{qw(warehouse_id bin_id chargenumber bestbefore)});
1461
1462         $request_map{$map_key}                 ||= $request;
1463         $request_map{$map_key}->{sum_base_qty} ||= 0;
1464         $request_map{$map_key}->{sum_base_qty}  += $request->{base_qty};
1465         $row_sum_base_qty                       += $request->{base_qty};
1466
1467         push @all_requests, $request;
1468       }
1469
1470       next if (0 == $row_sum_base_qty);
1471
1472       my $do_base_qty = $form->{"qty_$i"} * $units->{$form->{"unit_$i"}}->{factor} / $base_unit_factor;
1473
1474 #      if ($do_base_qty != $row_sum_base_qty) {
1475 #        push @{ $form->{ERRORS} }, $locale->text('Error in position #1: You must either assign no transfer at all or the full quantity of #2 #3.',
1476 #                                                 $i, $form->{"qty_$i"}, $form->{"unit_$i"});
1477 #      }
1478     }
1479
1480     if (%request_map) {
1481       my @bin_ids      = map { $_->{bin_id} } values %request_map;
1482       my %bin_info_map = WH->get_basic_bin_info('id' => \@bin_ids);
1483       my @contents     = DO->get_item_availability('parts_id' => \@part_ids);
1484
1485       foreach my $inv (@contents) {
1486         my $map_key = join '--', @{$inv}{qw(parts_id warehouse_id bin_id chargenumber bestbefore)};
1487
1488         next unless ($request_map{$map_key});
1489
1490         my $request    = $request_map{$map_key};
1491         $request->{ok} = $request->{sum_base_qty} <= $inv->{qty};
1492       }
1493
1494       foreach my $request (values %request_map) {
1495         next if ($request->{ok});
1496
1497         my $pinfo = $part_info_map{$request->{parts_id}};
1498         my $binfo = $bin_info_map{$request->{bin_id}};
1499
1500         if ($::instance_conf->get_show_bestbefore) {
1501             push @{ $form->{ERRORS} }, $locale->text("There is not enough available of '#1' at warehouse '#2', bin '#3', #4, #5, for the transfer of #6.",
1502                                                      $pinfo->{description},
1503                                                      $binfo->{warehouse_description},
1504                                                      $binfo->{bin_description},
1505                                                      $request->{chargenumber} ? $locale->text('chargenumber #1', $request->{chargenumber}) : $locale->text('no chargenumber'),
1506                                                      $request->{bestbefore} ? $locale->text('bestbefore #1', $request->{bestbefore}) : $locale->text('no bestbefore'),
1507                                                      $form->format_amount_units('amount'      => $request->{sum_base_qty},
1508                                                                                 'part_unit'   => $pinfo->{unit},
1509                                                                                 'conv_units'  => 'convertible_not_smaller'));
1510         } else {
1511             push @{ $form->{ERRORS} }, $locale->text("There is not enough available of '#1' at warehouse '#2', bin '#3', #4, for the transfer of #5.",
1512                                                      $pinfo->{description},
1513                                                      $binfo->{warehouse_description},
1514                                                      $binfo->{bin_description},
1515                                                      $request->{chargenumber} ? $locale->text('chargenumber #1', $request->{chargenumber}) : $locale->text('no chargenumber'),
1516                                                      $form->format_amount_units('amount'      => $request->{sum_base_qty},
1517                                                                                 'part_unit'   => $pinfo->{unit},
1518                                                                                 'conv_units'  => 'convertible_not_smaller'));
1519         }
1520       }
1521     }
1522
1523     if (@{ $form->{ERRORS} }) {
1524       push @{ $form->{ERRORS} }, $locale->text('The delivery order has not been marked as delivered. The warehouse contents have not changed.');
1525
1526       set_headings('edit');
1527       update();
1528       $main::lxdebug->leave_sub();
1529
1530       $::dispatcher->end_request;
1531     }
1532   }
1533   DO->transfer_in_out('direction' => 'out',
1534                       'requests'  => \@all_requests);
1535
1536   SL::DB::DeliveryOrder->new(id => $form->{id})->load->update_attributes(delivered => 1);
1537
1538   $form->{callback} = 'do.pl?action=edit&type=sales_delivery_order&id=' . $form->escape($form->{id});
1539   $form->redirect;
1540
1541   $main::lxdebug->leave_sub();
1542 }
1543
1544 sub mark_closed {
1545   $main::lxdebug->enter_sub();
1546
1547   my $form     = $main::form;
1548
1549   DO->close_orders('ids' => [ $form->{id} ]);
1550
1551   $form->{closed} = 1;
1552
1553   update();
1554
1555   $main::lxdebug->leave_sub();
1556 }
1557
1558 sub display_form {
1559   $::lxdebug->enter_sub;
1560
1561   $::auth->assert('purchase_delivery_order_edit | sales_delivery_order_edit');
1562
1563   relink_accounts();
1564   retrieve_partunits();
1565
1566   my $new_rowcount = $::form->{"rowcount"} * 1 + 1;
1567   $::form->{"project_id_${new_rowcount}"} = $::form->{"globalproject_id"};
1568
1569   $::form->language_payment(\%::myconfig);
1570
1571   Common::webdav_folder($::form);
1572
1573   form_header();
1574   display_row(++$::form->{rowcount});
1575   form_footer();
1576
1577   $::lxdebug->leave_sub;
1578 }
1579
1580 sub yes {
1581   call_sub($main::form->{yes_nextsub});
1582 }
1583
1584 sub no {
1585   call_sub($main::form->{no_nextsub});
1586 }
1587
1588 sub update {
1589   call_sub($main::form->{update_nextsub} || $main::form->{nextsub} || 'update_delivery_order');
1590 }
1591
1592 sub dispatcher {
1593   my $form     = $main::form;
1594   my $locale   = $main::locale;
1595
1596   foreach my $action (qw(update ship_to print e_mail save transfer_out transfer_out_default sort
1597                          transfer_in transfer_in_default mark_closed save_as_new invoice delete)) {
1598     if ($form->{"action_${action}"}) {
1599       call_sub($action);
1600       return;
1601     }
1602   }
1603
1604   $form->error($locale->text('No action defined.'));
1605 }
1606
1607 sub transfer_out_default {
1608   $main::lxdebug->enter_sub();
1609
1610   my $form     = $main::form;
1611
1612   transfer_in_out_default('direction' => 'out');
1613
1614   $main::lxdebug->leave_sub();
1615 }
1616
1617 sub transfer_in_default {
1618   $main::lxdebug->enter_sub();
1619
1620   my $form     = $main::form;
1621
1622   transfer_in_out_default('direction' => 'in');
1623
1624   $main::lxdebug->leave_sub();
1625 }
1626
1627 # Falls das Standardlagerverfahren aktiv ist, wird
1628 # geprüft, ob alle Standardlagerplätze für die Auslager-
1629 # artikel vorhanden sind UND ob die Warenmenge ausreicht zum
1630 # Auslagern. Falls nicht wird entsprechend eine Fehlermeldung
1631 # generiert. Offen Chargennummer / bestbefore wird nicht berücksichtigt
1632 sub transfer_in_out_default {
1633   $main::lxdebug->enter_sub();
1634
1635   my $form     = $main::form;
1636   my %myconfig = %main::myconfig;
1637   my $locale   = $main::locale;
1638   my %params   = @_;
1639
1640   my (%missing_default_bins, %qty_parts, @all_requests, %part_info_map, $default_warehouse_id, $default_bin_id);
1641
1642   Common::check_params(\%params, qw(direction));
1643
1644   # entsprechende defaults holen, falls standardlagerplatz verwendet werden soll
1645   if ($::instance_conf->get_transfer_default_use_master_default_bin) {
1646     $default_warehouse_id = $::instance_conf->get_warehouse_id;
1647     $default_bin_id       = $::instance_conf->get_bin_id;
1648   }
1649
1650
1651   my @part_ids = map { $form->{"id_${_}"} } (1 .. $form->{rowcount});
1652   if (@part_ids) {
1653     my $units         = AM->retrieve_units(\%myconfig, $form);
1654     %part_info_map = IC->get_basic_part_info('id' => \@part_ids);
1655     foreach my $i (1 .. $form->{rowcount}) {
1656       next unless ($form->{"id_$i"});
1657       my $base_unit_factor = $units->{ $part_info_map{$form->{"id_$i"}}->{unit} }->{factor} || 1;
1658       my $qty =   $form->parse_amount(\%myconfig, $form->{"qty_$i"}) * $units->{$form->{"unit_$i"}}->{factor} / $base_unit_factor;
1659
1660       $form->show_generic_error($locale->text("Cannot transfer negative entries." ), 'back_button' => 1) if ($qty < 0);
1661       # if we do not want to transfer services and this part is a service, set qty to zero
1662       # ... and do not create a hash entry in %qty_parts below (will skip check for bins for the transfer == out case)
1663       # ... 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)
1664
1665       $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});
1666       $qty_parts{$form->{"id_$i"}} += $qty;
1667       if ($qty == 0) {
1668         delete $qty_parts{$form->{"id_$i"}} unless $qty_parts{$form->{"id_$i"}};
1669         undef $form->{"stock_in_$i"};
1670       }
1671
1672       $part_info_map{$form->{"id_$i"}}{bin_id}       ||= $default_bin_id;
1673       $part_info_map{$form->{"id_$i"}}{warehouse_id} ||= $default_warehouse_id;
1674
1675       push @all_requests, ($qty == 0) ? { } : {
1676                         'chargenumber' => '',  #?? die müsste entsprechend geholt werden
1677                         #'bestbefore' => undef, # TODO wird nicht berücksichtigt
1678                         'bin_id' => $part_info_map{$form->{"id_$i"}}{bin_id},
1679                         'qty' => $qty,
1680                         'parts_id' => $form->{"id_$i"},
1681                         'comment' => $locale->text("Default transfer delivery order"),
1682                         'unit' => $part_info_map{$form->{"id_$i"}}{unit},
1683                         'warehouse_id' => $part_info_map{$form->{"id_$i"}}{warehouse_id},
1684                         'oe_id' => $form->{id},
1685                         'project_id' => $form->{"project_id_$i"} ? $form->{"project_id_$i"} : $form->{globalproject_id}
1686                       };
1687     }
1688
1689     # jetzt wird erst überprüft, ob die Stückzahl entsprechend stimmt.
1690     # check if bin (transfer in and transfer out and qty (transfer out) is correct
1691     foreach my $key (keys %qty_parts) {
1692
1693       $missing_default_bins{$key}{missing_bin} = 1 unless ($part_info_map{$key}{bin_id});
1694       next unless ($part_info_map{$key}{bin_id}); # abbruch
1695
1696       if ($params{direction} eq 'out') {  # wird nur für ausgehende Mengen benötigt
1697         my ($max_qty, $error) = WH->get_max_qty_parts_bin(parts_id => $key, bin_id => $part_info_map{$key}{bin_id});
1698         if ($error == 1) {
1699           # wir können nicht entscheiden, welche charge oder mhd (bestbefore) ausgewählt sein soll
1700           # deshalb rückmeldung nach oben geben, manuell auszulagern
1701           # TODO Bei nur einem Treffer mit Charge oder bestbefore wäre das noch möglich
1702           $missing_default_bins{$key}{chargenumber} = 1;
1703         }
1704         if ($max_qty < $qty_parts{$key}){
1705           $missing_default_bins{$key}{missing_qty} = $max_qty - $qty_parts{$key};
1706         }
1707       }
1708     }
1709   } # if @parts_id
1710
1711   # Abfrage für Fehlerbehandlung (nur bei direction == out)
1712   if (scalar (keys %missing_default_bins)) {
1713     my $fehlertext;
1714     foreach my $fehler (keys %missing_default_bins) {
1715
1716       my $ware = WH->get_part_description(parts_id => $fehler);
1717       if ($missing_default_bins{$fehler}{missing_bin}){
1718         $fehlertext .= "Kein Standardlagerplatz definiert bei $ware <br>";
1719       }
1720       if ($missing_default_bins{$fehler}{missing_qty}) {  # missing_qty
1721         $fehlertext .= "Es fehlen " . $missing_default_bins{$fehler}{missing_qty}*-1 .
1722                        " von $ware auf dem Standard-Lagerplatz " . $part_info_map{$fehler}{bin} .   " zum Auslagern<br>";
1723       }
1724       if ($missing_default_bins{$fehler}{chargenumber}){
1725         $fehlertext .= "Die Ware hat eine Chargennummer oder eine Mindesthaltbarkeit definiert.
1726                         Hier kann man nicht automatisch entscheiden.
1727                         Bitte diesen Lieferschein manuell auslagern.
1728                         Bei: $ware";
1729       }
1730       # auslagern soll immer gehen, auch wenn nicht genügend auf lager ist.
1731       # der lagerplatz ist hier extra konfigurierbar, bspw. Lager-Korrektur mit
1732       # Lagerplatz Lagerplatz-Korrektur
1733       my $default_warehouse_id_ignore_onhand = $::instance_conf->get_warehouse_id_ignore_onhand;
1734       my $default_bin_id_ignore_onhand       = $::instance_conf->get_bin_id_ignore_onhand;
1735       if ($::instance_conf->get_transfer_default_ignore_onhand && $default_bin_id_ignore_onhand) {
1736         # entsprechende defaults holen
1737         # falls chargenumber, bestbefore oder anzahl nicht stimmt, auf automatischen
1738         # lagerplatz wegbuchen!
1739         foreach (@all_requests) {
1740           if ($_->{parts_id} eq $fehler){
1741           $_->{bin_id}        = $default_bin_id_ignore_onhand;
1742           $_->{warehouse_id}  = $default_warehouse_id_ignore_onhand;
1743           }
1744         }
1745       } else {
1746         #$main::lxdebug->message(0, 'Fehlertext: ' . $fehlertext);
1747         $form->show_generic_error($locale->text("Cannot transfer. <br> Reason:<br>#1", $fehlertext ), 'back_button' => 1);
1748       }
1749     }
1750   }
1751
1752
1753   # hier der eigentliche fallunterschied für in oder out
1754   my $prefix   = $params{direction} eq 'in' ? 'in' : 'out';
1755
1756   # dieser array_ref ist für DO->save da:
1757   # einmal die all_requests in YAML verwandeln, damit delivery_order_items_stock
1758   # gefüllt werden kann.
1759   # could be dumped to the form in the first loop,
1760   # but maybe bin_id and warehouse_id has changed to the "korrekturlager" with
1761   # allowed negative qty ($::instance_conf->get_warehouse_id_ignore_onhand) ...
1762   my $i = 0;
1763   foreach (@all_requests){
1764     $i++;
1765     next unless scalar(%{ $_ });
1766     $form->{"stock_${prefix}_$i"} = YAML::Dump([$_]);
1767   }
1768
1769   save(no_redirect => 1); # Wir können auslagern, deshalb beleg speichern
1770                           # und in delivery_order_items_stock speichern
1771
1772   # ... and fill back the persistent dois_id for inventory fk
1773   undef (@all_requests);
1774   foreach my $i (1 .. $form->{rowcount}) {
1775     next unless ($form->{"id_$i"} && $form->{"stock_${prefix}_$i"});
1776     push @all_requests, @{ DO->unpack_stock_information('packed' => $form->{"stock_${prefix}_$i"}) };
1777   }
1778   DO->transfer_in_out('direction' => $prefix,
1779                       'requests'  => \@all_requests);
1780
1781   SL::DB::DeliveryOrder->new(id => $form->{id})->load->update_attributes(delivered => 1);
1782
1783   $form->{callback} = 'do.pl?action=edit&type=sales_delivery_order&id=' . $form->escape($form->{id}) if $params{direction} eq 'out';
1784   $form->{callback} = 'do.pl?action=edit&type=purchase_delivery_order&id=' . $form->escape($form->{id}) if $params{direction} eq 'in';
1785   $form->redirect;
1786
1787 }
1788
1789 sub sort {
1790   $main::lxdebug->enter_sub();
1791
1792   check_do_access();
1793
1794   my $form     = $main::form;
1795   my %temp_hash;
1796
1797   save(no_redirect => 1); # has to be done, at least for newly added positions
1798
1799   # hashify partnumbers, positions. key is delivery_order_items_id
1800   for my $i (1 .. ($form->{rowcount}) ) {
1801     $temp_hash{$form->{"delivery_order_items_id_$i"}} = { runningnumber => $form->{"runningnumber_$i"}, partnumber => $form->{"partnumber_$i"} };
1802     if ($form->{id} && $form->{"discount_$i"}) {
1803       # prepare_order assumes a db value if there is a form->id and multiplies *100
1804       # We hope for new controller code (no more format_amount/parse_amount distinction)
1805       $form->{"discount_$i"} /=100;
1806     }
1807   }
1808   # naturally sort partnumbers and get a sorted array of doi_ids
1809   my @sorted_doi_ids =  sort { Sort::Naturally::ncmp($temp_hash{$a}->{"partnumber"}, $temp_hash{$b}->{"partnumber"}) }  keys %temp_hash;
1810
1811
1812   my $new_number = 1;
1813
1814   for (@sorted_doi_ids) {
1815     $form->{"runningnumber_$temp_hash{$_}->{runningnumber}"} = $new_number;
1816     $new_number++;
1817   }
1818   # all parse_amounts changes are in form (i.e. , to .) therefore we need
1819   # another format_amount to change it back, for the next save ;-(
1820   # works great except for row discounts (see above comment)
1821   prepare_order();
1822
1823
1824     $main::lxdebug->leave_sub();
1825     save();
1826 }
1827
1828 __END__
1829
1830 =pod
1831
1832 =encoding utf8
1833
1834 =head1 NAME
1835
1836 do.pl - Script for all calls to delivery order
1837
1838
1839 =head1 FUNCTIONS
1840
1841 =over 2
1842
1843 =item C<sort>
1844
1845 Sorts all position with Natural Sort. Can be activated in form_footer.html like this
1846 C<E<lt>input class="submit" type="submit" name="action_sort" id="sort_button" value="[% 'Sort and Save' | $T8 %]"E<gt>>
1847
1848 =back
1849
1850 =head1 TODO
1851
1852 Sort and Save can be implemented as an optional button if configuration ca be set by client config.
1853 Example coding for database scripts and templates in (git show af2f24b8), check also
1854 autogeneration for rose (scripts/rose_auto_create_model.pl --h)