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