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