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