ActionBar: Verwendung in Berichtsfunktion in do.pl
[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 setup_do_search_action_bar {
354   my %params = @_;
355
356   for my $bar ($::request->layout->get('actionbar')) {
357     $bar->add(
358       action => [
359         t8('Search'),
360         submit    => [ '#form' ],
361         accesskey => 'enter',
362       ],
363     );
364   }
365 }
366
367 sub setup_do_orders_action_bar {
368   my %params = @_;
369
370   for my $bar ($::request->layout->get('actionbar')) {
371     $bar->add(
372       action => [
373         t8('New invoice'),
374         submit    => [ '#orders_form' ],
375         checks    => [ 'kivi.DeliveryOrder.multi_invoice_check_delivery_orders_selected' ],
376         accesskey => 'enter',
377       ],
378     );
379   }
380 }
381
382 sub form_header {
383   $main::lxdebug->enter_sub();
384
385   check_do_access();
386
387   my $form     = $main::form;
388   my %myconfig = %main::myconfig;
389
390   my $class       = "SL::DB::" . ($form->{vc} eq 'customer' ? 'Customer' : 'Vendor');
391   $form->{VC_OBJ} = $class->load_cached($form->{ $form->{vc} . '_id' });
392
393   $form->{employee_id} = $form->{old_employee_id} if $form->{old_employee_id};
394   $form->{salesman_id} = $form->{old_salesman_id} if $form->{old_salesman_id};
395
396   my $vc = $form->{vc} eq "customer" ? "customers" : "vendors";
397   $form->get_lists("price_factors"  => "ALL_PRICE_FACTORS",
398                    "business_types" => "ALL_BUSINESS_TYPES",
399     );
400   $form->{ALL_DEPARTMENTS} = SL::DB::Manager::Department->get_all;
401
402   # Projects
403   my @old_project_ids = uniq grep { $_ } map { $_ * 1 } ($form->{"globalproject_id"}, map { $form->{"project_id_$_"} } 1..$form->{"rowcount"});
404   my @old_ids_cond    = @old_project_ids ? (id => \@old_project_ids) : ();
405   my @customer_cond;
406   if (($vc eq 'customers') && $::instance_conf->get_customer_projects_only_in_sales) {
407     @customer_cond = (
408       or => [
409         customer_id          => $::form->{customer_id},
410         billable_customer_id => $::form->{customer_id},
411       ]);
412   }
413   my @conditions = (
414     or => [
415       and => [ active => 1, @customer_cond ],
416       @old_ids_cond,
417     ]);
418
419   $::form->{ALL_PROJECTS}          = SL::DB::Manager::Project->get_all_sorted(query => \@conditions);
420   $::form->{ALL_EMPLOYEES}         = SL::DB::Manager::Employee->get_all_sorted(query => [ or => [ id => $::form->{employee_id},  deleted => 0 ] ]);
421   $::form->{ALL_SALESMEN}          = SL::DB::Manager::Employee->get_all_sorted(query => [ or => [ id => $::form->{salesman_id},  deleted => 0 ] ]);
422   $::form->{ALL_SHIPTO}            = SL::DB::Manager::Shipto->get_all_sorted(query => [
423     or => [ trans_id  => $::form->{"$::form->{vc}_id"} * 1, and => [ shipto_id => $::form->{shipto_id} * 1, trans_id => undef ] ]
424   ]);
425   $::form->{ALL_CONTACTS}          = SL::DB::Manager::Contact->get_all_sorted(query => [
426     or => [
427       cp_cv_id => $::form->{"$::form->{vc}_id"} * 1,
428       and      => [
429         cp_cv_id => undef,
430         cp_id    => $::form->{cp_id} * 1
431       ]
432     ]
433   ]);
434
435   my $dispatch_to_popup = '';
436   if ($form->{resubmit} && ($form->{format} eq "html")) {
437     $dispatch_to_popup  = "window.open('about:blank','Beleg'); document.do.target = 'Beleg';";
438     $dispatch_to_popup .= "document.do.submit();";
439   } elsif ($form->{resubmit}) {
440     # emulate click for resubmitting actions
441     $dispatch_to_popup  = "document.do.${_}.click(); " for grep { /^action_/ } keys %$form;
442   }
443   $::request->{layout}->add_javascripts_inline("\$(function(){$dispatch_to_popup});");
444
445
446   $form->{follow_up_trans_info} = $form->{donumber} .'('. $form->{VC_OBJ}->name .')';
447
448   $::request->{layout}->use_javascript(map { "${_}.js" } qw(kivi.File kivi.MassDeliveryOrderPrint kivi.SalesPurchase kivi.Part ckeditor/ckeditor ckeditor/adapters/jquery kivi.io autocomplete_customer));
449
450   my @custom_hidden;
451   push @custom_hidden, map { "shiptocvar_" . $_->name } @{ SL::DB::Manager::CustomVariableConfig->get_all(where => [ module => 'ShipTo' ]) };
452
453   $::form->{HIDDENS} = [ map { +{ name => $_, value => $::form->{$_} } } (@custom_hidden) ];
454
455   setup_do_action_bar();
456
457   $form->header();
458   # Fix für Bug 1082 Erwartet wird: 'abteilungsNAME--abteilungsID'
459   # und Erweiterung für Bug 1760:
460   # Das war leider nur ein Teil-Fix, da das Verhalten den 'Erneuern'-Knopf
461   # nicht überlebt. Konsequent jetzt auf L umgestellt
462   #   $ perldoc SL::Template::Plugin::L
463   # Daher entsprechend nur die Anpassung in form_header
464   # und in DO.pm gemacht. 4 Testfälle:
465   # department_id speichern                 | i.O.
466   # department_id lesen                     | i.O.
467   # department leer überlebt erneuern       | i.O.
468   # department nicht leer überlebt erneuern | i.O.
469   # $main::lxdebug->message(0, 'ABTEILUNGS ID in form?' . $form->{department_id});
470   print $form->parse_html_template('do/form_header');
471
472   $main::lxdebug->leave_sub();
473 }
474
475 sub form_footer {
476   $main::lxdebug->enter_sub();
477
478   check_do_access();
479
480   my $form     = $main::form;
481
482   $form->{PRINT_OPTIONS} = print_options('inline' => 1);
483   $form->{ALL_DELIVERY_TERMS} = SL::DB::Manager::DeliveryTerm->get_all_sorted();
484
485   print $form->parse_html_template('do/form_footer',
486     {transfer_default         => ($::instance_conf->get_transfer_default)});
487
488   $main::lxdebug->leave_sub();
489 }
490
491 sub update_delivery_order {
492   $main::lxdebug->enter_sub();
493
494   check_do_access();
495
496   my $form     = $main::form;
497   my %myconfig = %main::myconfig;
498
499   set_headings($form->{"id"} ? "edit" : "add");
500
501   $form->{insertdate} = SL::DB::DeliveryOrder->new(id => $form->{id})->load->itime_as_date if $form->{id};
502
503   $form->{update} = 1;
504
505   my $payment_id;
506   $payment_id = $form->{payment_id} if $form->{payment_id};
507
508   my $vc = $form->{vc};
509   if (($form->{"previous_${vc}_id"} || $form->{"${vc}_id"}) != $form->{"${vc}_id"}) {
510     $::form->{salesman_id} = SL::DB::Manager::Employee->current->id if exists $::form->{salesman_id};
511
512     IS->get_customer(\%myconfig, $form) if $vc eq 'customer';
513     IR->get_vendor(\%myconfig, $form)   if $vc eq 'vendor';
514   }
515
516   $form->{discount} =  $form->{"$form->{vc}_discount"} if defined $form->{"$form->{vc}_discount"};
517   # Problem: Wenn man ohne Erneuern einen Kunden/Lieferanten
518   # wechselt, wird der entsprechende Kunden/ Lieferantenrabatt
519   # nicht übernommen. Grundproblem: In Commit 82574e78
520   # hab ich aus discount customer_discount und vendor_discount
521   # gemacht und entsprechend an den Oberflächen richtig hin-
522   # geschoben. Die damals bessere Lösung wäre gewesen:
523   # In den Templates nur die hidden für form-discount wieder ein-
524   # setzen dann wäre die Verrenkung jetzt nicht notwendig.
525   # TODO: Ggf. Bugfix 1284, 1575 und 817 wieder zusammenführen
526   # Testfälle: Kunden mit Rabatt 0 -> Rabatt 20 i.O.
527   #            Kunde mit Rabatt 20 -> Rabatt 0  i.O.
528   #            Kunde mit Rabatt 20 -> Rabatt 5,5 i.O.
529   $form->{payment_id} = $payment_id if $form->{payment_id} eq "";
530
531   my $i = $form->{rowcount};
532
533   if (   ($form->{"partnumber_$i"} eq "")
534       && ($form->{"description_$i"} eq "")
535       && ($form->{"partsgroup_$i"}  eq "")) {
536
537     check_form();
538
539   } else {
540
541     my $mode;
542     if ($form->{type} eq 'purchase_delivery_order') {
543       IR->retrieve_item(\%myconfig, $form);
544       $mode = 'IR';
545     } else {
546       IS->retrieve_item(\%myconfig, $form);
547       $mode = 'IS';
548     }
549
550     my $rows = scalar @{ $form->{item_list} };
551
552     if ($rows) {
553       $form->{"qty_$i"} = $form->parse_amount(\%myconfig, $form->{"qty_$i"});
554       if( !$form->{"qty_$i"} ) {
555         $form->{"qty_$i"} = 1;
556       }
557
558       if ($rows > 1) {
559
560         select_item(mode => $mode, pre_entered_qty => $form->{"qty_$i"});
561         $::dispatcher->end_request;
562
563       } else {
564
565         my $sellprice = $form->parse_amount(\%myconfig, $form->{"sellprice_$i"});
566
567         map { $form->{"${_}_$i"} = $form->{item_list}[0]{$_} } keys %{ $form->{item_list}[0] };
568
569         $form->{"marge_price_factor_$i"} = $form->{item_list}->[0]->{price_factor};
570
571         if ($sellprice) {
572           $form->{"sellprice_$i"} = $sellprice;
573         } else {
574           my $record        = _make_record();
575           my $price_source  = SL::PriceSource->new(record_item => $record->items->[$i-1], record => $record);
576           my $best_price    = $price_source->best_price;
577           my $best_discount = $price_source->best_discount;
578
579           if ($best_price) {
580             $::form->{"sellprice_$i"}           = $best_price->price;
581             $::form->{"active_price_source_$i"} = $best_price->source;
582           }
583           if ($best_discount) {
584             $::form->{"discount_$i"}               = $best_discount->discount;
585             $::form->{"active_discount_source_$i"} = $best_discount->source;
586           }
587         }
588
589         $form->{"sellprice_$i"}          = $form->format_amount(\%myconfig, $form->{"sellprice_$i"});
590         $form->{"lastcost_$i"}           = $form->format_amount(\%myconfig, $form->{"lastcost_$i"});
591         $form->{"qty_$i"}                = $form->format_amount(\%myconfig, $form->{"qty_$i"});
592         $form->{"discount_$i"}           = $form->format_amount(\%myconfig, $form->{"discount_$i"} * 100.0);
593       }
594
595       display_form();
596
597     } else {
598
599       # ok, so this is a new part
600       # ask if it is a part or service item
601
602       if (   $form->{"partsgroup_$i"}
603           && ($form->{"partsnumber_$i"} eq "")
604           && ($form->{"description_$i"} eq "")) {
605         $form->{rowcount}--;
606         $form->{"discount_$i"} = "";
607         $form->{"not_discountable_$i"} = "";
608         display_form();
609
610       } else {
611         $form->{"id_$i"}   = 0;
612         new_item();
613       }
614     }
615   }
616
617   $main::lxdebug->leave_sub();
618 }
619
620 sub search {
621   $main::lxdebug->enter_sub();
622
623   check_do_access();
624
625   my $form     = $main::form;
626   my %myconfig = %main::myconfig;
627   my $locale   = $main::locale;
628
629   $form->{vc} = $form->{type} eq 'purchase_delivery_order' ? 'vendor' : 'customer';
630
631   $form->get_lists("projects"       => { "key" => "ALL_PROJECTS",
632                                          "all" => 1 },
633                    "business_types" => "ALL_BUSINESS_TYPES");
634   $form->{ALL_EMPLOYEES} = SL::DB::Manager::Employee->get_all_sorted(query => [ deleted => 0 ]);
635   $form->{ALL_DEPARTMENTS} = SL::DB::Manager::Department->get_all;
636   $form->{title}             = $locale->text('Delivery Orders');
637
638   setup_do_search_action_bar();
639
640   $form->header();
641
642   print $form->parse_html_template('do/search');
643
644   $main::lxdebug->leave_sub();
645 }
646
647 sub orders {
648   $main::lxdebug->enter_sub();
649
650   check_do_access();
651
652   my $form     = $main::form;
653   my %myconfig = %main::myconfig;
654   my $locale   = $main::locale;
655   my $cgi      = $::request->{cgi};
656
657   $::request->{layout}->use_javascript(map { "${_}.js" } qw(kivi.MassDeliveryOrderPrint));
658   ($form->{ $form->{vc} }, $form->{"$form->{vc}_id"}) = split(/--/, $form->{ $form->{vc} });
659
660   report_generator_set_default_sort('transdate', 1);
661
662   DO->transactions();
663
664   $form->{rowcount} = scalar @{ $form->{DO} };
665
666   my @columns = qw(
667     ids                     transdate               reqdate
668     id                      donumber
669     ordnumber               customernumber          cusordnumber
670     name                    employee  salesman
671     shipvia                 globalprojectnumber
672     transaction_description department
673     open                    delivered
674     insertdate
675   );
676
677   $form->{l_open}      = $form->{l_closed} = "Y" if ($form->{open}      && $form->{closed});
678   $form->{l_delivered} = "Y"                     if ($form->{delivered} && $form->{notdelivered});
679
680   $form->{title}       = $locale->text('Delivery Orders');
681
682   my $attachment_basename = $form->{vc} eq 'vendor' ? $locale->text('purchase_delivery_order_list') : $locale->text('sales_delivery_order_list');
683
684   my $report = SL::ReportGenerator->new(\%myconfig, $form);
685
686   my @hidden_variables = map { "l_${_}" } @columns;
687   push @hidden_variables, $form->{vc}, qw(l_closed l_notdelivered open closed delivered notdelivered donumber ordnumber serialnumber cusordnumber
688                                           transaction_description transdatefrom transdateto reqdatefrom reqdateto
689                                           type vc employee_id salesman_id project_id parts_partnumber parts_description
690                                           insertdatefrom insertdateto business_id);
691
692   my $href = build_std_url('action=orders', grep { $form->{$_} } @hidden_variables);
693
694   my %column_defs = (
695     'ids'                     => { 'text' => '<input type="checkbox" id="multi_all" value="1">', 'align' => 'center' },
696     'transdate'               => { 'text' => $locale->text('Delivery Order Date'), },
697     'reqdate'                 => { 'text' => $locale->text('Reqdate'), },
698     'id'                      => { 'text' => $locale->text('ID'), },
699     'donumber'                => { 'text' => $locale->text('Delivery Order'), },
700     'ordnumber'               => { 'text' => $locale->text('Order'), },
701     'customernumber'          => { 'text' => $locale->text('Customer Number'), },
702     'cusordnumber'            => { 'text' => $locale->text('Customer Order Number'), },
703     'name'                    => { 'text' => $form->{vc} eq 'customer' ? $locale->text('Customer') : $locale->text('Vendor'), },
704     'employee'                => { 'text' => $locale->text('Employee'), },
705     'salesman'                => { 'text' => $locale->text('Salesman'), },
706     'shipvia'                 => { 'text' => $locale->text('Ship via'), },
707     'globalprojectnumber'     => { 'text' => $locale->text('Project Number'), },
708     'transaction_description' => { 'text' => $locale->text('Transaction description'), },
709     'open'                    => { 'text' => $locale->text('Open'), },
710     'delivered'               => { 'text' => $locale->text('Delivered'), },
711     'department'              => { 'text' => $locale->text('Department'), },
712     'insertdate'              => { 'text' => $locale->text('Insert Date'), },
713   );
714
715   foreach my $name (qw(id transdate reqdate donumber ordnumber name employee salesman shipvia transaction_description department insertdate)) {
716     my $sortdir                 = $form->{sort} eq $name ? 1 - $form->{sortdir} : $form->{sortdir};
717     $column_defs{$name}->{link} = $href . "&sort=$name&sortdir=$sortdir";
718   }
719
720   $form->{"l_type"} = "Y";
721   map { $column_defs{$_}->{visible} = $form->{"l_${_}"} ? 1 : 0 } @columns;
722
723   $column_defs{ids}->{visible} = 'HTML';
724
725   $report->set_columns(%column_defs);
726   $report->set_column_order(@columns);
727
728   $report->set_export_options('orders', @hidden_variables, qw(sort sortdir));
729
730   $report->set_sort_indicator($form->{sort}, $form->{sortdir});
731
732   my @options;
733   if ($form->{customer}) {
734     push @options, $locale->text('Customer') . " : $form->{customer}";
735   }
736   if ($form->{vendor}) {
737     push @options, $locale->text('Vendor') . " : $form->{vendor}";
738   }
739   if ($form->{cp_name}) {
740     push @options, $locale->text('Contact Person') . " : $form->{cp_name}";
741   }
742   if ($form->{department_id}) {
743     push @options, $locale->text('Department') . " : " . SL::DB::Department->new(id => $form->{department_id})->load->description;
744   }
745   if ($form->{donumber}) {
746     push @options, $locale->text('Delivery Order Number') . " : $form->{donumber}";
747   }
748   if ($form->{ordnumber}) {
749     push @options, $locale->text('Order Number') . " : $form->{ordnumber}";
750   }
751   push @options, $locale->text('Serial Number') . " : $form->{serialnumber}" if $form->{serialnumber};
752   if ($form->{business_id}) {
753     my $vc_type_label = $form->{vc} eq 'customer' ? $locale->text('Customer type') : $locale->text('Vendor type');
754     push @options, $vc_type_label . " : " . SL::DB::Business->new(id => $form->{business_id})->load->description;
755   }
756   if ($form->{transaction_description}) {
757     push @options, $locale->text('Transaction description') . " : $form->{transaction_description}";
758   }
759   if ($form->{parts_description}) {
760     push @options, $locale->text('Part Description') . " : $form->{parts_description}";
761   }
762   if ($form->{parts_partnumber}) {
763     push @options, $locale->text('Part Number') . " : $form->{parts_partnumber}";
764   }
765   if ( $form->{transdatefrom} or $form->{transdateto} ) {
766     push @options, $locale->text('Delivery Order Date');
767     push @options, $locale->text('From') . " " . $locale->date(\%myconfig, $form->{transdatefrom}, 1)     if $form->{transdatefrom};
768     push @options, $locale->text('Bis')  . " " . $locale->date(\%myconfig, $form->{transdateto},   1)     if $form->{transdateto};
769   };
770   if ( $form->{reqdatefrom} or $form->{reqdateto} ) {
771     push @options, $locale->text('Reqdate');
772     push @options, $locale->text('From') . " " . $locale->date(\%myconfig, $form->{reqdatefrom}, 1)       if $form->{reqdatefrom};
773     push @options, $locale->text('Bis')  . " " . $locale->date(\%myconfig, $form->{reqdateto},   1)       if $form->{reqdateto};
774   };
775   if ( $form->{insertdatefrom} or $form->{insertdateto} ) {
776     push @options, $locale->text('Insert Date');
777     push @options, $locale->text('From') . " " . $locale->date(\%myconfig, $form->{insertdatefrom}, 1)    if $form->{insertdatefrom};
778     push @options, $locale->text('Bis')  . " " . $locale->date(\%myconfig, $form->{insertdateto},   1)    if $form->{insertdateto};
779   };
780   if ($form->{open}) {
781     push @options, $locale->text('Open');
782   }
783   if ($form->{closed}) {
784     push @options, $locale->text('Closed');
785   }
786   if ($form->{delivered}) {
787     push @options, $locale->text('Delivered');
788   }
789   if ($form->{notdelivered}) {
790     push @options, $locale->text('Not delivered');
791   }
792
793   my $pr = SL::DB::Manager::Printer->find_by(
794       printer_description => $::locale->text("sales_delivery_order_printer"));
795   if ($pr ) {
796       $form->{printer_id} = $pr->id;
797   }
798
799   $report->set_options('top_info_text'        => join("\n", @options),
800                        'raw_top_info_text'    => $form->parse_html_template('do/orders_top'),
801                        'raw_bottom_info_text' => $form->parse_html_template('do/orders_bottom', { print_options => print_options(inline => 1,hide_language_id => 1) }),
802                        'output_format'        => 'HTML',
803                        'title'                => $form->{title},
804                        'attachment_basename'  => $attachment_basename . strftime('_%Y%m%d', localtime time),
805     );
806   $report->set_options_from_form();
807   $locale->set_numberformat_wo_thousands_separator(\%myconfig) if lc($report->{options}->{output_format}) eq 'csv';
808
809   # add sort and escape callback, this one we use for the add sub
810   $form->{callback} = $href .= "&sort=$form->{sort}";
811
812   # escape callback for href
813   my $callback = $form->escape($href);
814
815   my $edit_url       = build_std_url('action=edit', 'type', 'vc');
816   my $edit_order_url = build_std_url('script=oe.pl', 'type=' . ($form->{type} eq 'sales_delivery_order' ? 'sales_order' : 'purchase_order'), 'action=edit');
817
818   my $idx            = 1;
819
820   foreach my $dord (@{ $form->{DO} }) {
821     $dord->{open}      = $dord->{closed}    ? $locale->text('No')  : $locale->text('Yes');
822     $dord->{delivered} = $dord->{delivered} ? $locale->text('Yes') : $locale->text('No');
823
824     my $row = { map { $_ => { 'data' => $dord->{$_} } } @columns };
825
826     my $ord_id = $dord->{id};
827     $row->{ids}  = {
828       'raw_data' =>   $cgi->hidden('-name' => "trans_id_${idx}", '-value' => $ord_id)
829                     . $cgi->checkbox('-name' => "multi_id_${idx}",' id' => "multi_id_id_".$ord_id, '-value' => 1, '-label' => ''),
830       'valign'   => 'center',
831       'align'    => 'center',
832     };
833
834     $row->{donumber}->{link}  = $edit_url       . "&id=" . E($dord->{id})      . "&callback=${callback}";
835     $row->{ordnumber}->{link} = $edit_order_url . "&id=" . E($dord->{oe_id})   . "&callback=${callback}" if $dord->{oe_id};
836     $report->add_data($row);
837
838     $idx++;
839   }
840
841   $::request->layout->add_javascripts('kivi.DeliveryOrder.js');
842
843   setup_do_orders_action_bar();
844
845   $report->generate_with_headers(action_bar => 1);
846
847   $main::lxdebug->leave_sub();
848 }
849
850 sub save {
851   $main::lxdebug->enter_sub();
852
853   my (%params) = @_;
854
855   check_do_access();
856
857   my $form     = $main::form;
858   my %myconfig = %main::myconfig;
859   my $locale   = $main::locale;
860
861   $form->mtime_ischanged('delivery_orders');
862
863   $form->{defaultcurrency} = $form->get_default_currency(\%myconfig);
864
865   $form->isblank("transdate", $locale->text('Delivery Order Date missing!'));
866
867   $form->{donumber} =~ s/^\s*//g;
868   $form->{donumber} =~ s/\s*$//g;
869
870   my $msg = ucfirst $form->{vc};
871   $form->isblank($form->{vc} . "_id", $locale->text($msg . " missing!"));
872
873   # $locale->text('Customer missing!');
874   # $locale->text('Vendor missing!');
875
876   remove_emptied_rows();
877   validate_items();
878
879   # if the name changed get new values
880   my $vc = $form->{vc};
881   if (($form->{"previous_${vc}_id"} || $form->{"${vc}_id"}) != $form->{"${vc}_id"}) {
882     $::form->{salesman_id} = SL::DB::Manager::Employee->current->id if exists $::form->{salesman_id};
883
884     IS->get_customer(\%myconfig, $form) if $vc eq 'customer';
885     IR->get_vendor(\%myconfig, $form)   if $vc eq 'vendor';
886
887     update();
888     $::dispatcher->end_request;
889   }
890
891   $form->{id} = 0 if $form->{saveasnew};
892
893   DO->save();
894   # saving the history
895   if(!exists $form->{addition}) {
896     $form->{snumbers} = qq|donumber_| . $form->{donumber};
897     $form->{addition} = "SAVED";
898     $form->save_history;
899   }
900   # /saving the history
901
902   $form->{simple_save} = 1;
903   if (!$params{no_redirect} && !$form->{print_and_save}) {
904     delete @{$form}{ary_diff([keys %{ $form }], [qw(login id script type cursor_fokus)])};
905     edit();
906     $::dispatcher->end_request;
907   }
908   $main::lxdebug->leave_sub();
909 }
910
911 sub delete {
912   $main::lxdebug->enter_sub();
913
914   check_do_access();
915
916   my $form     = $main::form;
917   my %myconfig = %main::myconfig;
918   my $locale   = $main::locale;
919
920   if (DO->delete()) {
921     # saving the history
922     if(!exists $form->{addition}) {
923       $form->{snumbers} = qq|donumber_| . $form->{donumber};
924       $form->{addition} = "DELETED";
925       $form->save_history;
926     }
927     # /saving the history
928
929     $form->info($locale->text('Delivery Order deleted!'));
930     $::dispatcher->end_request;
931   }
932
933   $form->error($locale->text('Cannot delete delivery order!'));
934
935   $main::lxdebug->leave_sub();
936 }
937
938 sub invoice {
939   $main::lxdebug->enter_sub();
940
941   my $form     = $main::form;
942   my %myconfig = %main::myconfig;
943   my $locale   = $main::locale;
944
945   check_do_access();
946   $form->mtime_ischanged('delivery_orders');
947
948   $main::auth->assert($form->{type} eq 'purchase_delivery_order' ? 'vendor_invoice_edit' : 'invoice_edit');
949
950   $form->{convert_from_do_ids} = $form->{id};
951   $form->{deliverydate}        = $form->{transdate};
952   $form->{transdate}           = $form->{invdate} = $form->current_date(\%myconfig);
953   $form->{duedate}             = $form->current_date(\%myconfig, $form->{invdate}, $form->{terms} * 1);
954   $form->{defaultcurrency}     = $form->get_default_currency(\%myconfig);
955
956   $form->{rowcount}--;
957
958   delete @{$form}{qw(id closed delivered)};
959
960   my ($script, $buysell);
961   if ($form->{type} eq 'purchase_delivery_order') {
962     $form->{title}  = $locale->text('Add Vendor Invoice');
963     $form->{script} = 'ir.pl';
964     $script         = "ir";
965     $buysell        = 'sell';
966
967   } else {
968     $form->{title}  = $locale->text('Add Sales Invoice');
969     $form->{script} = 'is.pl';
970     $script         = "is";
971     $buysell        = 'buy';
972   }
973
974   for my $i (1 .. $form->{rowcount}) {
975     map { $form->{"${_}_${i}"} = $form->parse_amount(\%myconfig, $form->{"${_}_${i}"}) if $form->{"${_}_${i}"} } qw(ship qty sellprice lastcost basefactor discount);
976     # für bug 1284
977     # adds a customer/vendor discount, unless we have a workflow case
978     # CAVEAT: has to be done, after the above parse_amount
979     unless ($form->{"ordnumber"}) {
980       if ($form->{discount}) { # Falls wir einen Lieferanten-/Kundenrabatt haben
981         # und rabattfähig sind, dann
982         unless ($form->{"not_discountable_$i"}) {
983           $form->{"discount_$i"} = $form->{discount}*100; # ... nehmen wir diesen Rabatt
984         }
985       }
986     }
987     $form->{"donumber_$i"} = $form->{donumber};
988     $form->{"converted_from_delivery_order_items_id_$i"} = delete $form->{"delivery_order_items_id_$i"};
989   }
990
991   $form->{type} = "invoice";
992
993   # locale messages
994   $main::locale = Locale->new("$myconfig{countrycode}", "$script");
995   $locale = $main::locale;
996
997   require "bin/mozilla/$form->{script}";
998
999   my $currency = $form->{currency};
1000   invoice_links();
1001
1002   if ($form->{ordnumber}) {
1003     require SL::DB::Order;
1004     if (my $order = SL::DB::Manager::Order->find_by(ordnumber => $form->{ordnumber})) {
1005       $order->load;
1006       $form->{orddate} = $order->transdate_as_date;
1007       $form->{$_}      = $order->$_ for qw(payment_id salesman_id taxzone_id quonumber);
1008     }
1009   }
1010
1011   $form->{currency}     = $currency;
1012   $form->{exchangerate} = "";
1013   $form->{forex}        = $form->check_exchangerate(\%myconfig, $form->{currency}, $form->{invdate}, $buysell);
1014   $form->{exchangerate} = $form->{forex} if ($form->{forex});
1015
1016   prepare_invoice();
1017
1018   # format amounts
1019   for my $i (1 .. $form->{rowcount}) {
1020     $form->{"discount_$i"} = $form->format_amount(\%myconfig, $form->{"discount_$i"});
1021
1022     my ($dec) = ($form->{"sellprice_$i"} =~ /\.(\d+)/);
1023     $dec           = length $dec;
1024     my $decimalplaces = ($dec > 2) ? $dec : 2;
1025
1026     # copy delivery date from reqdate for order -> invoice conversion
1027     $form->{"deliverydate_$i"} = $form->{"reqdate_$i"}
1028       unless $form->{"deliverydate_$i"};
1029
1030
1031     $form->{"sellprice_$i"} =
1032       $form->format_amount(\%myconfig, $form->{"sellprice_$i"},
1033                            $decimalplaces);
1034
1035     $form->{"lastcost_$i"} =
1036       $form->format_amount(\%myconfig, $form->{"lastcost_$i"},
1037                            $decimalplaces);
1038
1039     (my $dec_qty) = ($form->{"qty_$i"} =~ /\.(\d+)/);
1040     $dec_qty = length $dec_qty;
1041     $form->{"qty_$i"} =
1042       $form->format_amount(\%myconfig, $form->{"qty_$i"}, $dec_qty);
1043
1044   }
1045
1046   display_form();
1047
1048   $main::lxdebug->leave_sub();
1049 }
1050
1051 sub invoice_multi {
1052   $main::lxdebug->enter_sub();
1053
1054   my $form     = $main::form;
1055   my %myconfig = %main::myconfig;
1056   my $locale   = $main::locale;
1057
1058   check_do_access();
1059   $main::auth->assert($form->{type} eq 'sales_delivery_order' ? 'invoice_edit' : 'vendor_invoice_edit');
1060
1061   my @do_ids = map { $form->{"trans_id_$_"} } grep { $form->{"multi_id_$_"} } (1..$form->{rowcount});
1062
1063   if (!scalar @do_ids) {
1064     $form->show_generic_error($locale->text('You have not selected any delivery order.'));
1065   }
1066
1067   map { delete $form->{$_} } grep { m/^(?:trans|multi)_id_\d+/ } keys %{ $form };
1068
1069   if (!DO->retrieve('vc' => $form->{vc}, 'ids' => \@do_ids)) {
1070     $form->show_generic_error($form->{vc} eq 'customer' ?
1071                               $locale->text('You cannot create an invoice for delivery orders for different customers.') :
1072                               $locale->text('You cannot create an invoice for delivery orders from different vendors.'),
1073                               'back_button' => 1);
1074   }
1075
1076   my $source_type              = $form->{type};
1077   $form->{convert_from_do_ids} = join ' ', @do_ids;
1078   # bei der auswahl von mehreren Lieferscheinen fuer eine Rechnung, die einfach in donumber_array
1079   # zwischenspeichern (DO.pm) und als ' '-separierte Liste wieder zurueckschreiben
1080   # Hinweis: delete gibt den wert zurueck und loescht danach das element (nett und einfach)
1081   # $shell: perldoc perlunc; /delete EXPR
1082   $form->{donumber}            = delete $form->{donumber_array};
1083   $form->{ordnumber}           = delete $form->{ordnumber_array};
1084   $form->{cusordnumber}        = delete $form->{cusordnumber_array};
1085   $form->{deliverydate}        = $form->{transdate};
1086   $form->{transdate}           = $form->current_date(\%myconfig);
1087   $form->{duedate}             = $form->current_date(\%myconfig, $form->{invdate}, $form->{terms} * 1);
1088   $form->{type}                = "invoice";
1089   $form->{closed}              = 0;
1090   $form->{defaultcurrency}     = $form->get_default_currency(\%myconfig);
1091
1092   my ($script, $buysell);
1093   if ($source_type eq 'purchase_delivery_order') {
1094     $form->{title}  = $locale->text('Add Vendor Invoice');
1095     $form->{script} = 'ir.pl';
1096     $script         = "ir";
1097     $buysell        = 'sell';
1098
1099   } else {
1100     $form->{title}  = $locale->text('Add Sales Invoice');
1101     $form->{script} = 'is.pl';
1102     $script         = "is";
1103     $buysell        = 'buy';
1104   }
1105
1106   map { delete $form->{$_} } qw(id subject message cc bcc printed emailed queued);
1107
1108   # get vendor or customer discount
1109   my $vc_discount;
1110   my $saved_form = save_form();
1111   if ($form->{vc} eq 'vendor') {
1112     IR->get_vendor(\%myconfig, \%$form);
1113     $vc_discount = $form->{vendor_discount};
1114   } else {
1115     IS->get_customer(\%myconfig, \%$form);
1116     $vc_discount = $form->{customer_discount};
1117   }
1118   # use payment terms from customer or vendor
1119   restore_form($saved_form,0,qw(payment_id));
1120
1121   $form->{rowcount} = 0;
1122   foreach my $ref (@{ $form->{form_details} }) {
1123     $form->{rowcount}++;
1124     $ref->{reqdate} ||= $ref->{dord_transdate}; # copy transdates into each invoice row
1125     map { $form->{"${_}_$form->{rowcount}"} = $ref->{$_} } keys %{ $ref };
1126     map { $form->{"${_}_$form->{rowcount}"} = $form->format_amount(\%myconfig, $ref->{$_}) } qw(qty sellprice lastcost);
1127     $form->{"converted_from_delivery_order_items_id_$form->{rowcount}"} = delete $form->{"delivery_order_items_id_$form->{rowcount}"};
1128
1129     if ($vc_discount){ # falls wir einen Lieferanten/Kundenrabatt haben
1130       # und keinen anderen discount wert an $i ...
1131       $form->{"discount_$form->{rowcount}"} ||= $vc_discount; # ... nehmen wir diesen Rabatt
1132     }
1133
1134     $form->{"discount_$form->{rowcount}"}   = $form->{"discount_$form->{rowcount}"}  * 100; #s.a. Bug 1151
1135     # Anm.: Eine Änderung des discounts in der SL/DO.pm->retrieve (select (doi.discount * 100) as discount) ergibt in psql einen
1136     # Wert von 10.0000001490116. Ferner ist der Rabatt in der Rechnung dann bei 1.0 (?). Deswegen lasse ich das hier. jb 10.10.09
1137
1138     $form->{"discount_$form->{rowcount}"} = $form->format_amount(\%myconfig, $form->{"discount_$form->{rowcount}"});
1139   }
1140   delete $form->{form_details};
1141
1142   $locale = Locale->new("$myconfig{countrycode}", "$script");
1143
1144   require "bin/mozilla/$form->{script}";
1145
1146   invoice_links();
1147   prepare_invoice();
1148
1149   display_form();
1150
1151   $main::lxdebug->leave_sub();
1152 }
1153
1154 sub save_as_new {
1155   $main::lxdebug->enter_sub();
1156
1157   check_do_access();
1158
1159   my $form     = $main::form;
1160
1161   $form->{saveasnew} = 1;
1162   $form->{closed}    = 0;
1163   $form->{delivered} = 0;
1164   map { delete $form->{$_} } qw(printed emailed queued);
1165   delete @{ $form }{ grep { m/^stock_(?:in|out)_\d+/ } keys %{ $form } };
1166   $form->{"converted_from_delivery_order_items_id_$_"} = delete $form->{"delivery_order_items_id_$_"} for 1 .. $form->{"rowcount"};
1167   # Let kivitendo assign a new order number if the user hasn't changed the
1168   # previous one. If it has been changed manually then use it as-is.
1169   $form->{donumber} =~ s/^\s*//g;
1170   $form->{donumber} =~ s/\s*$//g;
1171   if ($form->{saved_donumber} && ($form->{saved_donumber} eq $form->{donumber})) {
1172     delete($form->{donumber});
1173   }
1174
1175   save();
1176
1177   $main::lxdebug->leave_sub();
1178 }
1179
1180 sub e_mail {
1181   $main::lxdebug->enter_sub();
1182
1183   check_do_access();
1184
1185   $::form->mtime_ischanged('delivery_orders','mail');
1186
1187   $::form->{print_and_save} = 1;
1188
1189   my $saved_form = save_form();
1190
1191   save();
1192
1193   restore_form($saved_form, 0, qw(id ordnumber quonumber));
1194
1195   edit_e_mail();
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 e_mail 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)