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