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