Prüfen beim Speichern, ob Dokument geändert wurde.
[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->mtime_ischanged('delivery_orders');
712
713   $form->{defaultcurrency} = $form->get_default_currency(\%myconfig);
714
715   $form->isblank("transdate", $locale->text('Delivery Order Date missing!'));
716
717   $form->{donumber} =~ s/^\s*//g;
718   $form->{donumber} =~ s/\s*$//g;
719
720   my $msg = ucfirst $form->{vc};
721   $form->isblank($form->{vc}, $locale->text($msg . " missing!"));
722
723   # $locale->text('Customer missing!');
724   # $locale->text('Vendor missing!');
725
726   remove_emptied_rows();
727   validate_items();
728
729   # if the name changed get new values
730   if (check_name($form->{vc})) {
731     update();
732     ::end_of_request();
733   }
734
735   $form->{id} = 0 if $form->{saveasnew};
736
737   DO->save();
738   # saving the history
739   if(!exists $form->{addition}) {
740     $form->{snumbers} = qq|donumber_| . $form->{donumber};
741     $form->{addition} = "SAVED";
742     $form->save_history;
743   }
744   # /saving the history
745
746   $form->{simple_save} = 1;
747   if (!$params{no_redirect} && !$form->{print_and_save}) {
748     delete @{$form}{ary_diff([keys %{ $form }], [qw(login id script type cursor_fokus)])};
749     edit();
750     ::end_of_request();
751   }
752   $main::lxdebug->leave_sub();
753 }
754
755 sub delete {
756   $main::lxdebug->enter_sub();
757
758   check_do_access();
759
760   my $form     = $main::form;
761   my %myconfig = %main::myconfig;
762   my $locale   = $main::locale;
763
764   if (DO->delete()) {
765     # saving the history
766     if(!exists $form->{addition}) {
767       $form->{snumbers} = qq|donumber_| . $form->{donumber};
768       $form->{addition} = "DELETED";
769       $form->save_history;
770     }
771     # /saving the history
772
773     $form->info($locale->text('Delivery Order deleted!'));
774     ::end_of_request();
775   }
776
777   $form->error($locale->text('Cannot delete delivery order!'));
778
779   $main::lxdebug->leave_sub();
780 }
781
782 sub invoice {
783   $main::lxdebug->enter_sub();
784
785   my $form     = $main::form;
786   my %myconfig = %main::myconfig;
787   my $locale   = $main::locale;
788
789   check_do_access();
790   $form->mtime_ischanged('delivery_orders');
791
792   $main::auth->assert($form->{type} eq 'purchase_delivery_order' ? 'vendor_invoice_edit' : 'invoice_edit');
793
794   $form->{convert_from_do_ids} = $form->{id};
795   $form->{deliverydate}        = $form->{transdate};
796   $form->{transdate}           = $form->{invdate} = $form->current_date(\%myconfig);
797   $form->{duedate}             = $form->current_date(\%myconfig, $form->{invdate}, $form->{terms} * 1);
798   $form->{defaultcurrency}     = $form->get_default_currency(\%myconfig);
799
800   $form->{rowcount}--;
801
802   delete @{$form}{qw(id closed delivered)};
803
804   my ($script, $buysell);
805   if ($form->{type} eq 'purchase_delivery_order') {
806     $form->{title}  = $locale->text('Add Vendor Invoice');
807     $form->{script} = 'ir.pl';
808     $script         = "ir";
809     $buysell        = 'sell';
810
811   } else {
812     $form->{title}  = $locale->text('Add Sales Invoice');
813     $form->{script} = 'is.pl';
814     $script         = "is";
815     $buysell        = 'buy';
816   }
817
818   for my $i (1 .. $form->{rowcount}) {
819     # für bug 1284
820     unless ($form->{"ordnumber"}) {
821       if ($form->{discount}) { # Falls wir einen Lieferanten-/Kundenrabatt haben
822         # und rabattfähig sind, dann
823         unless ($form->{"not_discountable_$i"}) {
824           $form->{"discount_$i"} = $form->{discount}*100; # ... nehmen wir diesen Rabatt
825         }
826       }
827     }
828     map { $form->{"${_}_${i}"} = $form->parse_amount(\%myconfig, $form->{"${_}_${i}"}) if $form->{"${_}_${i}"} } qw(ship qty sellprice lastcost basefactor);
829     $form->{"donumber_$i"} = $form->{donumber};
830     $form->{"converted_from_delivery_order_items_id_$i"} = delete $form->{"delivery_order_items_id_$i"};
831   }
832
833   $form->{type} = "invoice";
834
835   # locale messages
836   $main::locale = Locale->new("$myconfig{countrycode}", "$script");
837   $locale = $main::locale;
838
839   require "bin/mozilla/$form->{script}";
840
841   my $currency = $form->{currency};
842   invoice_links();
843
844   $form->{currency}     = $currency;
845   $form->{exchangerate} = "";
846   $form->{forex}        = $form->check_exchangerate(\%myconfig, $form->{currency}, $form->{invdate}, $buysell);
847   $form->{exchangerate} = $form->{forex} if ($form->{forex});
848
849   prepare_invoice();
850
851   # format amounts
852   for my $i (1 .. $form->{rowcount}) {
853     $form->{"discount_$i"} = $form->format_amount(\%myconfig, $form->{"discount_$i"});
854
855     my ($dec) = ($form->{"sellprice_$i"} =~ /\.(\d+)/);
856     $dec           = length $dec;
857     my $decimalplaces = ($dec > 2) ? $dec : 2;
858
859     # copy delivery date from reqdate for order -> invoice conversion
860     $form->{"deliverydate_$i"} = $form->{"reqdate_$i"}
861       unless $form->{"deliverydate_$i"};
862
863
864     $form->{"sellprice_$i"} =
865       $form->format_amount(\%myconfig, $form->{"sellprice_$i"},
866                            $decimalplaces);
867
868     $form->{"lastcost_$i"} =
869       $form->format_amount(\%myconfig, $form->{"lastcost_$i"},
870                            $decimalplaces);
871
872     (my $dec_qty) = ($form->{"qty_$i"} =~ /\.(\d+)/);
873     $dec_qty = length $dec_qty;
874     $form->{"qty_$i"} =
875       $form->format_amount(\%myconfig, $form->{"qty_$i"}, $dec_qty);
876
877   }
878
879   display_form();
880
881   $main::lxdebug->leave_sub();
882 }
883
884 sub invoice_multi {
885   $main::lxdebug->enter_sub();
886
887   my $form     = $main::form;
888   my %myconfig = %main::myconfig;
889   my $locale   = $main::locale;
890
891   check_do_access();
892   $main::auth->assert($form->{type} eq 'sales_delivery_order' ? 'invoice_edit' : 'vendor_invoice_edit');
893
894   my @do_ids = map { $form->{"trans_id_$_"} } grep { $form->{"multi_id_$_"} } (1..$form->{rowcount});
895
896   if (!scalar @do_ids) {
897     $form->show_generic_error($locale->text('You have not selected any delivery order.'), 'back_button' => 1);
898   }
899
900   map { delete $form->{$_} } grep { m/^(?:trans|multi)_id_\d+/ } keys %{ $form };
901
902   if (!DO->retrieve('vc' => $form->{vc}, 'ids' => \@do_ids)) {
903     $form->show_generic_error($form->{vc} eq 'customer' ?
904                               $locale->text('You cannot create an invoice for delivery orders for different customers.') :
905                               $locale->text('You cannot create an invoice for delivery orders from different vendors.'),
906                               'back_button' => 1);
907   }
908
909   my $source_type              = $form->{type};
910   $form->{convert_from_do_ids} = join ' ', @do_ids;
911   # bei der auswahl von mehreren Lieferscheinen fuer eine Rechnung, die einfach in donumber_array
912   # zwischenspeichern (DO.pm) und als ' '-separierte Liste wieder zurueckschreiben
913   # Hinweis: delete gibt den wert zurueck und loescht danach das element (nett und einfach)
914   # $shell: perldoc perlunc; /delete EXPR
915   $form->{donumber}            = delete $form->{donumber_array};
916   $form->{ordnumber}           = delete $form->{ordnumber_array};
917   $form->{cusordnumber}        = delete $form->{cusordnumber_array};
918   $form->{deliverydate}        = $form->{transdate};
919   $form->{transdate}           = $form->current_date(\%myconfig);
920   $form->{duedate}             = $form->current_date(\%myconfig, $form->{invdate}, $form->{terms} * 1);
921   $form->{type}                = "invoice";
922   $form->{closed}              = 0;
923   $form->{defaultcurrency}     = $form->get_default_currency(\%myconfig);
924
925   my ($script, $buysell);
926   if ($source_type eq 'purchase_delivery_order') {
927     $form->{title}  = $locale->text('Add Vendor Invoice');
928     $form->{script} = 'ir.pl';
929     $script         = "ir";
930     $buysell        = 'sell';
931
932   } else {
933     $form->{title}  = $locale->text('Add Sales Invoice');
934     $form->{script} = 'is.pl';
935     $script         = "is";
936     $buysell        = 'buy';
937   }
938
939   map { delete $form->{$_} } qw(id subject message cc bcc printed emailed queued);
940
941   # get vendor or customer discount
942   my $vc_discount;
943   my $saved_form = save_form();
944   if ($form->{vc} eq 'vendor') {
945     IR->get_vendor(\%myconfig, \%$form);
946     $vc_discount = $form->{vendor_discount};
947   } else {
948     IS->get_customer(\%myconfig, \%$form);
949     $vc_discount = $form->{customer_discount};
950   }
951   restore_form($saved_form);
952
953   $form->{rowcount} = 0;
954   foreach my $ref (@{ $form->{form_details} }) {
955     $form->{rowcount}++;
956     $ref->{reqdate} ||= $ref->{dord_transdate}; # copy transdates into each invoice row
957     map { $form->{"${_}_$form->{rowcount}"} = $ref->{$_} } keys %{ $ref };
958     map { $form->{"${_}_$form->{rowcount}"} = $form->format_amount(\%myconfig, $ref->{$_}) } qw(qty sellprice lastcost);
959     $form->{"converted_from_delivery_order_items_id_$form->{rowcount}"} = delete $form->{"delivery_order_items_id_$form->{rowcount}"};
960
961     if ($vc_discount){ # falls wir einen Lieferanten/Kundenrabatt haben
962       # und keinen anderen discount wert an $i ...
963       $form->{"discount_$form->{rowcount}"} ||= $vc_discount; # ... nehmen wir diesen Rabatt
964     }
965
966     $form->{"discount_$form->{rowcount}"}   = $form->{"discount_$form->{rowcount}"}  * 100; #s.a. Bug 1151
967     # Anm.: Eine Änderung des discounts in der SL/DO.pm->retrieve (select (doi.discount * 100) as discount) ergibt in psql einen
968     # Wert von 10.0000001490116. Ferner ist der Rabatt in der Rechnung dann bei 1.0 (?). Deswegen lasse ich das hier. jb 10.10.09
969
970     $form->{"discount_$form->{rowcount}"} = $form->format_amount(\%myconfig, $form->{"discount_$form->{rowcount}"});
971   }
972   delete $form->{form_details};
973
974   $locale = Locale->new("$myconfig{countrycode}", "$script");
975
976   require "bin/mozilla/$form->{script}";
977
978   invoice_links();
979   prepare_invoice();
980
981   display_form();
982
983   $main::lxdebug->leave_sub();
984 }
985
986 sub save_as_new {
987   $main::lxdebug->enter_sub();
988
989   check_do_access();
990
991   my $form     = $main::form;
992
993   $form->{saveasnew} = 1;
994   $form->{closed}    = 0;
995   $form->{delivered} = 0;
996   map { delete $form->{$_} } qw(printed emailed queued);
997   delete @{ $form }{ grep { m/^stock_(?:in|out)_\d+/ } keys %{ $form } };
998   $form->{"converted_from_delivery_order_items_id_$_"} = delete $form->{"delivery_order_items_id_$_"} for 1 .. $form->{"rowcount"};
999   # Let kivitendo assign a new order number if the user hasn't changed the
1000   # previous one. If it has been changed manually then use it as-is.
1001   $form->{donumber} =~ s/^\s*//g;
1002   $form->{donumber} =~ s/\s*$//g;
1003   if ($form->{saved_donumber} && ($form->{saved_donumber} eq $form->{donumber})) {
1004     delete($form->{donumber});
1005   }
1006
1007   save();
1008
1009   $main::lxdebug->leave_sub();
1010 }
1011
1012 sub e_mail {
1013   $main::lxdebug->enter_sub();
1014
1015   check_do_access();
1016
1017   $::form->mtime_ischanged('delivery_orders','mail');
1018
1019   $::form->{print_and_save} = 1;
1020
1021   my $saved_form = save_form();
1022
1023   save();
1024
1025   restore_form($saved_form, 0, qw(id ordnumber quonumber));
1026
1027   edit_e_mail();
1028
1029   $main::lxdebug->leave_sub();
1030 }
1031
1032 sub calculate_stock_in_out {
1033   $main::lxdebug->enter_sub();
1034
1035   my $form     = $main::form;
1036
1037   my $i = shift;
1038
1039   if (!$form->{"id_${i}"}) {
1040     $main::lxdebug->leave_sub();
1041     return '';
1042   }
1043
1044   my $all_units = AM->retrieve_all_units();
1045
1046   my $in_out   = $form->{type} =~ /^sales/ ? 'out' : 'in';
1047   my $sinfo    = DO->unpack_stock_information('packed' => $form->{"stock_${in_out}_${i}"});
1048
1049   my $do_qty   = AM->sum_with_unit($::form->{"qty_$i"}, $::form->{"unit_$i"});
1050   my $sum      = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $sinfo });
1051   my $matches  = $do_qty == $sum;
1052
1053   my $content  = $form->format_amount_units('amount'      => $sum * 1,
1054                                             'part_unit'   => $form->{"partunit_$i"},
1055                                             'amount_unit' => $all_units->{$form->{"partunit_$i"}}->{base_unit},
1056                                             'conv_units'  => 'convertible_not_smaller',
1057                                             'max_places'  => 2);
1058   $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="?">|;
1059
1060   $main::lxdebug->leave_sub();
1061
1062   return $content;
1063 }
1064
1065 sub get_basic_bin_wh_info {
1066   $main::lxdebug->enter_sub();
1067
1068   my $stock_info = shift;
1069
1070   my $form     = $main::form;
1071
1072   foreach my $sinfo (@{ $stock_info }) {
1073     next unless ($sinfo->{bin_id});
1074
1075     my $bin_info = WH->get_basic_bin_info('id' => $sinfo->{bin_id});
1076     map { $sinfo->{"${_}_description"} = $sinfo->{"${_}description"} = $bin_info->{"${_}_description"} } qw(bin warehouse);
1077   }
1078
1079   $main::lxdebug->leave_sub();
1080 }
1081
1082 sub stock_in_out_form {
1083   $main::lxdebug->enter_sub();
1084
1085   my $form     = $main::form;
1086
1087   if ($form->{in_out} eq 'out') {
1088     stock_out_form();
1089   } else {
1090     stock_in_form();
1091   }
1092
1093   $main::lxdebug->leave_sub();
1094 }
1095
1096 sub redo_stock_info {
1097   $main::lxdebug->enter_sub();
1098
1099   my %params    = @_;
1100
1101   my $form     = $main::form;
1102
1103   my @non_empty = grep { $_->{qty} } @{ $params{stock_info} };
1104
1105   if ($params{add_empty_row}) {
1106     push @non_empty, {
1107       'warehouse_id' => scalar(@non_empty) ? $non_empty[-1]->{warehouse_id} : undef,
1108       'bin_id'       => scalar(@non_empty) ? $non_empty[-1]->{bin_id}       : undef,
1109     };
1110   }
1111
1112   @{ $params{stock_info} } = @non_empty;
1113
1114   $main::lxdebug->leave_sub();
1115 }
1116
1117 sub update_stock_in {
1118   $main::lxdebug->enter_sub();
1119
1120   my $form     = $main::form;
1121   my %myconfig = %main::myconfig;
1122
1123   my $stock_info = [];
1124
1125   foreach my $i (1..$form->{rowcount}) {
1126     $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
1127     push @{ $stock_info }, { map { $_ => $form->{"${_}_${i}"} } qw(warehouse_id bin_id chargenumber
1128                                                                    bestbefore qty unit delivery_order_items_stock_id) };
1129   }
1130
1131   display_stock_in_form($stock_info);
1132
1133   $main::lxdebug->leave_sub();
1134 }
1135
1136 sub stock_in_form {
1137   $main::lxdebug->enter_sub();
1138
1139   my $form     = $main::form;
1140
1141   my $stock_info = DO->unpack_stock_information('packed' => $form->{stock});
1142
1143   display_stock_in_form($stock_info);
1144
1145   $main::lxdebug->leave_sub();
1146 }
1147
1148 sub display_stock_in_form {
1149   $main::lxdebug->enter_sub();
1150
1151   my $stock_info = shift;
1152
1153   my $form     = $main::form;
1154   my %myconfig = %main::myconfig;
1155   my $locale   = $main::locale;
1156
1157   $form->{title} = $locale->text('Stock');
1158
1159   my $part_info  = IC->get_basic_part_info('id' => $form->{parts_id});
1160
1161   # Standardlagerplatz für Standard-Auslagern verwenden, falls keiner für die Ware explizit definiert wurde
1162   if ($::instance_conf->get_transfer_default_use_master_default_bin) {
1163     $part_info->{warehouse_id} ||= $::instance_conf->get_warehouse_id;
1164     $part_info->{bin_id}       ||= $::instance_conf->get_bin_id;
1165   }
1166
1167   my $units      = AM->retrieve_units(\%myconfig, $form);
1168   # der zweite Parameter von unit_select_data gibt den default-Namen (selected) vor
1169   my $units_data = AM->unit_select_data($units, $form->{do_unit}, undef, $part_info->{unit});
1170
1171   $form->get_lists('warehouses' => { 'key'    => 'WAREHOUSES',
1172                                      'bins'   => 'BINS' });
1173
1174   redo_stock_info('stock_info' => $stock_info, 'add_empty_row' => !$form->{delivered});
1175
1176   get_basic_bin_wh_info($stock_info);
1177
1178   $form->header(no_layout => 1);
1179   print $form->parse_html_template('do/stock_in_form', { 'UNITS'      => $units_data,
1180                                                          'STOCK_INFO' => $stock_info,
1181                                                          'PART_INFO'  => $part_info, });
1182
1183   $main::lxdebug->leave_sub();
1184 }
1185
1186 sub _stock_in_out_set_qty_display {
1187   my $stock_info       = shift;
1188   my $form             = $::form;
1189   my $all_units        = AM->retrieve_all_units();
1190   my $sum              = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $stock_info });
1191   $form->{qty_display} = $form->format_amount_units(amount      => $sum * 1,
1192                                                     part_unit   => $form->{partunit},
1193                                                     amount_unit => $all_units->{ $form->{partunit} }->{base_unit},
1194                                                     conv_units  => 'convertible_not_smaller',
1195                                                     max_places  => 2);
1196 }
1197
1198 sub set_stock_in {
1199   $main::lxdebug->enter_sub();
1200
1201   my $form     = $main::form;
1202   my %myconfig = %main::myconfig;
1203
1204   my $stock_info = [];
1205
1206   foreach my $i (1..$form->{rowcount}) {
1207     $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
1208
1209     next if ($form->{"qty_$i"} <= 0);
1210
1211     push @{ $stock_info }, { map { $_ => $form->{"${_}_${i}"} } qw(delivery_order_items_stock_id warehouse_id bin_id chargenumber bestbefore qty unit) };
1212   }
1213
1214   $form->{stock} = YAML::Dump($stock_info);
1215
1216   _stock_in_out_set_qty_display($stock_info);
1217
1218   my $do_qty       = AM->sum_with_unit($::form->parse_amount(\%::myconfig, $::form->{do_qty}), $::form->{do_unit});
1219   my $transfer_qty = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $stock_info });
1220
1221   $form->header();
1222   print $form->parse_html_template('do/set_stock_in_out', {
1223     qty_matches => $do_qty == $transfer_qty,
1224   });
1225
1226   $main::lxdebug->leave_sub();
1227 }
1228
1229 sub stock_out_form {
1230   $main::lxdebug->enter_sub();
1231
1232   my $form     = $main::form;
1233   my %myconfig = %main::myconfig;
1234   my $locale   = $main::locale;
1235
1236   $form->{title} = $locale->text('Release From Stock');
1237
1238   my $part_info  = IC->get_basic_part_info('id' => $form->{parts_id});
1239
1240   my $units      = AM->retrieve_units(\%myconfig, $form);
1241   my $units_data = AM->unit_select_data($units, undef, undef, $part_info->{unit});
1242
1243   my @contents   = DO->get_item_availability('parts_id' => $form->{parts_id});
1244
1245   my $stock_info = DO->unpack_stock_information('packed' => $form->{stock});
1246
1247   if (!$form->{delivered}) {
1248     foreach my $row (@contents) {
1249       $row->{available_qty} = $form->format_amount_units('amount'      => $row->{qty} * 1,
1250                                                          'part_unit'   => $part_info->{unit},
1251                                                          'conv_units'  => 'convertible_not_smaller',
1252                                                          'max_places'  => 2);
1253
1254       foreach my $sinfo (@{ $stock_info }) {
1255         next if (($row->{bin_id}       != $sinfo->{bin_id}) ||
1256                  ($row->{warehouse_id} != $sinfo->{warehouse_id}) ||
1257                  ($row->{chargenumber} ne $sinfo->{chargenumber}) ||
1258                  ($row->{bestbefore}   ne $sinfo->{bestbefore}));
1259
1260         map { $row->{"stock_$_"} = $sinfo->{$_} } qw(qty unit error delivery_order_items_stock_id);
1261       }
1262     }
1263
1264   } else {
1265     get_basic_bin_wh_info($stock_info);
1266
1267     foreach my $sinfo (@{ $stock_info }) {
1268       map { $sinfo->{"stock_$_"} = $sinfo->{$_} } qw(qty unit);
1269     }
1270   }
1271
1272   $form->header(no_layout => 1);
1273   print $form->parse_html_template('do/stock_out_form', { 'UNITS'      => $units_data,
1274                                                           'WHCONTENTS' => $form->{delivered} ? $stock_info : \@contents,
1275                                                           'PART_INFO'  => $part_info, });
1276
1277   $main::lxdebug->leave_sub();
1278 }
1279
1280 sub set_stock_out {
1281   $main::lxdebug->enter_sub();
1282
1283   my $form     = $main::form;
1284   my %myconfig = %main::myconfig;
1285   my $locale   = $main::locale;
1286
1287   my $stock_info = [];
1288
1289   foreach my $i (1 .. $form->{rowcount}) {
1290     $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
1291
1292     next if ($form->{"qty_$i"} <= 0);
1293
1294     push @{ $stock_info }, {
1295       'warehouse_id' => $form->{"warehouse_id_$i"},
1296       'bin_id'       => $form->{"bin_id_$i"},
1297       'chargenumber' => $form->{"chargenumber_$i"},
1298       'bestbefore'   => $form->{"bestbefore_$i"},
1299       'qty'          => $form->{"qty_$i"},
1300       'unit'         => $form->{"unit_$i"},
1301       'row'          => $i,
1302       'delivery_order_items_stock_id'  => $form->{"delivery_order_items_stock_id_$i"},
1303     };
1304   }
1305
1306   my @errors     = DO->check_stock_availability('requests' => $stock_info,
1307                                                 'parts_id' => $form->{parts_id});
1308
1309   $form->{stock} = YAML::Dump($stock_info);
1310
1311   if (@errors) {
1312     $form->{ERRORS} = [];
1313     map { push @{ $form->{ERRORS} }, $locale->text('Error in row #1: The quantity you entered is bigger than the stocked quantity.', $_->{row}); } @errors;
1314     stock_in_out_form();
1315
1316   } else {
1317     _stock_in_out_set_qty_display($stock_info);
1318
1319     my $do_qty       = AM->sum_with_unit($::form->parse_amount(\%::myconfig, $::form->{do_qty}), $::form->{do_unit});
1320     my $transfer_qty = AM->sum_with_unit(map { $_->{qty}, $_->{unit} } @{ $stock_info });
1321
1322     $form->header();
1323     print $form->parse_html_template('do/set_stock_in_out', {
1324       qty_matches => $do_qty == $transfer_qty,
1325     });
1326   }
1327
1328   $main::lxdebug->leave_sub();
1329 }
1330
1331 sub transfer_in {
1332   $main::lxdebug->enter_sub();
1333
1334   my $form     = $main::form;
1335   my %myconfig = %main::myconfig;
1336   my $locale   = $main::locale;
1337
1338   if ($form->{id} && DO->is_marked_as_delivered(id => $form->{id})) {
1339     $form->show_generic_error($locale->text('The parts for this delivery order have already been transferred in.'), 'back_button' => 1);
1340   }
1341
1342   save(no_redirect => 1);
1343
1344   my @part_ids = map { $form->{"id_${_}"} } grep { $form->{"id_${_}"} && $form->{"stock_in_${_}"} } (1 .. $form->{rowcount});
1345   my @all_requests;
1346
1347   if (@part_ids) {
1348     my $units         = AM->retrieve_units(\%myconfig, $form);
1349     my %part_info_map = IC->get_basic_part_info('id' => \@part_ids);
1350     my %request_map;
1351
1352     $form->{ERRORS}   = [];
1353
1354     foreach my $i (1 .. $form->{rowcount}) {
1355       next unless ($form->{"id_$i"} && $form->{"stock_in_$i"});
1356
1357       my $row_sum_base_qty = 0;
1358       my $base_unit_factor = $units->{ $part_info_map{$form->{"id_$i"}}->{unit} }->{factor} || 1;
1359
1360       foreach my $request (@{ DO->unpack_stock_information('packed' => $form->{"stock_in_$i"}) }) {
1361         $request->{parts_id}  = $form->{"id_$i"};
1362         $row_sum_base_qty    += $request->{qty} * $units->{$request->{unit}}->{factor} / $base_unit_factor;
1363
1364         $request->{project_id} = $form->{"project_id_$i"} || $form->{globalproject_id};
1365
1366         push @all_requests, $request;
1367       }
1368
1369       next if (0 == $row_sum_base_qty);
1370
1371       my $do_base_qty = $form->parse_amount(\%myconfig, $form->{"qty_$i"}) * $units->{$form->{"unit_$i"}}->{factor} / $base_unit_factor;
1372
1373 #      if ($do_base_qty != $row_sum_base_qty) {
1374 #        push @{ $form->{ERRORS} }, $locale->text('Error in position #1: You must either assign no stock at all or the full quantity of #2 #3.',
1375 #                                                 $i, $form->{"qty_$i"}, $form->{"unit_$i"});
1376 #      }
1377     }
1378
1379     if (@{ $form->{ERRORS} }) {
1380       push @{ $form->{ERRORS} }, $locale->text('The delivery order has not been marked as delivered. The warehouse contents have not changed.');
1381
1382       set_headings('edit');
1383       update();
1384       $main::lxdebug->leave_sub();
1385
1386       ::end_of_request();
1387     }
1388   }
1389
1390   DO->transfer_in_out('direction' => 'in',
1391                       'requests'  => \@all_requests);
1392
1393   SL::DB::DeliveryOrder->new(id => $form->{id})->load->update_attributes(delivered => 1);
1394
1395   $form->{callback} = 'do.pl?action=edit&type=purchase_delivery_order&id=' . $form->escape($form->{id});
1396   $form->redirect;
1397
1398   $main::lxdebug->leave_sub();
1399 }
1400
1401 sub transfer_out {
1402   $main::lxdebug->enter_sub();
1403
1404   my $form     = $main::form;
1405   my %myconfig = %main::myconfig;
1406   my $locale   = $main::locale;
1407
1408   if ($form->{id} && DO->is_marked_as_delivered(id => $form->{id})) {
1409     $form->show_generic_error($locale->text('The parts for this delivery order have already been transferred out.'), 'back_button' => 1);
1410   }
1411
1412   save(no_redirect => 1);
1413
1414   my @part_ids = map { $form->{"id_${_}"} } grep { $form->{"id_${_}"} && $form->{"stock_out_${_}"} } (1 .. $form->{rowcount});
1415   my @all_requests;
1416
1417   if (@part_ids) {
1418     my $units         = AM->retrieve_units(\%myconfig, $form);
1419     my %part_info_map = IC->get_basic_part_info('id' => \@part_ids);
1420     my %request_map;
1421
1422     $form->{ERRORS}   = [];
1423
1424     foreach my $i (1 .. $form->{rowcount}) {
1425       next unless ($form->{"id_$i"} && $form->{"stock_out_$i"});
1426
1427       my $row_sum_base_qty = 0;
1428       my $base_unit_factor = $units->{ $part_info_map{$form->{"id_$i"}}->{unit} }->{factor} || 1;
1429
1430       foreach my $request (@{ DO->unpack_stock_information('packed' => $form->{"stock_out_$i"}) }) {
1431         $request->{parts_id} = $form->{"id_$i"};
1432         $request->{base_qty} = $request->{qty} * $units->{$request->{unit}}->{factor} / $base_unit_factor;
1433         $request->{project_id} = $form->{"project_id_$i"} ? $form->{"project_id_$i"} : $form->{globalproject_id};
1434
1435         my $map_key          = join '--', ($form->{"id_$i"}, @{$request}{qw(warehouse_id bin_id chargenumber bestbefore)});
1436
1437         $request_map{$map_key}                 ||= $request;
1438         $request_map{$map_key}->{sum_base_qty} ||= 0;
1439         $request_map{$map_key}->{sum_base_qty}  += $request->{base_qty};
1440         $row_sum_base_qty                       += $request->{base_qty};
1441
1442         push @all_requests, $request;
1443       }
1444
1445       next if (0 == $row_sum_base_qty);
1446
1447       my $do_base_qty = $form->{"qty_$i"} * $units->{$form->{"unit_$i"}}->{factor} / $base_unit_factor;
1448
1449 #      if ($do_base_qty != $row_sum_base_qty) {
1450 #        push @{ $form->{ERRORS} }, $locale->text('Error in position #1: You must either assign no transfer at all or the full quantity of #2 #3.',
1451 #                                                 $i, $form->{"qty_$i"}, $form->{"unit_$i"});
1452 #      }
1453     }
1454
1455     if (%request_map) {
1456       my @bin_ids      = map { $_->{bin_id} } values %request_map;
1457       my %bin_info_map = WH->get_basic_bin_info('id' => \@bin_ids);
1458       my @contents     = DO->get_item_availability('parts_id' => \@part_ids);
1459
1460       foreach my $inv (@contents) {
1461         my $map_key = join '--', @{$inv}{qw(parts_id warehouse_id bin_id chargenumber bestbefore)};
1462
1463         next unless ($request_map{$map_key});
1464
1465         my $request    = $request_map{$map_key};
1466         $request->{ok} = $request->{sum_base_qty} <= $inv->{qty};
1467       }
1468
1469       foreach my $request (values %request_map) {
1470         next if ($request->{ok});
1471
1472         my $pinfo = $part_info_map{$request->{parts_id}};
1473         my $binfo = $bin_info_map{$request->{bin_id}};
1474
1475         if ($::instance_conf->get_show_bestbefore) {
1476             push @{ $form->{ERRORS} }, $locale->text("There is not enough available of '#1' at warehouse '#2', bin '#3', #4, #5, for the transfer of #6.",
1477                                                      $pinfo->{description},
1478                                                      $binfo->{warehouse_description},
1479                                                      $binfo->{bin_description},
1480                                                      $request->{chargenumber} ? $locale->text('chargenumber #1', $request->{chargenumber}) : $locale->text('no chargenumber'),
1481                                                      $request->{bestbefore} ? $locale->text('bestbefore #1', $request->{bestbefore}) : $locale->text('no bestbefore'),
1482                                                      $form->format_amount_units('amount'      => $request->{sum_base_qty},
1483                                                                                 'part_unit'   => $pinfo->{unit},
1484                                                                                 'conv_units'  => 'convertible_not_smaller'));
1485         } else {
1486             push @{ $form->{ERRORS} }, $locale->text("There is not enough available of '#1' at warehouse '#2', bin '#3', #4, for the transfer of #5.",
1487                                                      $pinfo->{description},
1488                                                      $binfo->{warehouse_description},
1489                                                      $binfo->{bin_description},
1490                                                      $request->{chargenumber} ? $locale->text('chargenumber #1', $request->{chargenumber}) : $locale->text('no chargenumber'),
1491                                                      $form->format_amount_units('amount'      => $request->{sum_base_qty},
1492                                                                                 'part_unit'   => $pinfo->{unit},
1493                                                                                 'conv_units'  => 'convertible_not_smaller'));
1494         }
1495       }
1496     }
1497
1498     if (@{ $form->{ERRORS} }) {
1499       push @{ $form->{ERRORS} }, $locale->text('The delivery order has not been marked as delivered. The warehouse contents have not changed.');
1500
1501       set_headings('edit');
1502       update();
1503       $main::lxdebug->leave_sub();
1504
1505       ::end_of_request();
1506     }
1507   }
1508   DO->transfer_in_out('direction' => 'out',
1509                       'requests'  => \@all_requests);
1510
1511   SL::DB::DeliveryOrder->new(id => $form->{id})->load->update_attributes(delivered => 1);
1512
1513   $form->{callback} = 'do.pl?action=edit&type=sales_delivery_order&id=' . $form->escape($form->{id});
1514   $form->redirect;
1515
1516   $main::lxdebug->leave_sub();
1517 }
1518
1519 sub mark_closed {
1520   $main::lxdebug->enter_sub();
1521
1522   my $form     = $main::form;
1523
1524   DO->close_orders('ids' => [ $form->{id} ]);
1525
1526   $form->{closed} = 1;
1527
1528   update();
1529
1530   $main::lxdebug->leave_sub();
1531 }
1532
1533
1534 sub yes {
1535   call_sub($main::form->{yes_nextsub});
1536 }
1537
1538 sub no {
1539   call_sub($main::form->{no_nextsub});
1540 }
1541
1542 sub update {
1543   call_sub($main::form->{update_nextsub} || $main::form->{nextsub} || 'update_delivery_order');
1544 }
1545
1546 sub dispatcher {
1547   my $form     = $main::form;
1548   my $locale   = $main::locale;
1549
1550   foreach my $action (qw(update ship_to print e_mail save transfer_out transfer_out_default sort
1551                          transfer_in transfer_in_default mark_closed save_as_new invoice delete)) {
1552     if ($form->{"action_${action}"}) {
1553       call_sub($action);
1554       return;
1555     }
1556   }
1557
1558   $form->error($locale->text('No action defined.'));
1559 }
1560
1561 sub transfer_out_default {
1562   $main::lxdebug->enter_sub();
1563
1564   my $form     = $main::form;
1565
1566   transfer_in_out_default('direction' => 'out');
1567
1568   $main::lxdebug->leave_sub();
1569 }
1570
1571 sub transfer_in_default {
1572   $main::lxdebug->enter_sub();
1573
1574   my $form     = $main::form;
1575
1576   transfer_in_out_default('direction' => 'in');
1577
1578   $main::lxdebug->leave_sub();
1579 }
1580
1581 # Falls das Standardlagerverfahren aktiv ist, wird
1582 # geprüft, ob alle Standardlagerplätze für die Auslager-
1583 # artikel vorhanden sind UND ob die Warenmenge ausreicht zum
1584 # Auslagern. Falls nicht wird entsprechend eine Fehlermeldung
1585 # generiert. Offen Chargennummer / bestbefore wird nicht berücksichtigt
1586 sub transfer_in_out_default {
1587   $main::lxdebug->enter_sub();
1588
1589   my $form     = $main::form;
1590   my %myconfig = %main::myconfig;
1591   my $locale   = $main::locale;
1592   my %params   = @_;
1593
1594   my (%missing_default_bins, %qty_parts, @all_requests, %part_info_map, $default_warehouse_id, $default_bin_id);
1595
1596   Common::check_params(\%params, qw(direction));
1597
1598   # entsprechende defaults holen, falls standardlagerplatz verwendet werden soll
1599   if ($::instance_conf->get_transfer_default_use_master_default_bin) {
1600     $default_warehouse_id = $::instance_conf->get_warehouse_id;
1601     $default_bin_id       = $::instance_conf->get_bin_id;
1602   }
1603
1604
1605   my @part_ids = map { $form->{"id_${_}"} } (1 .. $form->{rowcount});
1606   if (@part_ids) {
1607     my $units         = AM->retrieve_units(\%myconfig, $form);
1608     %part_info_map = IC->get_basic_part_info('id' => \@part_ids);
1609     foreach my $i (1 .. $form->{rowcount}) {
1610       next unless ($form->{"id_$i"});
1611       my $base_unit_factor = $units->{ $part_info_map{$form->{"id_$i"}}->{unit} }->{factor} || 1;
1612       my $qty =   $form->parse_amount(\%myconfig, $form->{"qty_$i"}) * $units->{$form->{"unit_$i"}}->{factor} / $base_unit_factor;
1613
1614       $form->show_generic_error($locale->text("Cannot transfer negative entries." ), 'back_button' => 1) if ($qty < 0);
1615       # if we do not want to transfer services and this part is a service, set qty to zero
1616       # ... and do not create a hash entry in %qty_parts below (will skip check for bins for the transfer == out case)
1617       # ... 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)
1618
1619       $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});
1620       $qty_parts{$form->{"id_$i"}} += $qty;
1621       if ($qty == 0) {
1622         delete $qty_parts{$form->{"id_$i"}} unless $qty_parts{$form->{"id_$i"}};
1623         undef $form->{"stock_in_$i"};
1624       }
1625
1626       $part_info_map{$form->{"id_$i"}}{bin_id}       ||= $default_bin_id;
1627       $part_info_map{$form->{"id_$i"}}{warehouse_id} ||= $default_warehouse_id;
1628
1629       push @all_requests, ($qty == 0) ? { } : {
1630                         'chargenumber' => '',  #?? die müsste entsprechend geholt werden
1631                         #'bestbefore' => undef, # TODO wird nicht berücksichtigt
1632                         'bin_id' => $part_info_map{$form->{"id_$i"}}{bin_id},
1633                         'qty' => $qty,
1634                         'parts_id' => $form->{"id_$i"},
1635                         'comment' => $locale->text("Default transfer delivery order"),
1636                         'unit' => $part_info_map{$form->{"id_$i"}}{unit},
1637                         'warehouse_id' => $part_info_map{$form->{"id_$i"}}{warehouse_id},
1638                         'oe_id' => $form->{id},
1639                         'project_id' => $form->{"project_id_$i"} ? $form->{"project_id_$i"} : $form->{globalproject_id}
1640                       };
1641     }
1642
1643     # jetzt wird erst überprüft, ob die Stückzahl entsprechend stimmt.
1644     # check if bin (transfer in and transfer out and qty (transfer out) is correct
1645     foreach my $key (keys %qty_parts) {
1646
1647       $missing_default_bins{$key}{missing_bin} = 1 unless ($part_info_map{$key}{bin_id});
1648       next unless ($part_info_map{$key}{bin_id}); # abbruch
1649
1650       if ($params{direction} eq 'out') {  # wird nur für ausgehende Mengen benötigt
1651         my ($max_qty, $error) = WH->get_max_qty_parts_bin(parts_id => $key, bin_id => $part_info_map{$key}{bin_id});
1652         if ($error == 1) {
1653           # wir können nicht entscheiden, welche charge oder mhd (bestbefore) ausgewählt sein soll
1654           # deshalb rückmeldung nach oben geben, manuell auszulagern
1655           # TODO Bei nur einem Treffer mit Charge oder bestbefore wäre das noch möglich
1656           $missing_default_bins{$key}{chargenumber} = 1;
1657         }
1658         if ($max_qty < $qty_parts{$key}){
1659           $missing_default_bins{$key}{missing_qty} = $max_qty - $qty_parts{$key};
1660         }
1661       }
1662     }
1663   } # if @parts_id
1664
1665   # Abfrage für Fehlerbehandlung (nur bei direction == out)
1666   if (scalar (keys %missing_default_bins)) {
1667     my $fehlertext;
1668     foreach my $fehler (keys %missing_default_bins) {
1669
1670       my $ware = WH->get_part_description(parts_id => $fehler);
1671       if ($missing_default_bins{$fehler}{missing_bin}){
1672         $fehlertext .= "Kein Standardlagerplatz definiert bei $ware <br>";
1673       }
1674       if ($missing_default_bins{$fehler}{missing_qty}) {  # missing_qty
1675         $fehlertext .= "Es fehlen " . $missing_default_bins{$fehler}{missing_qty}*-1 .
1676                        " von $ware auf dem Standard-Lagerplatz " . $part_info_map{$fehler}{bin} .   " zum Auslagern<br>";
1677       }
1678       if ($missing_default_bins{$fehler}{chargenumber}){
1679         $fehlertext .= "Die Ware hat eine Chargennummer oder eine Mindesthaltbarkeit definiert.
1680                         Hier kann man nicht automatisch entscheiden.
1681                         Bitte diesen Lieferschein manuell auslagern.
1682                         Bei: $ware";
1683       }
1684       # auslagern soll immer gehen, auch wenn nicht genügend auf lager ist.
1685       # der lagerplatz ist hier extra konfigurierbar, bspw. Lager-Korrektur mit
1686       # Lagerplatz Lagerplatz-Korrektur
1687       my $default_warehouse_id_ignore_onhand = $::instance_conf->get_warehouse_id_ignore_onhand;
1688       my $default_bin_id_ignore_onhand       = $::instance_conf->get_bin_id_ignore_onhand;
1689       if ($::instance_conf->get_transfer_default_ignore_onhand && $default_bin_id_ignore_onhand) {
1690         # entsprechende defaults holen
1691         # falls chargenumber, bestbefore oder anzahl nicht stimmt, auf automatischen
1692         # lagerplatz wegbuchen!
1693         foreach (@all_requests) {
1694           if ($_->{parts_id} eq $fehler){
1695           $_->{bin_id}        = $default_bin_id_ignore_onhand;
1696           $_->{warehouse_id}  = $default_warehouse_id_ignore_onhand;
1697           }
1698         }
1699       } else {
1700         #$main::lxdebug->message(0, 'Fehlertext: ' . $fehlertext);
1701         $form->show_generic_error($locale->text("Cannot transfer. <br> Reason:<br>#1", $fehlertext ), 'back_button' => 1);
1702       }
1703     }
1704   }
1705
1706
1707   # hier der eigentliche fallunterschied für in oder out
1708   my $prefix   = $params{direction} eq 'in' ? 'in' : 'out';
1709
1710   # dieser array_ref ist für DO->save da:
1711   # einmal die all_requests in YAML verwandeln, damit delivery_order_items_stock
1712   # gefüllt werden kann.
1713   my $i = 0;
1714   foreach (@all_requests){
1715     $i++;
1716     next unless scalar(%{ $_ });
1717     $form->{"stock_${prefix}_$i"} = YAML::Dump([$_]);
1718   }
1719
1720   save(no_redirect => 1); # Wir können auslagern, deshalb beleg speichern
1721                           # und in delivery_order_items_stock speichern
1722   DO->transfer_in_out('direction' => $prefix,
1723                       'requests'  => \@all_requests);
1724
1725   SL::DB::DeliveryOrder->new(id => $form->{id})->load->update_attributes(delivered => 1);
1726
1727   $form->{callback} = 'do.pl?action=edit&type=sales_delivery_order&id=' . $form->escape($form->{id}) if $params{direction} eq 'out';
1728   $form->{callback} = 'do.pl?action=edit&type=purchase_delivery_order&id=' . $form->escape($form->{id}) if $params{direction} eq 'in';
1729   $form->redirect;
1730
1731 }
1732
1733 sub sort {
1734   $main::lxdebug->enter_sub();
1735
1736   check_do_access();
1737
1738   my $form     = $main::form;
1739   my %temp_hash;
1740
1741   croak ("Delivery Order needs to be saved") unless $form->{id};
1742
1743   # hashify partnumbers, positions. key is delivery_order_items_id
1744   for my $i (1 .. ($form->{rowcount}) ) {
1745     $temp_hash{$form->{"delivery_order_items_id_$i"}} = { runningnumber => $form->{"runningnumber_$i"}, partnumber => $form->{"partnumber_$i"} };
1746   }
1747   # naturally sort partnumbers and get a sorted array of doi_ids
1748   my @sorted_doi_ids =  sort { Sort::Naturally::ncmp($temp_hash{$a}->{"partnumber"}, $temp_hash{$b}->{"partnumber"}) }  keys %temp_hash;
1749
1750
1751   my $new_number = 1;
1752
1753   for (@sorted_doi_ids) {
1754     $form->{"runningnumber_$temp_hash{$_}->{runningnumber}"} = $new_number;
1755     $new_number++;
1756   }
1757     $main::lxdebug->leave_sub();
1758     save();
1759 }
1760
1761 __END__
1762
1763 =pod
1764
1765 =encoding utf8
1766
1767 =head1 NAME
1768
1769 do.pl - Script for all calls to delivery order
1770
1771
1772 =head1 FUNCTIONS
1773
1774 =over 2
1775
1776 =item C<sort>
1777
1778 Sorts all position with Natural Sort. Can be activated in form_footer.html like this
1779 C<E<lt>input class="submit" type="submit" name="action_sort" id="sort_button" value="[% 'Sort and Save' | $T8 %]"E<gt>>
1780
1781 =back
1782
1783 =head1 TODO
1784
1785 Sort and Save can be implemented as an optional button if configuration ca be set by client config.
1786 Example coding for database scripts and templates in (git show af2f24b8), check also
1787 autogeneration for rose (scripts/rose_auto_create_model.pl --h)