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