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