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