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