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