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