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