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