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