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