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